From 34e6c26b9fa5b94d01ae825951be009ed38801dd Mon Sep 17 00:00:00 2001 From: k9er Date: Mon, 13 Jan 2025 22:23:07 +0000 Subject: [PATCH] Split up nades & buffs into individual files --- qcsrc/client/hud/panel/ammo.qc | 2 +- qcsrc/client/weapons/projectile.qc | 2 +- qcsrc/common/monsters/sv_monsters.qc | 2 +- qcsrc/common/mutators/mutator/buffs/_mod.inc | 2 + qcsrc/common/mutators/mutator/buffs/_mod.qh | 2 + qcsrc/common/mutators/mutator/buffs/all.inc | 157 -- .../mutators/mutator/buffs/buff/_mod.inc | 14 + .../mutators/mutator/buffs/buff/_mod.qh | 14 + .../mutators/mutator/buffs/buff/ammo.qc | 59 + .../mutators/mutator/buffs/buff/ammo.qh | 20 + .../mutators/mutator/buffs/buff/bash.qc | 21 + .../mutators/mutator/buffs/buff/bash.qh | 23 + .../mutators/mutator/buffs/buff/disability.qc | 44 + .../mutators/mutator/buffs/buff/disability.qh | 23 + .../mutators/mutator/buffs/buff/flight.qc | 25 + .../mutators/mutator/buffs/buff/flight.qh | 20 + .../mutators/mutator/buffs/buff/inferno.qc | 17 + .../mutators/mutator/buffs/buff/inferno.qh | 26 + .../mutators/mutator/buffs/buff/jump.qc | 19 + .../mutators/mutator/buffs/buff/jump.qh | 21 + .../mutators/mutator/buffs/buff/luck.qc | 11 + .../mutators/mutator/buffs/buff/luck.qh | 21 + .../mutators/mutator/buffs/buff/magnet.qc | 26 + .../mutators/mutator/buffs/buff/magnet.qh | 19 + .../mutators/mutator/buffs/buff/medic.qc | 22 + .../mutators/mutator/buffs/buff/medic.qh | 26 + .../mutators/mutator/buffs/buff/resistance.qc | 8 + .../mutators/mutator/buffs/buff/resistance.qh | 21 + .../mutators/mutator/buffs/buff/swapper.qc | 82 + .../mutators/mutator/buffs/buff/swapper.qh | 19 + .../mutators/mutator/buffs/buff/vampire.qc | 19 + .../mutators/mutator/buffs/buff/vampire.qh | 19 + .../mutators/mutator/buffs/buff/vengeance.qc | 16 + .../mutators/mutator/buffs/buff/vengeance.qh | 22 + qcsrc/common/mutators/mutator/buffs/buffs.qc | 21 +- qcsrc/common/mutators/mutator/buffs/buffs.qh | 2 - .../common/mutators/mutator/buffs/sv_buffs.qc | 319 +-- .../common/mutators/mutator/buffs/sv_buffs.qh | 33 +- qcsrc/common/mutators/mutator/nades/_mod.inc | 8 + qcsrc/common/mutators/mutator/nades/_mod.qh | 8 + qcsrc/common/mutators/mutator/nades/all.inc | 63 +- .../common/mutators/mutator/nades/cl_nades.qc | 122 ++ .../common/mutators/mutator/nades/cl_nades.qh | 6 + .../mutators/mutator/nades/nade/_mod.inc | 12 + .../mutators/mutator/nades/nade/_mod.qh | 12 + .../mutators/mutator/nades/nade/ammo.qc | 66 + .../mutators/mutator/nades/nade/ammo.qh | 24 + .../mutators/mutator/nades/nade/darkness.qc | 138 ++ .../mutators/mutator/nades/nade/darkness.qh | 21 + .../mutators/mutator/nades/nade/entrap.qc | 66 + .../mutators/mutator/nades/nade/entrap.qh | 24 + .../mutators/mutator/nades/nade/heal.qc | 41 + .../mutators/mutator/nades/nade/heal.qh | 22 + .../common/mutators/mutator/nades/nade/ice.qc | 116 ++ .../common/mutators/mutator/nades/nade/ice.qh | 22 + .../mutators/mutator/nades/nade/monster.qc | 21 + .../mutators/mutator/nades/nade/monster.qh | 21 + .../mutators/mutator/nades/nade/napalm.qc | 178 ++ .../mutators/mutator/nades/nade/napalm.qh | 33 + .../mutators/mutator/nades/nade/normal.qc | 11 + .../mutators/mutator/nades/nade/normal.qh | 13 + .../mutators/mutator/nades/nade/spawn.qc | 34 + .../mutators/mutator/nades/nade/spawn.qh | 19 + .../mutator/nades/nade/translocate.qc | 39 + .../mutator/nades/nade/translocate.qh | 19 + .../mutators/mutator/nades/nade/veil.qc | 45 + .../mutators/mutator/nades/nade/veil.qh | 25 + qcsrc/common/mutators/mutator/nades/nades.qc | 1789 +---------------- qcsrc/common/mutators/mutator/nades/nades.qh | 174 +- .../common/mutators/mutator/nades/sv_nades.qc | 951 +++++++++ .../common/mutators/mutator/nades/sv_nades.qh | 73 + qcsrc/server/client.qc | 2 +- qcsrc/server/compat/quake3.qc | 3 +- qcsrc/server/damage.qc | 3 +- qcsrc/server/items/items.qc | 3 +- 75 files changed, 2941 insertions(+), 2505 deletions(-) delete mode 100644 qcsrc/common/mutators/mutator/buffs/all.inc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/_mod.inc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/_mod.qh create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/ammo.qc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/ammo.qh create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/bash.qc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/bash.qh create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/disability.qc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/disability.qh create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/flight.qc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/flight.qh create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/inferno.qc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/inferno.qh create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/jump.qc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/jump.qh create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/luck.qc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/luck.qh create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/magnet.qc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/magnet.qh create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/medic.qc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/medic.qh create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/resistance.qc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/resistance.qh create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/swapper.qc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/swapper.qh create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/vampire.qc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/vampire.qh create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/vengeance.qc create mode 100644 qcsrc/common/mutators/mutator/buffs/buff/vengeance.qh create mode 100644 qcsrc/common/mutators/mutator/nades/cl_nades.qc create mode 100644 qcsrc/common/mutators/mutator/nades/cl_nades.qh create mode 100644 qcsrc/common/mutators/mutator/nades/nade/_mod.inc create mode 100644 qcsrc/common/mutators/mutator/nades/nade/_mod.qh create mode 100644 qcsrc/common/mutators/mutator/nades/nade/ammo.qc create mode 100644 qcsrc/common/mutators/mutator/nades/nade/ammo.qh create mode 100644 qcsrc/common/mutators/mutator/nades/nade/darkness.qc create mode 100644 qcsrc/common/mutators/mutator/nades/nade/darkness.qh create mode 100644 qcsrc/common/mutators/mutator/nades/nade/entrap.qc create mode 100644 qcsrc/common/mutators/mutator/nades/nade/entrap.qh create mode 100644 qcsrc/common/mutators/mutator/nades/nade/heal.qc create mode 100644 qcsrc/common/mutators/mutator/nades/nade/heal.qh create mode 100644 qcsrc/common/mutators/mutator/nades/nade/ice.qc create mode 100644 qcsrc/common/mutators/mutator/nades/nade/ice.qh create mode 100644 qcsrc/common/mutators/mutator/nades/nade/monster.qc create mode 100644 qcsrc/common/mutators/mutator/nades/nade/monster.qh create mode 100644 qcsrc/common/mutators/mutator/nades/nade/napalm.qc create mode 100644 qcsrc/common/mutators/mutator/nades/nade/napalm.qh create mode 100644 qcsrc/common/mutators/mutator/nades/nade/normal.qc create mode 100644 qcsrc/common/mutators/mutator/nades/nade/normal.qh create mode 100644 qcsrc/common/mutators/mutator/nades/nade/spawn.qc create mode 100644 qcsrc/common/mutators/mutator/nades/nade/spawn.qh create mode 100644 qcsrc/common/mutators/mutator/nades/nade/translocate.qc create mode 100644 qcsrc/common/mutators/mutator/nades/nade/translocate.qh create mode 100644 qcsrc/common/mutators/mutator/nades/nade/veil.qc create mode 100644 qcsrc/common/mutators/mutator/nades/nade/veil.qh create mode 100644 qcsrc/common/mutators/mutator/nades/sv_nades.qc create mode 100644 qcsrc/common/mutators/mutator/nades/sv_nades.qh diff --git a/qcsrc/client/hud/panel/ammo.qc b/qcsrc/client/hud/panel/ammo.qc index 891123bf1..8835adc4a 100644 --- a/qcsrc/client/hud/panel/ammo.qc +++ b/qcsrc/client/hud/panel/ammo.qc @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include diff --git a/qcsrc/client/weapons/projectile.qc b/qcsrc/client/weapons/projectile.qc index cf2e86e64..dfd9c35f5 100644 --- a/qcsrc/client/weapons/projectile.qc +++ b/qcsrc/client/weapons/projectile.qc @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc index 233f3ef40..cae2085d8 100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/qcsrc/common/mutators/mutator/buffs/_mod.inc b/qcsrc/common/mutators/mutator/buffs/_mod.inc index e66d3d859..3c8ae5b25 100644 --- a/qcsrc/common/mutators/mutator/buffs/_mod.inc +++ b/qcsrc/common/mutators/mutator/buffs/_mod.inc @@ -6,3 +6,5 @@ #ifdef SVQC #include #endif + +#include diff --git a/qcsrc/common/mutators/mutator/buffs/_mod.qh b/qcsrc/common/mutators/mutator/buffs/_mod.qh index 4d1bf6ec4..6a4f211e5 100644 --- a/qcsrc/common/mutators/mutator/buffs/_mod.qh +++ b/qcsrc/common/mutators/mutator/buffs/_mod.qh @@ -6,3 +6,5 @@ #ifdef SVQC #include #endif + +#include diff --git a/qcsrc/common/mutators/mutator/buffs/all.inc b/qcsrc/common/mutators/mutator/buffs/all.inc deleted file mode 100644 index a5127a153..000000000 --- a/qcsrc/common/mutators/mutator/buffs/all.inc +++ /dev/null @@ -1,157 +0,0 @@ -string Buff_CompatName(string buffname) -{ - switch(buffname) - { - case "ammoregen": return "ammo"; // Q3TA ammoregen - case "doubler": return "inferno"; // Q3TA doubler - case "scout": return "bash"; // Q3TA scout - case "guard": return "resistance"; // Q3TA guard - case "revival": case "regen": return "medic"; // WOP revival, Q3A regen - case "jumper": return "jump"; // WOP jumper - case "invulnerability": return "vampire"; // Q3TA invulnerability - case "kamikaze": return "vengeance"; // Q3TA kamikaze - case "teleporter": return "swapper"; // Q3A personal teleporter - default: return buffname; - } -} - -CLASS(AmmoBuff, Buff) - ATTRIB(AmmoBuff, m_name, string, _("Ammo")); - ATTRIB(AmmoBuff, netname, string, "ammo"); - ATTRIB(AmmoBuff, m_icon, string, "buff_ammo"); - ATTRIB(AmmoBuff, m_skin, int, 3); - ATTRIB(AmmoBuff, m_color, vector, '0.76 1 0.1'); -ENDCLASS(AmmoBuff) -REGISTER_BUFF(AMMO, NEW(AmmoBuff)); -BUFF_SPAWNFUNCS(ammo, BUFF_AMMO) -BUFF_SPAWNFUNC_Q3COMPAT(item_ammoregen, BUFF_AMMO) - -CLASS(ResistanceBuff, Buff) - ATTRIB(ResistanceBuff, m_name, string, _("Resistance")); - ATTRIB(ResistanceBuff, netname, string, "resistance"); - ATTRIB(ResistanceBuff, m_icon, string, "buff_resistance"); - ATTRIB(ResistanceBuff, m_skin, int, 0); - ATTRIB(ResistanceBuff, m_color, vector, '0.36 1 0.07'); -ENDCLASS(ResistanceBuff) -REGISTER_BUFF(RESISTANCE, NEW(ResistanceBuff)); -BUFF_SPAWNFUNCS(resistance, BUFF_RESISTANCE) -BUFF_SPAWNFUNC_Q3COMPAT(item_guard, BUFF_RESISTANCE) - -CLASS(MedicBuff, Buff) - ATTRIB(MedicBuff, m_name, string, _("Medic")); - ATTRIB(MedicBuff, netname, string, "medic"); - ATTRIB(MedicBuff, m_icon, string, "buff_medic"); - ATTRIB(MedicBuff, m_skin, int, 1); - ATTRIB(MedicBuff, m_color, vector, '1 0.12 0'); -ENDCLASS(MedicBuff) -REGISTER_BUFF(MEDIC, NEW(MedicBuff)); -BUFF_SPAWNFUNCS(medic, BUFF_MEDIC) -BUFF_SPAWNFUNC_Q3COMPAT(item_regen, BUFF_MEDIC) -BUFF_SPAWNFUNC_Q3COMPAT(item_revival, BUFF_MEDIC) - -CLASS(BashBuff, Buff) - ATTRIB(BashBuff, m_name, string, _("Bash")); - ATTRIB(BashBuff, netname, string, "bash"); - ATTRIB(BashBuff, m_icon, string, "buff_bash"); - ATTRIB(BashBuff, m_skin, int, 5); - ATTRIB(BashBuff, m_color, vector, '1 0.39 0'); -ENDCLASS(BashBuff) -REGISTER_BUFF(BASH, NEW(BashBuff)); -BUFF_SPAWNFUNCS(bash, BUFF_BASH) -BUFF_SPAWNFUNC_Q3COMPAT(item_scout, BUFF_BASH) - -CLASS(VampireBuff, Buff) - ATTRIB(VampireBuff, m_name, string, _("Vampire")); - ATTRIB(VampireBuff, netname, string, "vampire"); - ATTRIB(VampireBuff, m_icon, string, "buff_vampire"); - ATTRIB(VampireBuff, m_skin, int, 2); - ATTRIB(VampireBuff, m_color, vector, '1 0 0.24'); -ENDCLASS(VampireBuff) -REGISTER_BUFF(VAMPIRE, NEW(VampireBuff)); -BUFF_SPAWNFUNCS(vampire, BUFF_VAMPIRE) -BUFF_SPAWNFUNC_Q3COMPAT(holdable_invulnerability, BUFF_VAMPIRE) - -CLASS(DisabilityBuff, Buff) - ATTRIB(DisabilityBuff, m_name, string, _("Disability")); - ATTRIB(DisabilityBuff, netname, string, "disability"); - ATTRIB(DisabilityBuff, m_icon, string, "buff_disability"); - ATTRIB(DisabilityBuff, m_skin, int, 7); - ATTRIB(DisabilityBuff, m_color, vector, '0.94 0.3 1'); -ENDCLASS(DisabilityBuff) -REGISTER_BUFF(DISABILITY, NEW(DisabilityBuff)); -BUFF_SPAWNFUNCS(disability, BUFF_DISABILITY) - -CLASS(VengeanceBuff, Buff) - ATTRIB(VengeanceBuff, m_name, string, _("Vengeance")); - ATTRIB(VengeanceBuff, netname, string, "vengeance"); - ATTRIB(VengeanceBuff, m_icon, string, "buff_vengeance"); - ATTRIB(VengeanceBuff, m_skin, int, 15); - ATTRIB(VengeanceBuff, m_color, vector, '1 0.23 0.61'); -ENDCLASS(VengeanceBuff) -REGISTER_BUFF(VENGEANCE, NEW(VengeanceBuff)); -BUFF_SPAWNFUNCS(vengeance, BUFF_VENGEANCE) -BUFF_SPAWNFUNC_Q3COMPAT(holdable_kamikaze, BUFF_VENGEANCE) - -CLASS(JumpBuff, Buff) - ATTRIB(JumpBuff, m_name, string, _("Jump")); - ATTRIB(JumpBuff, netname, string, "jump"); - ATTRIB(JumpBuff, m_icon, string, "buff_jump"); - ATTRIB(JumpBuff, m_skin, int, 10); - ATTRIB(JumpBuff, m_color, vector, '0.24 0.78 1'); -ENDCLASS(JumpBuff) -REGISTER_BUFF(JUMP, NEW(JumpBuff)); -BUFF_SPAWNFUNCS(jump, BUFF_JUMP) -BUFF_SPAWNFUNC_Q3COMPAT(item_jumper, BUFF_JUMP) - -CLASS(InfernoBuff, Buff) - ATTRIB(InfernoBuff, m_name, string, _("Inferno")); - ATTRIB(InfernoBuff, netname, string, "inferno"); - ATTRIB(InfernoBuff, m_icon, string, "buff_inferno"); - ATTRIB(InfernoBuff, m_skin, int, 16); - ATTRIB(InfernoBuff, m_color, vector, '1 0.62 0'); -ENDCLASS(InfernoBuff) -REGISTER_BUFF(INFERNO, NEW(InfernoBuff)); -BUFF_SPAWNFUNCS(inferno, BUFF_INFERNO) -BUFF_SPAWNFUNC_Q3COMPAT(item_doubler, BUFF_INFERNO) - -CLASS(SwapperBuff, Buff) - ATTRIB(SwapperBuff, m_name, string, _("Swapper")); - ATTRIB(SwapperBuff, netname, string, "swapper"); - ATTRIB(SwapperBuff, m_icon, string, "buff_swapper"); - ATTRIB(SwapperBuff, m_skin, int, 17); - ATTRIB(SwapperBuff, m_color, vector, '0.63 0.36 1'); -ENDCLASS(SwapperBuff) -REGISTER_BUFF(SWAPPER, NEW(SwapperBuff)); -BUFF_SPAWNFUNCS(swapper, BUFF_SWAPPER) -BUFF_SPAWNFUNC_Q3COMPAT(holdable_teleporter, BUFF_SWAPPER) - -CLASS(MagnetBuff, Buff) - ATTRIB(MagnetBuff, m_name, string, _("Magnet")); - ATTRIB(MagnetBuff, netname, string, "magnet"); - ATTRIB(MagnetBuff, m_icon, string, "buff_magnet"); - ATTRIB(MagnetBuff, m_skin, int, 18); - ATTRIB(MagnetBuff, m_color, vector, '1 0.95 0.18'); -ENDCLASS(MagnetBuff) -REGISTER_BUFF(MAGNET, NEW(MagnetBuff)); -BUFF_SPAWNFUNCS(magnet, BUFF_MAGNET) - -CLASS(LuckBuff, Buff) - ATTRIB(LuckBuff, m_name, string, _("Luck")); - ATTRIB(LuckBuff, netname, string, "luck"); - ATTRIB(LuckBuff, m_icon, string, "buff_luck"); - ATTRIB(LuckBuff, m_skin, int, 19); - ATTRIB(LuckBuff, m_color, vector, '1 0.23 0.44'); -ENDCLASS(LuckBuff) -REGISTER_BUFF(LUCK, NEW(LuckBuff)); -BUFF_SPAWNFUNCS(luck, BUFF_LUCK) - -CLASS(FlightBuff, Buff) - ATTRIB(FlightBuff, m_name, string, _("Flight")); - ATTRIB(FlightBuff, netname, string, "flight"); - ATTRIB(FlightBuff, m_icon, string, "buff_flight"); - ATTRIB(FlightBuff, m_skin, int, 11); - ATTRIB(FlightBuff, m_color, vector, '0.23 0.44 1'); -ENDCLASS(FlightBuff) -REGISTER_BUFF(FLIGHT, NEW(FlightBuff)); -BUFF_SPAWNFUNCS(flight, BUFF_FLIGHT) -BUFF_SPAWNFUNC_Q3COMPAT(item_flight, BUFF_FLIGHT) diff --git a/qcsrc/common/mutators/mutator/buffs/buff/_mod.inc b/qcsrc/common/mutators/mutator/buffs/buff/_mod.inc new file mode 100644 index 000000000..4e54149d0 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/_mod.inc @@ -0,0 +1,14 @@ +// genmod.sh autogenerated file; do not modify +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/qcsrc/common/mutators/mutator/buffs/buff/_mod.qh b/qcsrc/common/mutators/mutator/buffs/buff/_mod.qh new file mode 100644 index 000000000..d62f49bfc --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/_mod.qh @@ -0,0 +1,14 @@ +// genmod.sh autogenerated file; do not modify +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/qcsrc/common/mutators/mutator/buffs/buff/ammo.qc b/qcsrc/common/mutators/mutator/buffs/buff/ammo.qc new file mode 100644 index 000000000..441034a2b --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/ammo.qc @@ -0,0 +1,59 @@ +#include "ammo.qh" + +#ifdef SVQC +METHOD(AmmoBuff, m_apply, void(StatusEffect this, entity actor, float eff_time, float eff_flags)) +{ + bool wasactive = (actor.statuseffects && (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE)); + if(!wasactive) + { + actor.buff_ammo_prev_infitems = (actor.items & IT_UNLIMITED_AMMO); + actor.items |= IT_UNLIMITED_AMMO; + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + if(!actor.(weaponentity)) + continue; + if(actor.(weaponentity).clip_load) + actor.(weaponentity).buff_ammo_prev_clipload = actor.(weaponentity).clip_load; + if(actor.(weaponentity).clip_size) + actor.(weaponentity).clip_load = actor.(weaponentity).(weapon_load[actor.(weaponentity).m_switchweapon.m_id]) = actor.(weaponentity).clip_size; + } + } + SUPER(AmmoBuff).m_apply(this, actor, eff_time, eff_flags); +} +METHOD(AmmoBuff, m_remove, void(StatusEffect this, entity actor, int removal_type)) +{ + bool wasactive = (actor.statuseffects && (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE)); + if(wasactive) + { + actor.items = BITSET(actor.items, IT_UNLIMITED_AMMO, actor.buff_ammo_prev_infitems); + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + if(!actor.(weaponentity)) + continue; + if(actor.(weaponentity).buff_ammo_prev_clipload) + { + actor.(weaponentity).clip_load = actor.(weaponentity).buff_ammo_prev_clipload; + actor.(weaponentity).buff_ammo_prev_clipload = 0; + } + } + } + actor.buff_ammo_prev_infitems = 0; + SUPER(AmmoBuff).m_remove(this, actor, removal_type); +} +METHOD(AmmoBuff, m_tick, void(StatusEffect this, entity actor)) +{ + if(IS_PLAYER(actor)) + { + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + if(actor.(weaponentity).clip_size) + actor.(weaponentity).clip_load = actor.(weaponentity).(weapon_load[actor.(weaponentity).m_switchweapon.m_id]) = actor.(weaponentity).clip_size; + } + } + + SUPER(AmmoBuff).m_tick(this, actor); +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/buffs/buff/ammo.qh b/qcsrc/common/mutators/mutator/buffs/buff/ammo.qh new file mode 100644 index 000000000..ee654c8ad --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/ammo.qh @@ -0,0 +1,20 @@ +#pragma once + +#include + +#ifdef SVQC +.int buff_ammo_prev_infitems; +.int buff_ammo_prev_clipload; +#endif // SVQC + +CLASS(AmmoBuff, Buff) + ATTRIB(AmmoBuff, m_name, string, _("Ammo")); + ATTRIB(AmmoBuff, netname, string, "ammo"); + ATTRIB(AmmoBuff, m_icon, string, "buff_ammo"); + ATTRIB(AmmoBuff, m_skin, int, 3); + ATTRIB(AmmoBuff, m_color, vector, '0.76 1 0.1'); +ENDCLASS(AmmoBuff) + +REGISTER_BUFF(AMMO, NEW(AmmoBuff)); +BUFF_SPAWNFUNCS(ammo, BUFF_AMMO) +BUFF_SPAWNFUNC_Q3COMPAT(item_ammoregen, BUFF_AMMO) diff --git a/qcsrc/common/mutators/mutator/buffs/buff/bash.qc b/qcsrc/common/mutators/mutator/buffs/buff/bash.qc new file mode 100644 index 000000000..a14d6755e --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/bash.qc @@ -0,0 +1,21 @@ +#include "bash.qh" + +#ifdef SVQC +vector buff_Bash_TargetCalculateForce(vector frag_force, entity frag_target, entity frag_attacker) +{ + if(frag_attacker != frag_target) + frag_force = '0 0 0'; + return frag_force; +} +vector buff_Bash_AttackerCalculateForce(vector frag_force, entity frag_target, entity frag_attacker) +{ + if(frag_force) + { + if(frag_attacker == frag_target) + frag_force *= autocvar_g_buffs_bash_force_self; + else + frag_force *= autocvar_g_buffs_bash_force; + } + return frag_force; +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/buffs/buff/bash.qh b/qcsrc/common/mutators/mutator/buffs/buff/bash.qh new file mode 100644 index 000000000..ba306eec5 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/bash.qh @@ -0,0 +1,23 @@ +#pragma once + +#include + +#ifdef SVQC +float autocvar_g_buffs_bash_force; +float autocvar_g_buffs_bash_force_self; + +vector buff_Bash_TargetCalculateForce(vector frag_force, entity frag_target, entity frag_attacker); +vector buff_Bash_AttackerCalculateForce(vector frag_force, entity frag_target, entity frag_attacker); +#endif // SVQC + +CLASS(BashBuff, Buff) + ATTRIB(BashBuff, m_name, string, _("Bash")); + ATTRIB(BashBuff, netname, string, "bash"); + ATTRIB(BashBuff, m_icon, string, "buff_bash"); + ATTRIB(BashBuff, m_skin, int, 5); + ATTRIB(BashBuff, m_color, vector, '1 0.39 0'); +ENDCLASS(BashBuff) + +REGISTER_BUFF(BASH, NEW(BashBuff)); +BUFF_SPAWNFUNCS(bash, BUFF_BASH) +BUFF_SPAWNFUNC_Q3COMPAT(item_scout, BUFF_BASH) diff --git a/qcsrc/common/mutators/mutator/buffs/buff/disability.qc b/qcsrc/common/mutators/mutator/buffs/buff/disability.qc new file mode 100644 index 000000000..967a0d198 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/disability.qc @@ -0,0 +1,44 @@ +#include "disability.qh" + +#ifdef SVQC +void buff_Disability_ApplyStunned(entity frag_target, entity frag_attacker) +{ + if(frag_target != frag_attacker) + StatusEffects_apply(STATUSEFFECT_Stunned, frag_target, time + autocvar_g_buffs_disability_slowtime, 0); +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerPhysics_UpdateStats) +{ + entity player = M_ARGV(0, entity); + // these automatically reset, no need to worry + + if(StatusEffects_active(STATUSEFFECT_Stunned, player)) + STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_buffs_disability_speed; +} + +MUTATOR_HOOKFUNCTION(buffs, MonsterMove) +{ + entity mon = M_ARGV(0, entity); + + if(StatusEffects_active(STATUSEFFECT_Stunned, mon)) + { + M_ARGV(1, float) *= autocvar_g_buffs_disability_speed; // run speed + M_ARGV(2, float) *= autocvar_g_buffs_disability_speed; // walk speed + } +} + +MUTATOR_HOOKFUNCTION(buffs, WeaponRateFactor) +{ + entity player = M_ARGV(1, entity); + + if(StatusEffects_active(STATUSEFFECT_Stunned, player)) + M_ARGV(0, float) *= autocvar_g_buffs_disability_rate; +} +MUTATOR_HOOKFUNCTION(buffs, WeaponSpeedFactor) +{ + entity player = M_ARGV(1, entity); + + if(StatusEffects_active(STATUSEFFECT_Stunned, player)) + M_ARGV(0, float) *= autocvar_g_buffs_disability_weaponspeed; +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/buffs/buff/disability.qh b/qcsrc/common/mutators/mutator/buffs/buff/disability.qh new file mode 100644 index 000000000..26d3c446b --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/disability.qh @@ -0,0 +1,23 @@ +#pragma once + +#include + +#ifdef SVQC +float autocvar_g_buffs_disability_slowtime; +float autocvar_g_buffs_disability_speed; +float autocvar_g_buffs_disability_rate; +float autocvar_g_buffs_disability_weaponspeed; + +void buff_Disability_ApplyStunned(entity frag_target, entity frag_attacker); +#endif // SVQC + +CLASS(DisabilityBuff, Buff) + ATTRIB(DisabilityBuff, m_name, string, _("Disability")); + ATTRIB(DisabilityBuff, netname, string, "disability"); + ATTRIB(DisabilityBuff, m_icon, string, "buff_disability"); + ATTRIB(DisabilityBuff, m_skin, int, 7); + ATTRIB(DisabilityBuff, m_color, vector, '0.94 0.3 1'); +ENDCLASS(DisabilityBuff) + +REGISTER_BUFF(DISABILITY, NEW(DisabilityBuff)); +BUFF_SPAWNFUNCS(disability, BUFF_DISABILITY) diff --git a/qcsrc/common/mutators/mutator/buffs/buff/flight.qc b/qcsrc/common/mutators/mutator/buffs/buff/flight.qc new file mode 100644 index 000000000..2c1a3b23e --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/flight.qc @@ -0,0 +1,25 @@ +#include "flight.qh" + +#ifdef SVQC +METHOD(FlightBuff, m_apply, void(StatusEffect this, entity actor, float eff_time, float eff_flags)) +{ + bool wasactive = (actor.statuseffects && (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE)); + if(!wasactive) + { + actor.buff_flight_oldgravity = actor.gravity; + if(!actor.gravity) + actor.gravity = 1; + } + SUPER(FlightBuff).m_apply(this, actor, eff_time, eff_flags); +} +METHOD(FlightBuff, m_remove, void(StatusEffect this, entity actor, int removal_type)) +{ + bool wasactive = (actor.statuseffects && (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE)); + if(wasactive) + { + actor.gravity = ((actor.trigger_gravity_check) ? actor.trigger_gravity_check.enemy.gravity : actor.buff_flight_oldgravity); + } + actor.buff_flight_oldgravity = 0; + SUPER(FlightBuff).m_remove(this, actor, removal_type); +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/buffs/buff/flight.qh b/qcsrc/common/mutators/mutator/buffs/buff/flight.qh new file mode 100644 index 000000000..90903a822 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/flight.qh @@ -0,0 +1,20 @@ +#pragma once + +#include + +#ifdef SVQC +.float buff_flight_oldgravity; +.bool buff_flight_crouchheld; +#endif // SVQC + +CLASS(FlightBuff, Buff) + ATTRIB(FlightBuff, m_name, string, _("Flight")); + ATTRIB(FlightBuff, netname, string, "flight"); + ATTRIB(FlightBuff, m_icon, string, "buff_flight"); + ATTRIB(FlightBuff, m_skin, int, 11); + ATTRIB(FlightBuff, m_color, vector, '0.23 0.44 1'); +ENDCLASS(FlightBuff) + +REGISTER_BUFF(FLIGHT, NEW(FlightBuff)); +BUFF_SPAWNFUNCS(flight, BUFF_FLIGHT) +BUFF_SPAWNFUNC_Q3COMPAT(item_flight, BUFF_FLIGHT) diff --git a/qcsrc/common/mutators/mutator/buffs/buff/inferno.qc b/qcsrc/common/mutators/mutator/buffs/buff/inferno.qc new file mode 100644 index 000000000..0521de916 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/inferno.qc @@ -0,0 +1,17 @@ +#include "inferno.qh" + +#ifdef SVQC +float buff_Inferno_CalculateTime(float dmg) +{ + float offset_x = 0; + float offset_y = autocvar_g_buffs_inferno_burntime_min_time; + float intersect_x = autocvar_g_buffs_inferno_burntime_target_damage; + float intersect_y = autocvar_g_buffs_inferno_burntime_target_time; + float base = autocvar_g_buffs_inferno_burntime_factor; + return offset_y + (intersect_y - offset_y) * logn(((dmg - offset_x) * ((base - 1) / intersect_x)) + 1, base); +} +float buff_Inferno_CalculateDamage(float frag_damage) +{ + return frag_damage * autocvar_g_buffs_inferno_damagemultiplier; +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/buffs/buff/inferno.qh b/qcsrc/common/mutators/mutator/buffs/buff/inferno.qh new file mode 100644 index 000000000..57148e01e --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/inferno.qh @@ -0,0 +1,26 @@ +#pragma once + +#include + +#ifdef SVQC +float autocvar_g_buffs_inferno_burntime_factor; +float autocvar_g_buffs_inferno_burntime_min_time; +float autocvar_g_buffs_inferno_burntime_target_damage; +float autocvar_g_buffs_inferno_burntime_target_time; +float autocvar_g_buffs_inferno_damagemultiplier; + +float buff_Inferno_CalculateTime(float dmg); +float buff_Inferno_CalculateDamage(float frag_damage); +#endif // SVQC + +CLASS(InfernoBuff, Buff) + ATTRIB(InfernoBuff, m_name, string, _("Inferno")); + ATTRIB(InfernoBuff, netname, string, "inferno"); + ATTRIB(InfernoBuff, m_icon, string, "buff_inferno"); + ATTRIB(InfernoBuff, m_skin, int, 16); + ATTRIB(InfernoBuff, m_color, vector, '1 0.62 0'); +ENDCLASS(InfernoBuff) + +REGISTER_BUFF(INFERNO, NEW(InfernoBuff)); +BUFF_SPAWNFUNCS(inferno, BUFF_INFERNO) +BUFF_SPAWNFUNC_Q3COMPAT(item_doubler, BUFF_INFERNO) diff --git a/qcsrc/common/mutators/mutator/buffs/buff/jump.qc b/qcsrc/common/mutators/mutator/buffs/buff/jump.qc new file mode 100644 index 000000000..820a5f4a8 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/jump.qc @@ -0,0 +1,19 @@ +#include "jump.qh" + +#ifdef SVQC +float buff_Jump_ChangeDamage(float frag_damage, float frag_deathtype) +{ + if(frag_deathtype == DEATH_FALL.m_id) + frag_damage = 0; + return frag_damage; +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerPhysics) +{ + entity player = M_ARGV(0, entity); + // these automatically reset, no need to worry + + if(StatusEffects_active(BUFF_JUMP, player)) + STAT(MOVEVARS_JUMPVELOCITY, player) = autocvar_g_buffs_jump_height; +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/buffs/buff/jump.qh b/qcsrc/common/mutators/mutator/buffs/buff/jump.qh new file mode 100644 index 000000000..7163f542c --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/jump.qh @@ -0,0 +1,21 @@ +#pragma once + +#include + +#ifdef SVQC +float autocvar_g_buffs_jump_height; + +float buff_Jump_ChangeDamage(float frag_damage, float frag_deathtype); +#endif // SVQC + +CLASS(JumpBuff, Buff) + ATTRIB(JumpBuff, m_name, string, _("Jump")); + ATTRIB(JumpBuff, netname, string, "jump"); + ATTRIB(JumpBuff, m_icon, string, "buff_jump"); + ATTRIB(JumpBuff, m_skin, int, 10); + ATTRIB(JumpBuff, m_color, vector, '0.24 0.78 1'); +ENDCLASS(JumpBuff) + +REGISTER_BUFF(JUMP, NEW(JumpBuff)); +BUFF_SPAWNFUNCS(jump, BUFF_JUMP) +BUFF_SPAWNFUNC_Q3COMPAT(item_jumper, BUFF_JUMP) diff --git a/qcsrc/common/mutators/mutator/buffs/buff/luck.qc b/qcsrc/common/mutators/mutator/buffs/buff/luck.qc new file mode 100644 index 000000000..395da3439 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/luck.qc @@ -0,0 +1,11 @@ +#include "luck.qh" + +#ifdef SVQC +float buff_Luck_CalculateDamage(float frag_damage) +{ + if(autocvar_g_buffs_luck_damagemultiplier > 0) + if(random() <= autocvar_g_buffs_luck_chance) + frag_damage *= autocvar_g_buffs_luck_damagemultiplier; + return frag_damage; +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/buffs/buff/luck.qh b/qcsrc/common/mutators/mutator/buffs/buff/luck.qh new file mode 100644 index 000000000..1fd1226d1 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/luck.qh @@ -0,0 +1,21 @@ +#pragma once + +#include + +#ifdef SVQC +float autocvar_g_buffs_luck_chance = 0.15; +float autocvar_g_buffs_luck_damagemultiplier = 3; + +float buff_Luck_CalculateDamage(float frag_damage); +#endif // SVQC + +CLASS(LuckBuff, Buff) + ATTRIB(LuckBuff, m_name, string, _("Luck")); + ATTRIB(LuckBuff, netname, string, "luck"); + ATTRIB(LuckBuff, m_icon, string, "buff_luck"); + ATTRIB(LuckBuff, m_skin, int, 19); + ATTRIB(LuckBuff, m_color, vector, '1 0.23 0.44'); +ENDCLASS(LuckBuff) + +REGISTER_BUFF(LUCK, NEW(LuckBuff)); +BUFF_SPAWNFUNCS(luck, BUFF_LUCK) diff --git a/qcsrc/common/mutators/mutator/buffs/buff/magnet.qc b/qcsrc/common/mutators/mutator/buffs/buff/magnet.qc new file mode 100644 index 000000000..15aa097f1 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/magnet.qc @@ -0,0 +1,26 @@ +#include "magnet.qh" + +#ifdef SVQC +METHOD(MagnetBuff, m_tick, void(StatusEffect this, entity actor)) +{ + if(IS_PLAYER(actor)) + { + vector pickup_size; + IL_EACH(g_items, it.itemdef, + { + if(it.buffdef) + pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_buff; + else + pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_item; + + if(boxesoverlap(actor.absmin - pickup_size, actor.absmax + pickup_size, it.absmin, it.absmax)) + { + if(gettouch(it)) + gettouch(it)(it, actor); + } + }); + } + + SUPER(MagnetBuff).m_tick(this, actor); +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/buffs/buff/magnet.qh b/qcsrc/common/mutators/mutator/buffs/buff/magnet.qh new file mode 100644 index 000000000..1b04615c0 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/magnet.qh @@ -0,0 +1,19 @@ +#pragma once + +#include + +#ifdef SVQC +float autocvar_g_buffs_magnet_range_item; +float autocvar_g_buffs_magnet_range_buff = 200; +#endif // SVQC + +CLASS(MagnetBuff, Buff) + ATTRIB(MagnetBuff, m_name, string, _("Magnet")); + ATTRIB(MagnetBuff, netname, string, "magnet"); + ATTRIB(MagnetBuff, m_icon, string, "buff_magnet"); + ATTRIB(MagnetBuff, m_skin, int, 18); + ATTRIB(MagnetBuff, m_color, vector, '1 0.95 0.18'); +ENDCLASS(MagnetBuff) + +REGISTER_BUFF(MAGNET, NEW(MagnetBuff)); +BUFF_SPAWNFUNCS(magnet, BUFF_MAGNET) diff --git a/qcsrc/common/mutators/mutator/buffs/buff/medic.qc b/qcsrc/common/mutators/mutator/buffs/buff/medic.qc new file mode 100644 index 000000000..ef1c34537 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/medic.qc @@ -0,0 +1,22 @@ +#include "medic.qh" + +#ifdef SVQC +MUTATOR_HOOKFUNCTION(buffs, PlayerRegen) +{ + entity player = M_ARGV(0, entity); + + if(StatusEffects_active(BUFF_MEDIC, player)) + { + M_ARGV(3, float) = autocvar_g_buffs_medic_rot; // rot_mod + M_ARGV(4, float) = M_ARGV(1, float) = autocvar_g_buffs_medic_max; // limit_mod = max_mod + M_ARGV(2, float) = autocvar_g_buffs_medic_regen; // regen_mod + } +} + +float buff_Medic_CalculateSurviveDamage(float frag_damage, float health) +{ + if(random() <= autocvar_g_buffs_medic_survive_chance) + frag_damage = max(5, health - autocvar_g_buffs_medic_survive_health); + return frag_damage; +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/buffs/buff/medic.qh b/qcsrc/common/mutators/mutator/buffs/buff/medic.qh new file mode 100644 index 000000000..c3021c118 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/medic.qh @@ -0,0 +1,26 @@ +#pragma once + +#include + +#ifdef SVQC +float autocvar_g_buffs_medic_survive_chance; +float autocvar_g_buffs_medic_survive_health; +float autocvar_g_buffs_medic_rot; +float autocvar_g_buffs_medic_max; +float autocvar_g_buffs_medic_regen; + +float buff_Medic_CalculateSurviveDamage(float frag_damage, float health); +#endif // SVQC + +CLASS(MedicBuff, Buff) + ATTRIB(MedicBuff, m_name, string, _("Medic")); + ATTRIB(MedicBuff, netname, string, "medic"); + ATTRIB(MedicBuff, m_icon, string, "buff_medic"); + ATTRIB(MedicBuff, m_skin, int, 1); + ATTRIB(MedicBuff, m_color, vector, '1 0.12 0'); +ENDCLASS(MedicBuff) + +REGISTER_BUFF(MEDIC, NEW(MedicBuff)); +BUFF_SPAWNFUNCS(medic, BUFF_MEDIC) +BUFF_SPAWNFUNC_Q3COMPAT(item_regen, BUFF_MEDIC) +BUFF_SPAWNFUNC_Q3COMPAT(item_revival, BUFF_MEDIC) diff --git a/qcsrc/common/mutators/mutator/buffs/buff/resistance.qc b/qcsrc/common/mutators/mutator/buffs/buff/resistance.qc new file mode 100644 index 000000000..0bb9f424d --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/resistance.qc @@ -0,0 +1,8 @@ +#include "resistance.qh" + +#ifdef SVQC +float buff_Resistance_CalculateDamage(float frag_damage) +{ + return bound(0, frag_damage * (1 - autocvar_g_buffs_resistance_blockpercent), frag_damage); +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/buffs/buff/resistance.qh b/qcsrc/common/mutators/mutator/buffs/buff/resistance.qh new file mode 100644 index 000000000..d351a9453 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/resistance.qh @@ -0,0 +1,21 @@ +#pragma once + +#include + +#ifdef SVQC +float autocvar_g_buffs_resistance_blockpercent; + +float buff_Resistance_CalculateDamage(float frag_damage); +#endif // SVQC + +CLASS(ResistanceBuff, Buff) + ATTRIB(ResistanceBuff, m_name, string, _("Resistance")); + ATTRIB(ResistanceBuff, netname, string, "resistance"); + ATTRIB(ResistanceBuff, m_icon, string, "buff_resistance"); + ATTRIB(ResistanceBuff, m_skin, int, 0); + ATTRIB(ResistanceBuff, m_color, vector, '0.36 1 0.07'); +ENDCLASS(ResistanceBuff) + +REGISTER_BUFF(RESISTANCE, NEW(ResistanceBuff)); +BUFF_SPAWNFUNCS(resistance, BUFF_RESISTANCE) +BUFF_SPAWNFUNC_Q3COMPAT(item_guard, BUFF_RESISTANCE) diff --git a/qcsrc/common/mutators/mutator/buffs/buff/swapper.qc b/qcsrc/common/mutators/mutator/buffs/buff/swapper.qc new file mode 100644 index 000000000..7c17cb110 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/swapper.qc @@ -0,0 +1,82 @@ +#include "swapper.qh" + +#ifdef SVQC +MUTATOR_HOOKFUNCTION(buffs, ForbidThrowCurrentWeapon) +{ + if(MUTATOR_RETURNVALUE || game_stopped) return; + entity player = M_ARGV(0, entity); + + if(StatusEffects_active(BUFF_SWAPPER, player)) + { + float best_distance = autocvar_g_buffs_swapper_range; + entity closest = NULL; + FOREACH_CLIENT(IS_PLAYER(it), { + if(!IS_DEAD(it) && !STAT(FROZEN, it) && !it.vehicle) + if(DIFF_TEAM(it, player)) + { + float test = vlen2(player.origin - it.origin); + if(test <= best_distance * best_distance) + { + best_distance = sqrt(test); + closest = it; + } + } + }); + + if(closest) + { + vector my_org, my_vel, my_ang, their_org, their_vel, their_ang; + + my_org = player.origin; + my_vel = player.velocity; + my_ang = player.angles; + their_org = closest.origin; + their_vel = closest.velocity; + their_ang = closest.angles; + + Drop_Special_Items(closest); + + MUTATOR_CALLHOOK(PortalTeleport, player); // initiate flag dropper + + setorigin(player, their_org); + setorigin(closest, my_org); + + closest.velocity = my_vel; + closest.angles = my_ang; + if (IS_BOT_CLIENT(closest)) + { + closest.v_angle = closest.angles; + bot_aim_reset(closest); + } + closest.fixangle = true; + closest.oldorigin = my_org; + closest.oldvelocity = my_vel; + player.velocity = their_vel; + player.angles = their_ang; + if (IS_BOT_CLIENT(player)) + { + player.v_angle = player.angles; + bot_aim_reset(player); + } + player.fixangle = true; + player.oldorigin = their_org; + player.oldvelocity = their_vel; + + // set pusher so player gets the kill if they fall into void + closest.pusher = player; + closest.pushltime = time + autocvar_g_maxpushtime; + closest.istypefrag = PHYS_INPUT_BUTTON_CHAT(closest); + + Send_Effect(EFFECT_ELECTRO_COMBO, their_org, '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_COMBO, my_org, '0 0 0', 1); + + sound(player, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM); + sound(closest, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM); + + // TODO: add a counter to handle how many times one can teleport, and a delay to prevent spam + buff_RemoveAll(player, STATUSEFFECT_REMOVE_NORMAL); + return true; + } + } +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/buffs/buff/swapper.qh b/qcsrc/common/mutators/mutator/buffs/buff/swapper.qh new file mode 100644 index 000000000..6b795ac84 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/swapper.qh @@ -0,0 +1,19 @@ +#pragma once + +#include + +#ifdef SVQC +float autocvar_g_buffs_swapper_range; +#endif // SVQC + +CLASS(SwapperBuff, Buff) + ATTRIB(SwapperBuff, m_name, string, _("Swapper")); + ATTRIB(SwapperBuff, netname, string, "swapper"); + ATTRIB(SwapperBuff, m_icon, string, "buff_swapper"); + ATTRIB(SwapperBuff, m_skin, int, 17); + ATTRIB(SwapperBuff, m_color, vector, '0.63 0.36 1'); +ENDCLASS(SwapperBuff) + +REGISTER_BUFF(SWAPPER, NEW(SwapperBuff)); +BUFF_SPAWNFUNCS(swapper, BUFF_SWAPPER) +BUFF_SPAWNFUNC_Q3COMPAT(holdable_teleporter, BUFF_SWAPPER) diff --git a/qcsrc/common/mutators/mutator/buffs/buff/vampire.qc b/qcsrc/common/mutators/mutator/buffs/buff/vampire.qc new file mode 100644 index 000000000..7145bbe08 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/vampire.qc @@ -0,0 +1,19 @@ +#include "vampire.qh" + +#ifdef SVQC +MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_SplitHealthArmor) +{ + // NOTE: vampire PlayerDamage_SplitHealthArmor code is similar + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + if(!StatusEffects_active(BUFF_VAMPIRE, frag_attacker)) + return; + float health_take = bound(0, M_ARGV(4, float), GetResource(frag_target, RES_HEALTH)); + + if (!StatusEffects_active(STATUSEFFECT_SpawnShield, frag_target) && frag_target != frag_attacker + && IS_PLAYER(frag_attacker) && !IS_DEAD(frag_target) && !STAT(FROZEN, frag_target)) + { + GiveResource(frag_attacker, RES_HEALTH, autocvar_g_buffs_vampire_damage_steal * health_take); + } +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/buffs/buff/vampire.qh b/qcsrc/common/mutators/mutator/buffs/buff/vampire.qh new file mode 100644 index 000000000..131173337 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/vampire.qh @@ -0,0 +1,19 @@ +#pragma once + +#include + +#ifdef SVQC +float autocvar_g_buffs_vampire_damage_steal; +#endif // SVQC + +CLASS(VampireBuff, Buff) + ATTRIB(VampireBuff, m_name, string, _("Vampire")); + ATTRIB(VampireBuff, netname, string, "vampire"); + ATTRIB(VampireBuff, m_icon, string, "buff_vampire"); + ATTRIB(VampireBuff, m_skin, int, 2); + ATTRIB(VampireBuff, m_color, vector, '1 0 0.24'); +ENDCLASS(VampireBuff) + +REGISTER_BUFF(VAMPIRE, NEW(VampireBuff)); +BUFF_SPAWNFUNCS(vampire, BUFF_VAMPIRE) +BUFF_SPAWNFUNC_Q3COMPAT(holdable_invulnerability, BUFF_VAMPIRE) diff --git a/qcsrc/common/mutators/mutator/buffs/buff/vengeance.qc b/qcsrc/common/mutators/mutator/buffs/buff/vengeance.qc new file mode 100644 index 000000000..049dba3cf --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/vengeance.qc @@ -0,0 +1,16 @@ +#include "vengeance.qh" + +#ifdef SVQC +void buff_Vengeance_DelayedDamage(entity this) +{ + if(this.enemy) + Damage(this.enemy, this.owner, this.owner, this.dmg, DEATH_BUFF.m_id, DMG_NOWEP, this.enemy.origin, '0 0 0'); + + delete(this); + return; +} +float buff_Vengeance_CalculateDamage(float frag_damage) +{ + return frag_damage * autocvar_g_buffs_vengeance_damage_multiplier; +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/buffs/buff/vengeance.qh b/qcsrc/common/mutators/mutator/buffs/buff/vengeance.qh new file mode 100644 index 000000000..539182106 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buff/vengeance.qh @@ -0,0 +1,22 @@ +#pragma once + +#include + +#ifdef SVQC +float autocvar_g_buffs_vengeance_damage_multiplier; + +void buff_Vengeance_DelayedDamage(entity this); +float buff_Vengeance_CalculateDamage(float frag_damage); +#endif // SVQC + +CLASS(VengeanceBuff, Buff) + ATTRIB(VengeanceBuff, m_name, string, _("Vengeance")); + ATTRIB(VengeanceBuff, netname, string, "vengeance"); + ATTRIB(VengeanceBuff, m_icon, string, "buff_vengeance"); + ATTRIB(VengeanceBuff, m_skin, int, 15); + ATTRIB(VengeanceBuff, m_color, vector, '1 0.23 0.61'); +ENDCLASS(VengeanceBuff) + +REGISTER_BUFF(VENGEANCE, NEW(VengeanceBuff)); +BUFF_SPAWNFUNCS(vengeance, BUFF_VENGEANCE) +BUFF_SPAWNFUNC_Q3COMPAT(holdable_kamikaze, BUFF_VENGEANCE) diff --git a/qcsrc/common/mutators/mutator/buffs/buffs.qc b/qcsrc/common/mutators/mutator/buffs/buffs.qc index baf813e8e..2db2b48d1 100644 --- a/qcsrc/common/mutators/mutator/buffs/buffs.qc +++ b/qcsrc/common/mutators/mutator/buffs/buffs.qc @@ -2,8 +2,25 @@ string BUFF_NAME(int i) { - Buff b = REGISTRY_GET(StatusEffects, i); - return strcat(rgb_to_hexcolor(b.m_color), b.m_name); + Buff b = REGISTRY_GET(StatusEffects, i); + return strcat(rgb_to_hexcolor(b.m_color), b.m_name); +} + +string Buff_CompatName(string buffname) +{ + switch (buffname) + { + case "ammoregen": return "ammo"; // Q3TA ammoregen + case "doubler": return "inferno"; // Q3TA doubler + case "scout": return "bash"; // Q3TA scout + case "guard": return "resistance"; // Q3TA guard + case "revival": case "regen": return "medic"; // WOP revival, Q3A regen + case "jumper": return "jump"; // WOP jumper + case "invulnerability": return "vampire"; // Q3TA invulnerability + case "kamikaze": return "vengeance"; // Q3TA kamikaze + case "teleporter": return "swapper"; // Q3A personal teleporter + default: return buffname; + } } #ifdef GAMEQC diff --git a/qcsrc/common/mutators/mutator/buffs/buffs.qh b/qcsrc/common/mutators/mutator/buffs/buffs.qh index 6c214b3df..c4580212e 100644 --- a/qcsrc/common/mutators/mutator/buffs/buffs.qh +++ b/qcsrc/common/mutators/mutator/buffs/buffs.qh @@ -65,8 +65,6 @@ string Buff_CompatName(string buffname); BUFF_SPAWNFUNCS(random, NULL) -#include "all.inc" - #ifdef GAMEQC REPLICATE_INIT(bool, cvar_cl_buffs_autoreplace); #endif diff --git a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc index 39010127a..cf4d890c1 100644 --- a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc +++ b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc @@ -1,10 +1,12 @@ #include "sv_buffs.qh" #include -#include +#include #include #include +#include "buff/_mod.qh" + bool buffs_BuffModel_Customize(entity this, entity client) { entity player = WaypointSprite_getviewentity(client); @@ -505,25 +507,11 @@ void buff_SpawnReplacement(entity ent, entity old) buff_Init(ent); } -void buff_Vengeance_DelayedDamage(entity this) -{ - if(this.enemy) - Damage(this.enemy, this.owner, this.owner, this.dmg, DEATH_BUFF.m_id, DMG_NOWEP, this.enemy.origin, '0 0 0'); - - delete(this); - return; -} - -float buff_Inferno_CalculateTime(float damg, float offset_x, float offset_y, float intersect_x, float intersect_y, float base) -{ - return offset_y + (intersect_y - offset_y) * logn(((damg - offset_x) * ((base - 1) / intersect_x)) + 1, base); -} - METHOD(Buff, m_apply, void(StatusEffect this, entity actor, float eff_time, float eff_flags)) { - if(IS_PLAYER(actor)) - actor.effects |= EF_NOSHADOW; // does not play well with buff icon - SUPER(Buff).m_apply(this, actor, eff_time, eff_flags); + if(IS_PLAYER(actor)) + actor.effects |= EF_NOSHADOW; // does not play well with buff icon + SUPER(Buff).m_apply(this, actor, eff_time, eff_flags); } METHOD(Buff, m_remove, void(StatusEffect this, entity actor, int removal_type)) { @@ -545,107 +533,6 @@ METHOD(Buff, m_remove, void(StatusEffect this, entity actor, int removal_type)) SUPER(Buff).m_remove(this, actor, removal_type); } -METHOD(AmmoBuff, m_apply, void(StatusEffect this, entity actor, float eff_time, float eff_flags)) -{ - bool wasactive = (actor.statuseffects && (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE)); - if(!wasactive) - { - actor.buff_ammo_prev_infitems = (actor.items & IT_UNLIMITED_AMMO); - actor.items |= IT_UNLIMITED_AMMO; - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(!actor.(weaponentity)) - continue; - if(actor.(weaponentity).clip_load) - actor.(weaponentity).buff_ammo_prev_clipload = actor.(weaponentity).clip_load; - if(actor.(weaponentity).clip_size) - actor.(weaponentity).clip_load = actor.(weaponentity).(weapon_load[actor.(weaponentity).m_switchweapon.m_id]) = actor.(weaponentity).clip_size; - } - } - SUPER(AmmoBuff).m_apply(this, actor, eff_time, eff_flags); -} -METHOD(AmmoBuff, m_remove, void(StatusEffect this, entity actor, int removal_type)) -{ - bool wasactive = (actor.statuseffects && (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE)); - if(wasactive) - { - actor.items = BITSET(actor.items, IT_UNLIMITED_AMMO, actor.buff_ammo_prev_infitems); - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(!actor.(weaponentity)) - continue; - if(actor.(weaponentity).buff_ammo_prev_clipload) - { - actor.(weaponentity).clip_load = actor.(weaponentity).buff_ammo_prev_clipload; - actor.(weaponentity).buff_ammo_prev_clipload = 0; - } - } - } - actor.buff_ammo_prev_infitems = 0; - SUPER(AmmoBuff).m_remove(this, actor, removal_type); -} -METHOD(AmmoBuff, m_tick, void(StatusEffect this, entity actor)) -{ - if(IS_PLAYER(actor)) - { - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(actor.(weaponentity).clip_size) - actor.(weaponentity).clip_load = actor.(weaponentity).(weapon_load[actor.(weaponentity).m_switchweapon.m_id]) = actor.(weaponentity).clip_size; - } - } - - SUPER(AmmoBuff).m_tick(this, actor); -} - - -METHOD(FlightBuff, m_apply, void(StatusEffect this, entity actor, float eff_time, float eff_flags)) -{ - bool wasactive = (actor.statuseffects && (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE)); - if(!wasactive) - { - actor.buff_flight_oldgravity = actor.gravity; - if(!actor.gravity) - actor.gravity = 1; - } - SUPER(FlightBuff).m_apply(this, actor, eff_time, eff_flags); -} -METHOD(FlightBuff, m_remove, void(StatusEffect this, entity actor, int removal_type)) -{ - bool wasactive = (actor.statuseffects && (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE)); - if(wasactive) - { - actor.gravity = ((actor.trigger_gravity_check) ? actor.trigger_gravity_check.enemy.gravity : actor.buff_flight_oldgravity); - } - actor.buff_flight_oldgravity = 0; - SUPER(FlightBuff).m_remove(this, actor, removal_type); -} - -METHOD(MagnetBuff, m_tick, void(StatusEffect this, entity actor)) -{ - if(IS_PLAYER(actor)) - { - vector pickup_size; - IL_EACH(g_items, it.itemdef, - { - if(it.buffdef) - pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_buff; - else - pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_item; - - if(boxesoverlap(actor.absmin - pickup_size, actor.absmax + pickup_size, it.absmin, it.absmax)) - { - if(gettouch(it)) - gettouch(it)(it, actor); - } - }); - } - - SUPER(MagnetBuff).m_tick(this, actor); -} // mutator hooks MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate) @@ -659,21 +546,16 @@ MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate) if(frag_deathtype == DEATH_BUFF.m_id) { return; } if(StatusEffects_active(BUFF_RESISTANCE, frag_target)) - { - float reduced = frag_damage * autocvar_g_buffs_resistance_blockpercent; - frag_damage = bound(0, frag_damage - reduced, frag_damage); - } + frag_damage = buff_Resistance_CalculateDamage(frag_damage); if(StatusEffects_active(BUFF_MEDIC, frag_target)) if((GetResource(frag_target, RES_HEALTH) - frag_damage) <= 0) if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) if(frag_attacker) - if(random() <= autocvar_g_buffs_medic_survive_chance) - frag_damage = max(5, GetResource(frag_target, RES_HEALTH) - autocvar_g_buffs_medic_survive_health); + frag_damage = buff_Medic_CalculateSurviveDamage(frag_damage, GetResource(frag_target, RES_HEALTH)); if(StatusEffects_active(BUFF_JUMP, frag_target)) - if(frag_deathtype == DEATH_FALL.m_id) - frag_damage = 0; + frag_damage = buff_Jump_ChangeDamage(frag_damage, frag_deathtype); if(StatusEffects_active(BUFF_VENGEANCE, frag_target)) if(frag_attacker) @@ -682,7 +564,7 @@ MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate) { entity dmgent = new_pure(dmgent); - dmgent.dmg = frag_damage * autocvar_g_buffs_vengeance_damage_multiplier; + dmgent.dmg = buff_Vengeance_CalculateDamage(frag_damage); dmgent.enemy = frag_attacker; dmgent.owner = frag_target; setthink(dmgent, buff_Vengeance_DelayedDamage); @@ -690,21 +572,13 @@ MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate) } if(StatusEffects_active(BUFF_BASH, frag_target)) - if(frag_attacker != frag_target) - frag_force = '0 0 0'; + frag_force = buff_Bash_TargetCalculateForce(frag_force, frag_target, frag_attacker); if(StatusEffects_active(BUFF_BASH, frag_attacker)) - if(frag_force) - { - if(frag_attacker == frag_target) - frag_force *= autocvar_g_buffs_bash_force_self; - else - frag_force *= autocvar_g_buffs_bash_force; - } + frag_force = buff_Bash_AttackerCalculateForce(frag_force, frag_target, frag_attacker); if(StatusEffects_active(BUFF_DISABILITY, frag_attacker)) - if(frag_target != frag_attacker) - StatusEffects_apply(STATUSEFFECT_Stunned, frag_target, time + autocvar_g_buffs_disability_slowtime, 0); + buff_Disability_ApplyStunned(frag_target, frag_attacker); if(StatusEffects_active(BUFF_INFERNO, frag_target)) { @@ -716,72 +590,18 @@ MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate) if(StatusEffects_active(BUFF_LUCK, frag_attacker)) if(frag_attacker != frag_target) - if(autocvar_g_buffs_luck_damagemultiplier > 0) - if(random() <= autocvar_g_buffs_luck_chance) - frag_damage *= autocvar_g_buffs_luck_damagemultiplier; + frag_damage = buff_Luck_CalculateDamage(frag_damage); if(StatusEffects_active(BUFF_INFERNO, frag_attacker)) if(frag_target != frag_attacker) { - float btime = buff_Inferno_CalculateTime( - frag_damage, - 0, - autocvar_g_buffs_inferno_burntime_min_time, - autocvar_g_buffs_inferno_burntime_target_damage, - autocvar_g_buffs_inferno_burntime_target_time, - autocvar_g_buffs_inferno_burntime_factor - ); - Fire_AddDamage(frag_target, frag_attacker, (frag_damage * autocvar_g_buffs_inferno_damagemultiplier), btime, DEATH_BUFF.m_id); + float btime = buff_Inferno_CalculateTime(frag_damage); + Fire_AddDamage(frag_target, frag_attacker, buff_Inferno_CalculateDamage(frag_damage), btime, DEATH_BUFF.m_id); } M_ARGV(4, float) = frag_damage; M_ARGV(6, vector) = frag_force; } -MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_SplitHealthArmor) -{ - // NOTE: vampire PlayerDamage_SplitHealthArmor code is similar - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - if(!StatusEffects_active(BUFF_VAMPIRE, frag_attacker)) - return; - float health_take = bound(0, M_ARGV(4, float), GetResource(frag_target, RES_HEALTH)); - - if (!StatusEffects_active(STATUSEFFECT_SpawnShield, frag_target) && frag_target != frag_attacker - && IS_PLAYER(frag_attacker) && !IS_DEAD(frag_target) && !STAT(FROZEN, frag_target)) - { - GiveResource(frag_attacker, RES_HEALTH, autocvar_g_buffs_vampire_damage_steal * health_take); - } -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerPhysics_UpdateStats) -{ - entity player = M_ARGV(0, entity); - // these automatically reset, no need to worry - - if(StatusEffects_active(STATUSEFFECT_Stunned, player)) - STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_buffs_disability_speed; -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerPhysics) -{ - entity player = M_ARGV(0, entity); - // these automatically reset, no need to worry - - if(StatusEffects_active(BUFF_JUMP, player)) - STAT(MOVEVARS_JUMPVELOCITY, player) = autocvar_g_buffs_jump_height; -} - -MUTATOR_HOOKFUNCTION(buffs, MonsterMove) -{ - entity mon = M_ARGV(0, entity); - - if(StatusEffects_active(STATUSEFFECT_Stunned, mon)) - { - M_ARGV(1, float) *= autocvar_g_buffs_disability_speed; // run speed - M_ARGV(2, float) *= autocvar_g_buffs_disability_speed; // walk speed - } -} - MUTATOR_HOOKFUNCTION(buffs, PlayerUseKey, CBC_ORDER_FIRST) { if(MUTATOR_RETURNVALUE || game_stopped || !autocvar_g_buffs_drop) return; @@ -803,85 +623,6 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerUseKey, CBC_ORDER_FIRST) } } -MUTATOR_HOOKFUNCTION(buffs, ForbidThrowCurrentWeapon) -{ - if(MUTATOR_RETURNVALUE || game_stopped) return; - entity player = M_ARGV(0, entity); - - if(StatusEffects_active(BUFF_SWAPPER, player)) - { - float best_distance = autocvar_g_buffs_swapper_range; - entity closest = NULL; - FOREACH_CLIENT(IS_PLAYER(it), { - if(!IS_DEAD(it) && !STAT(FROZEN, it) && !it.vehicle) - if(DIFF_TEAM(it, player)) - { - float test = vlen2(player.origin - it.origin); - if(test <= best_distance * best_distance) - { - best_distance = sqrt(test); - closest = it; - } - } - }); - - if(closest) - { - vector my_org, my_vel, my_ang, their_org, their_vel, their_ang; - - my_org = player.origin; - my_vel = player.velocity; - my_ang = player.angles; - their_org = closest.origin; - their_vel = closest.velocity; - their_ang = closest.angles; - - Drop_Special_Items(closest); - - MUTATOR_CALLHOOK(PortalTeleport, player); // initiate flag dropper - - setorigin(player, their_org); - setorigin(closest, my_org); - - closest.velocity = my_vel; - closest.angles = my_ang; - if (IS_BOT_CLIENT(closest)) - { - closest.v_angle = closest.angles; - bot_aim_reset(closest); - } - closest.fixangle = true; - closest.oldorigin = my_org; - closest.oldvelocity = my_vel; - player.velocity = their_vel; - player.angles = their_ang; - if (IS_BOT_CLIENT(player)) - { - player.v_angle = player.angles; - bot_aim_reset(player); - } - player.fixangle = true; - player.oldorigin = their_org; - player.oldvelocity = their_vel; - - // set pusher so player gets the kill if they fall into void - closest.pusher = player; - closest.pushltime = time + autocvar_g_maxpushtime; - closest.istypefrag = PHYS_INPUT_BUTTON_CHAT(closest); - - Send_Effect(EFFECT_ELECTRO_COMBO, their_org, '0 0 0', 1); - Send_Effect(EFFECT_ELECTRO_COMBO, my_org, '0 0 0', 1); - - sound(player, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM); - sound(closest, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM); - - // TODO: add a counter to handle how many times one can teleport, and a delay to prevent spam - buff_RemoveAll(player, STATUSEFFECT_REMOVE_NORMAL); - return true; - } - } -} - bool buffs_RemovePlayer(entity player) { buffs_BuffModel_Remove(player); @@ -908,22 +649,6 @@ MUTATOR_HOOKFUNCTION(buffs, FilterItem) return false; } -MUTATOR_HOOKFUNCTION(buffs, WeaponRateFactor) -{ - entity player = M_ARGV(1, entity); - - if(StatusEffects_active(STATUSEFFECT_Stunned, player)) - M_ARGV(0, float) *= autocvar_g_buffs_disability_rate; -} - -MUTATOR_HOOKFUNCTION(buffs, WeaponSpeedFactor) -{ - entity player = M_ARGV(1, entity); - - if(StatusEffects_active(STATUSEFFECT_Stunned, player)) - M_ARGV(0, float) *= autocvar_g_buffs_disability_weaponspeed; -} - MUTATOR_HOOKFUNCTION(buffs, Freeze) { entity targ = M_ARGV(0, entity); @@ -952,18 +677,6 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) buffs_BuffModel_Update(player); } -MUTATOR_HOOKFUNCTION(buffs, PlayerRegen) -{ - entity player = M_ARGV(0, entity); - - if(StatusEffects_active(BUFF_MEDIC, player)) - { - M_ARGV(3, float) = autocvar_g_buffs_medic_rot; // rot_mod - M_ARGV(4, float) = M_ARGV(1, float) = autocvar_g_buffs_medic_max; // limit_mod = max_mod - M_ARGV(2, float) = autocvar_g_buffs_medic_regen; // regen_mod - } -} - MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsString) { if(autocvar_g_buffs > 0) // only report as a mutator if they're enabled diff --git a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qh b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qh index 783cdbea0..6c6d6bb2e 100644 --- a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qh +++ b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qh @@ -16,7 +16,7 @@ REGISTER_MUTATOR(buffs, autocvar_g_buffs) } } -bool autocvar_g_buffs_effects; +bool autocvar_g_buffs_effects; float autocvar_g_buffs_waypoint_distance; bool autocvar_g_buffs_pickup_anyway = false; float autocvar_g_buffs_pickup_delay = 0.7; @@ -31,38 +31,7 @@ bool autocvar_g_buffs_replace_available = true; bool autocvar_g_buffs_drop = false; float autocvar_g_buffs_cooldown_activate; float autocvar_g_buffs_cooldown_respawn; -float autocvar_g_buffs_resistance_blockpercent; -float autocvar_g_buffs_medic_survive_chance; -float autocvar_g_buffs_medic_survive_health; -float autocvar_g_buffs_medic_rot; -float autocvar_g_buffs_medic_max; -float autocvar_g_buffs_medic_regen; -float autocvar_g_buffs_vengeance_damage_multiplier; -float autocvar_g_buffs_bash_force; -float autocvar_g_buffs_bash_force_self; -float autocvar_g_buffs_disability_slowtime; -float autocvar_g_buffs_disability_speed; -float autocvar_g_buffs_disability_rate; -float autocvar_g_buffs_disability_weaponspeed; -float autocvar_g_buffs_vampire_damage_steal; -float autocvar_g_buffs_jump_height; -float autocvar_g_buffs_inferno_burntime_factor; -float autocvar_g_buffs_inferno_burntime_min_time; -float autocvar_g_buffs_inferno_burntime_target_damage; -float autocvar_g_buffs_inferno_burntime_target_time; -float autocvar_g_buffs_inferno_damagemultiplier; -float autocvar_g_buffs_swapper_range; -float autocvar_g_buffs_magnet_range_item; -float autocvar_g_buffs_magnet_range_buff = 200; -float autocvar_g_buffs_luck_chance = 0.15; -float autocvar_g_buffs_luck_damagemultiplier = 3; -// ammo -.int buff_ammo_prev_infitems; -.int buff_ammo_prev_clipload; -// flight -.float buff_flight_oldgravity; -.bool buff_flight_crouchheld; // common buff variables .float buff_effect_delay; diff --git a/qcsrc/common/mutators/mutator/nades/_mod.inc b/qcsrc/common/mutators/mutator/nades/_mod.inc index 0ba83c439..1862f3ab7 100644 --- a/qcsrc/common/mutators/mutator/nades/_mod.inc +++ b/qcsrc/common/mutators/mutator/nades/_mod.inc @@ -1,3 +1,11 @@ // genmod.sh autogenerated file; do not modify #include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif #include + +#include diff --git a/qcsrc/common/mutators/mutator/nades/_mod.qh b/qcsrc/common/mutators/mutator/nades/_mod.qh index 5c7aa9a1c..12980408a 100644 --- a/qcsrc/common/mutators/mutator/nades/_mod.qh +++ b/qcsrc/common/mutators/mutator/nades/_mod.qh @@ -1,3 +1,11 @@ // genmod.sh autogenerated file; do not modify #include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif #include + +#include diff --git a/qcsrc/common/mutators/mutator/nades/all.inc b/qcsrc/common/mutators/mutator/nades/all.inc index 821491930..a439aded7 100644 --- a/qcsrc/common/mutators/mutator/nades/all.inc +++ b/qcsrc/common/mutators/mutator/nades/all.inc @@ -1,15 +1,5 @@ -#ifdef GAMEQC -#define NADE_PROJECTILE(i, projectile, trail) MACRO_BEGIN \ - this.m_projectile[i] = projectile; \ - this.m_trail[i] = trail; \ -MACRO_END -#endif +#include "nade/_mod.qh" -CLASS(NormalNade, Nade) - ATTRIB(NormalNade, m_color, vector, '1 1 1'); - ATTRIB(NormalNade, m_name, string, _("Grenade")); - ATTRIB(NormalNade, m_icon, string, "nade_normal"); -ENDCLASS(NormalNade) REGISTER_NADE(NORMAL, NEW(NormalNade)) { #ifdef GAMEQC NADE_PROJECTILE(0, PROJECTILE_NADE, EFFECT_Null); @@ -17,11 +7,6 @@ REGISTER_NADE(NORMAL, NEW(NormalNade)) { #endif } -CLASS(NapalmNade, Nade) - ATTRIB(NapalmNade, m_color, vector, '2 0.5 0'); - ATTRIB(NapalmNade, m_name, string, _("Napalm grenade")); - ATTRIB(NapalmNade, m_icon, string, "nade_napalm"); -ENDCLASS(NapalmNade) REGISTER_NADE(NAPALM, NEW(NapalmNade)) { #ifdef GAMEQC NADE_PROJECTILE(0, PROJECTILE_NADE_NAPALM, EFFECT_TR_ROCKET); @@ -29,11 +14,6 @@ REGISTER_NADE(NAPALM, NEW(NapalmNade)) { #endif } -CLASS(IceNade, Nade) - ATTRIB(IceNade, m_color, vector, '0 0.5 2'); - ATTRIB(IceNade, m_name, string, _("Ice grenade")); - ATTRIB(IceNade, m_icon, string, "nade_ice"); -ENDCLASS(IceNade) REGISTER_NADE(ICE, NEW(IceNade)) { #ifdef GAMEQC NADE_PROJECTILE(0, PROJECTILE_NADE_ICE, EFFECT_TR_NEXUIZPLASMA); @@ -41,11 +21,6 @@ REGISTER_NADE(ICE, NEW(IceNade)) { #endif } -CLASS(TranslocateNade, Nade) - ATTRIB(TranslocateNade, m_color, vector, '1 0 1'); - ATTRIB(TranslocateNade, m_name, string, _("Translocate grenade")); - ATTRIB(TranslocateNade, m_icon, string, "nade_translocate"); -ENDCLASS(TranslocateNade) REGISTER_NADE(TRANSLOCATE, NEW(TranslocateNade)) { #ifdef GAMEQC NADE_PROJECTILE(0, PROJECTILE_NADE_TRANSLOCATE, EFFECT_TR_CRYLINKPLASMA); @@ -53,11 +28,6 @@ REGISTER_NADE(TRANSLOCATE, NEW(TranslocateNade)) { #endif } -CLASS(SpawnNade, Nade) - ATTRIB(SpawnNade, m_color, vector, '1 0.9 0'); - ATTRIB(SpawnNade, m_name, string, _("Spawn grenade")); - ATTRIB(SpawnNade, m_icon, string, "nade_spawn"); -ENDCLASS(SpawnNade) REGISTER_NADE(SPAWN, NEW(SpawnNade)) { #ifdef GAMEQC NADE_PROJECTILE(0, PROJECTILE_NADE_SPAWN, EFFECT_NADE_TRAIL_YELLOW); @@ -65,11 +35,6 @@ REGISTER_NADE(SPAWN, NEW(SpawnNade)) { #endif } -CLASS(HealNade, Nade) - ATTRIB(HealNade, m_color, vector, '1 0 0'); - ATTRIB(HealNade, m_name, string, _("Heal grenade")); - ATTRIB(HealNade, m_icon, string, "nade_heal"); -ENDCLASS(HealNade) REGISTER_NADE(HEAL, NEW(HealNade)) { #ifdef GAMEQC NADE_PROJECTILE(0, PROJECTILE_NADE_HEAL, EFFECT_NADE_TRAIL_RED); @@ -77,11 +42,6 @@ REGISTER_NADE(HEAL, NEW(HealNade)) { #endif } -CLASS(MonsterNade, Nade) - ATTRIB(MonsterNade, m_color, vector, '0.25 0.75 0'); - ATTRIB(MonsterNade, m_name, string, _("Monster grenade")); - ATTRIB(MonsterNade, m_icon, string, "nade_monster"); -ENDCLASS(MonsterNade) REGISTER_NADE(MONSTER, NEW(MonsterNade)) { #ifdef GAMEQC NADE_PROJECTILE(0, PROJECTILE_NADE_MONSTER, EFFECT_NADE_TRAIL_RED); @@ -89,11 +49,6 @@ REGISTER_NADE(MONSTER, NEW(MonsterNade)) { #endif } -CLASS(EntrapNade, Nade) - ATTRIB(EntrapNade, m_color, vector, '0.15 0.85 0'); - ATTRIB(EntrapNade, m_name, string, _("Entrap grenade")); - ATTRIB(EntrapNade, m_icon, string, "nade_entrap"); -ENDCLASS(EntrapNade) REGISTER_NADE(ENTRAP, NEW(EntrapNade)) { #ifdef GAMEQC NADE_PROJECTILE(0, PROJECTILE_NADE_ENTRAP, EFFECT_NADE_TRAIL_YELLOW); @@ -101,12 +56,6 @@ REGISTER_NADE(ENTRAP, NEW(EntrapNade)) { #endif } -CLASS(VeilNade, Nade) - ATTRIB(VeilNade, m_color, vector, '0.65 0.85 0.65'); - ATTRIB(VeilNade, m_name, string, _("Veil grenade")); - ATTRIB(VeilNade, m_icon, string, "nade_veil"); - ATTRIB(VeilNade, m_alpha, float, 0.45); -ENDCLASS(VeilNade) REGISTER_NADE(VEIL, NEW(VeilNade)) { #ifdef GAMEQC NADE_PROJECTILE(0, PROJECTILE_NADE_VEIL, EFFECT_NADE_TRAIL_NEUTRAL); @@ -114,11 +63,6 @@ REGISTER_NADE(VEIL, NEW(VeilNade)) { #endif } -CLASS(AmmoNade, Nade) - ATTRIB(AmmoNade, m_color, vector, '0.66 0.33 0'); - ATTRIB(AmmoNade, m_name, string, _("Ammo grenade")); - ATTRIB(AmmoNade, m_icon, string, "nade_ammo"); -ENDCLASS(AmmoNade) REGISTER_NADE(AMMO, NEW(AmmoNade)) { #ifdef GAMEQC NADE_PROJECTILE(0, PROJECTILE_NADE_AMMO, EFFECT_NADE_TRAIL_BROWN); @@ -126,11 +70,6 @@ REGISTER_NADE(AMMO, NEW(AmmoNade)) { #endif } -CLASS(DarknessNade, Nade) - ATTRIB(DarknessNade, m_color, vector, '0.23 0 0.23'); - ATTRIB(DarknessNade, m_name, string, _("Darkness grenade")); - ATTRIB(DarknessNade, m_icon, string, "nade_darkness"); -ENDCLASS(DarknessNade) REGISTER_NADE(DARKNESS, NEW(DarknessNade)) { #ifdef GAMEQC NADE_PROJECTILE(0, PROJECTILE_NADE_DARKNESS, EFFECT_NADE_TRAIL_PURPLE); diff --git a/qcsrc/common/mutators/mutator/nades/cl_nades.qc b/qcsrc/common/mutators/mutator/nades/cl_nades.qc new file mode 100644 index 000000000..7cfabe9a8 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/cl_nades.qc @@ -0,0 +1,122 @@ +#include "cl_nades.qh" + +#include +#include + +REGISTER_MUTATOR(cl_nades, true); + +MUTATOR_HOOKFUNCTION(cl_nades, Ent_Projectile) +{ + entity proj = M_ARGV(0, entity); + + if (proj.cnt == PROJECTILE_NAPALM_FOUNTAIN) + { + proj.modelindex = 0; + proj.traileffect = EFFECT_FIREBALL.m_id; + return true; + } + if (Nade_FromProjectile(proj.cnt) != NADE_TYPE_Null) + { + setmodel(proj, MDL_PROJECTILE_NADE); + entity trail = Nade_TrailEffect(proj.cnt, proj.team); + if (trail.eent_eff_name) proj.traileffect = trail.m_id; + return true; + } +} +MUTATOR_HOOKFUNCTION(cl_nades, EditProjectile) +{ + if (!mut_is_active(MUT_NADES)) return; + + entity proj = M_ARGV(0, entity); + + if (proj.cnt == PROJECTILE_NAPALM_FOUNTAIN) + { + loopsound(proj, CH_SHOTS_SINGLE, SND_FIREBALL_FLY2, VOL_BASE, ATTEN_NORM); + proj.mins = '-16 -16 -16'; + proj.maxs = '16 16 16'; + } + + entity nade_type = Nade_FromProjectile(proj.cnt); + if (nade_type == NADE_TYPE_Null) return; + + if(STAT(NADES_SMALL)) + { + proj.mins = '-8 -8 -8'; + proj.maxs = '8 8 8'; + } + else + { + proj.mins = '-16 -16 -16'; + proj.maxs = '16 16 16'; + } + proj.colormod = nade_type.m_color; + set_movetype(proj, MOVETYPE_BOUNCE); + settouch(proj, func_null); + proj.scale = 1.5; + proj.avelocity = randomvec() * 720; + proj.alphamod = nade_type.m_alpha; + + if (nade_type == NADE_TYPE_TRANSLOCATE || nade_type == NADE_TYPE_SPAWN) + proj.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + else + proj.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY; +} + +MUTATOR_HOOKFUNCTION(cl_nades, BuildGameplayTipsString) +{ + if (mut_is_active(MUT_NADES)) + { + string key = getcommandkey(_("drop weapon / throw nade"), "dropweapon"); + M_ARGV(0, string) = strcat(M_ARGV(0, string), + "\n", sprintf(_("^3nades^8 are enabled, press ^3%s^8 to use them"), key), "\n"); + } +} + +bool Projectile_isnade(int p) +{ + return Nade_FromProjectile(p) != NADE_TYPE_Null; +} +void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expand_time) +{ + float bonusNades = STAT(NADE_BONUS); + float bonusProgress = STAT(NADE_BONUS_SCORE); + float bonusType = STAT(NADE_BONUS_TYPE); + Nade def = REGISTRY_GET(Nades, max(1, bonusType)); + string nadeIcon = def.m_icon; // use the Normal Nade icon for the random nade, and draw it as rainbow + vector nadeColor; + if(bonusType) + nadeColor = def.m_color; + else + { + nadeColor.x = time % (M_PI * 2); + nadeColor.y = 1; + nadeColor.z = 1; + nadeColor = hsv_to_rgb(nadeColor); + } + + vector iconPos, textPos; + + if(autocvar_hud_panel_ammo_iconalign) + { + iconPos = myPos + eX * 2 * mySize.y; + textPos = myPos; + } + else + { + iconPos = myPos; + textPos = myPos + eX * mySize.y; + } + + if(bonusNades > 0 || bonusProgress > 0) + { + DrawNadeProgressBar(myPos, mySize, bonusProgress, nadeColor); + + if(autocvar_hud_panel_ammo_text) + drawstring_aspect(textPos, ftos(bonusNades), vec2((2/3) * mySize.x, mySize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); + + if(draw_expanding) + drawpic_aspect_skin_expanding(iconPos, nadeIcon, '1 1 0' * mySize.y, (bonusType ? '1 1 1' : nadeColor), panel_fg_alpha, DRAWFLAG_NORMAL, expand_time); + + drawpic_aspect_skin(iconPos, nadeIcon, '1 1 0' * mySize.y, (bonusType ? '1 1 1' : nadeColor), panel_fg_alpha, DRAWFLAG_NORMAL); + } +} diff --git a/qcsrc/common/mutators/mutator/nades/cl_nades.qh b/qcsrc/common/mutators/mutator/nades/cl_nades.qh new file mode 100644 index 000000000..5759aea9a --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/cl_nades.qh @@ -0,0 +1,6 @@ +#pragma once + +#include "nades.qh" + +bool Projectile_isnade(int proj); // TODO: remove +void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expand_time); // TODO: mutator diff --git a/qcsrc/common/mutators/mutator/nades/nade/_mod.inc b/qcsrc/common/mutators/mutator/nades/nade/_mod.inc new file mode 100644 index 000000000..2d63abbd4 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/_mod.inc @@ -0,0 +1,12 @@ +// genmod.sh autogenerated file; do not modify +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/qcsrc/common/mutators/mutator/nades/nade/_mod.qh b/qcsrc/common/mutators/mutator/nades/nade/_mod.qh new file mode 100644 index 000000000..a4e4aa18a --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/_mod.qh @@ -0,0 +1,12 @@ +// genmod.sh autogenerated file; do not modify +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/qcsrc/common/mutators/mutator/nades/nade/ammo.qc b/qcsrc/common/mutators/mutator/nades/nade/ammo.qc new file mode 100644 index 000000000..25338029f --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/ammo.qc @@ -0,0 +1,66 @@ +#include "ammo.qh" + +#ifdef SVQC +void nade_ammo_touch(entity this, entity toucher) +{ + float maxammo = 999; + float ammo_factor; + float amshells = GetResource(toucher, RES_SHELLS); + float ambullets = GetResource(toucher, RES_BULLETS); + float amrockets = GetResource(toucher, RES_ROCKETS); + float amcells = GetResource(toucher, RES_CELLS); + if(IS_PLAYER(toucher) || IS_MONSTER(toucher)) + if(!IS_DEAD(toucher)) + if(!STAT(FROZEN, toucher)) + { + ammo_factor = autocvar_g_nades_ammo_rate*frametime/2; + if ( toucher != this.realowner ) + ammo_factor *= (SAME_TEAM(toucher, this)) ? autocvar_g_nades_ammo_friend : autocvar_g_nades_ammo_foe; + +#define CHECK_AMMO_RESOURCE_LIMIT(amresource, res_resource) \ + if (amresource < maxammo) \ + GiveResourceWithLimit(toucher, res_resource, ammo_factor, maxammo); + +#define DROP_AMMO_RESOURCE(amresource, res_resource) \ + if (amresource > 0) \ + SetResource(toucher, res_resource, amresource + ammo_factor); + + if ( ammo_factor > 0 ) + { + CHECK_AMMO_RESOURCE_LIMIT(amshells, RES_SHELLS); + CHECK_AMMO_RESOURCE_LIMIT(ambullets, RES_BULLETS); + CHECK_AMMO_RESOURCE_LIMIT(amrockets, RES_ROCKETS); + CHECK_AMMO_RESOURCE_LIMIT(amcells, RES_CELLS); + + if (this.nade_show_particles) + Send_Effect(EFFECT_HEALING, toucher.origin, '0 0 0', 1); + } + else if ( ammo_factor < 0 ) + { + //Foe drops ammo points + DROP_AMMO_RESOURCE(amshells, RES_SHELLS); + DROP_AMMO_RESOURCE(ambullets, RES_BULLETS); + DROP_AMMO_RESOURCE(amrockets, RES_ROCKETS); + DROP_AMMO_RESOURCE(amcells, RES_CELLS); + + return; + } + } +#undef CHECK_AMMO_RESOURCE_LIMIT +#undef DROP_AMMO_RESOURCE + + if ( IS_REAL_CLIENT(toucher) || (IS_VEHICLE(toucher) && toucher.owner) ) + { + entity show_tint = (IS_VEHICLE(toucher) && toucher.owner) ? toucher.owner : toucher; + show_tint.nade_ammo_time = time + 0.1; + } +} + +void nade_ammo_boom(entity this) +{ + entity orb = nades_spawn_orb(this.owner, this.realowner, this.origin, autocvar_g_nades_ammo_time, autocvar_g_nades_nade_radius); + + settouch(orb, nade_ammo_touch); + orb.colormod = '0.66 0.33 0'; +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/nades/nade/ammo.qh b/qcsrc/common/mutators/mutator/nades/nade/ammo.qh new file mode 100644 index 000000000..6797826f8 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/ammo.qh @@ -0,0 +1,24 @@ +#pragma once + +#include + +#ifdef SVQC +bool autocvar_g_nades_ammo; +float autocvar_g_nades_ammo_time; +float autocvar_g_nades_ammo_rate; +float autocvar_g_nades_ammo_friend; +float autocvar_g_nades_ammo_foe; + +.float nade_ammo_time; + +void nade_ammo_boom(entity this); +#endif // SVQC + +const int PROJECTILE_NADE_AMMO = 88; +const int PROJECTILE_NADE_AMMO_BURN = 89; + +CLASS(AmmoNade, Nade) + ATTRIB(AmmoNade, m_color, vector, '0.66 0.33 0'); + ATTRIB(AmmoNade, m_name, string, _("Ammo grenade")); + ATTRIB(AmmoNade, m_icon, string, "nade_ammo"); +ENDCLASS(AmmoNade) diff --git a/qcsrc/common/mutators/mutator/nades/nade/darkness.qc b/qcsrc/common/mutators/mutator/nades/nade/darkness.qc new file mode 100644 index 000000000..af399741b --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/darkness.qc @@ -0,0 +1,138 @@ +#include "darkness.qh" + +#ifdef SVQC + +void nade_darkness_think(entity this) +{ + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + { + delete(this); + return; + } + + if(time >= this.ltime) + { + if ( autocvar_g_nades_darkness_explode ) + { + vector expcol_min = nades_PlayerColor(this.realowner, false); + vector expcol_max = nades_PlayerColor(this.realowner, true); + entity expef = EFFECT_NADE_EXPLODE_NEUTRAL; + Send_Effect_Except(expef, this.origin + '0 0 1', '0 0 0', 1, expcol_min, expcol_max, NULL); + sound(this, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); + + normal_nade_boom(this); + } + else + Send_Effect(EFFECT_SPAWN_PURPLE, this.origin + '0 0 1', '0 0 0', 1); + + delete(this); + return; + } + + this.nextthink = time + 0.1; + + // gaussian + float randomr; + randomr = random(); + randomr = exp(-5 * randomr * randomr) * autocvar_g_nades_nade_radius; + float randomw; + randomw = random() * (2 * M_PI); + vector randomp; + randomp.x = randomr * cos(randomw); + randomp.y = randomr * sin(randomw); + randomp.z = 1; + Send_Effect(EFFECT_DARKFIELD, this.origin + randomp, '0 0 0', 1); + + if(time >= this.nade_special_time) + { + this.nade_special_time = time + 0.7; + Send_Effect(EFFECT_DARKFIELD, this.origin, '0 0 0', 1); + } + + + float current_dark_time = this.ltime - time - 0.1; + + FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_nades_nade_radius, it != this && it.takedamage + && !IS_DEAD(it) && GetResource(it, RES_HEALTH) > 0 && current_dark_time > 0 && IS_REAL_CLIENT(it), + { + switch (autocvar_g_nades_darkness_teamcheck) + { + case 0: break; // affect everyone + default: + case 2: if(SAME_TEAM(it, this.realowner)) continue; // don't affect teammates + // fall through (check case 1 condition too) + case 1: if(it == this.realowner) continue; // don't affect the player who threw the nade + } + STAT(NADE_DARKNESS_TIME, it) = time + 0.1; + }); +} + +void nade_darkness_boom(entity this) +{ + entity fountain = new(nade_darkness_fountain); + fountain.owner = this.owner; + fountain.realowner = this.realowner; + fountain.origin = this.origin; + setorigin(fountain, fountain.origin); + setthink(fountain, nade_darkness_think); + fountain.nextthink = time; + fountain.ltime = time + autocvar_g_nades_darkness_time; + fountain.pushltime = fountain.wait = fountain.ltime; + fountain.team = this.team; + set_movetype(fountain, MOVETYPE_TOSS); + fountain.projectiledeathtype = DEATH_NADE.m_id; + fountain.bot_dodge = false; + setsize(fountain, '-16 -16 -16', '16 16 16'); + fountain.nade_special_time = time + 0.3; + fountain.angles = this.angles; + + if ( autocvar_g_nades_darkness_explode ) + { + setmodel(fountain, MDL_PROJECTILE_GRENADE); + entity timer = new(nade_timer); + setmodel(timer, MDL_NADE_TIMER); + setattachment(timer, fountain, ""); + timer.colormap = this.colormap; + timer.glowmod = this.glowmod; + setthink(timer, nade_timer_think); + timer.nextthink = time; + timer.wait = fountain.ltime; + timer.owner = fountain; + timer.skin = 10; + } + else + setmodel(fountain, MDL_Null); +} + +#endif // SVQC +#ifdef CSQC + +bool darkness_fadealpha; + +void HUD_DarkBlinking() +{ + vector bottomright = vec2(vid_conwidth, vid_conheight); + drawfill('0 0 0', bottomright, NADE_TYPE_DARKNESS.m_color, darkness_fadealpha, DRAWFLAG_NORMAL); +} + +MUTATOR_HOOKFUNCTION(cl_nades, HUD_Draw_overlay) +{ + if (STAT(NADE_DARKNESS_TIME) > time) + { + if (!darkness_fadealpha) + sound(csqcplayer, CH_PAIN, SND_BLIND, VOL_BASE, ATTEN_NORM); + darkness_fadealpha = min(0.986, darkness_fadealpha + frametime * 7); + } + else if (darkness_fadealpha > 0) + darkness_fadealpha = max(0, darkness_fadealpha - frametime * 7); + + if (darkness_fadealpha > 0) + { + HUD_DarkBlinking(); + M_ARGV(1, float) = 0; // alpha_multipl 0, don't draw normal overlay + return true; + } + return false; +} +#endif // CSQC diff --git a/qcsrc/common/mutators/mutator/nades/nade/darkness.qh b/qcsrc/common/mutators/mutator/nades/nade/darkness.qh new file mode 100644 index 000000000..e638aa175 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/darkness.qh @@ -0,0 +1,21 @@ +#pragma once + +#include + +#ifdef SVQC +bool autocvar_g_nades_darkness; +bool autocvar_g_nades_darkness_explode; +bool autocvar_g_nades_darkness_teamcheck; +float autocvar_g_nades_darkness_time; + +void nade_darkness_boom(entity this); +#endif // SVQC + +const int PROJECTILE_NADE_DARKNESS = 90; +const int PROJECTILE_NADE_DARKNESS_BURN = 91; + +CLASS(DarknessNade, Nade) + ATTRIB(DarknessNade, m_color, vector, '0.23 0 0.23'); + ATTRIB(DarknessNade, m_name, string, _("Darkness grenade")); + ATTRIB(DarknessNade, m_icon, string, "nade_darkness"); +ENDCLASS(DarknessNade) diff --git a/qcsrc/common/mutators/mutator/nades/nade/entrap.qc b/qcsrc/common/mutators/mutator/nades/nade/entrap.qc new file mode 100644 index 000000000..c0a348f4b --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/entrap.qc @@ -0,0 +1,66 @@ +#include "entrap.qh" + +#ifdef SVQC +#include "veil.qh" + +void nade_entrap_touch(entity this, entity toucher) +{ + if(DIFF_TEAM(toucher, this.realowner)) // TODO: what if realowner changes team or disconnects? + { + if (!isPushable(toucher)) + return; + + float pushdeltatime = time - toucher.lastpushtime; + if (pushdeltatime > 0.15) pushdeltatime = 0; + toucher.lastpushtime = time; + if(!pushdeltatime) return; + + // div0: ticrate independent, 1 = identity (not 20) + toucher.velocity = toucher.velocity * (autocvar_g_nades_entrap_strength ** pushdeltatime); + + #ifdef SVQC + UpdateCSQCProjectile(toucher); + #endif + } + + if ( IS_REAL_CLIENT(toucher) || (IS_VEHICLE(toucher) && toucher.owner) ) + { + entity show_tint = (IS_VEHICLE(toucher) && toucher.owner) ? toucher.owner : toucher; + show_tint.nade_entrap_time = time + 0.1; + } +} + +void nade_entrap_boom(entity this) +{ + entity orb = nades_spawn_orb(this.owner, this.realowner, this.origin, autocvar_g_nades_entrap_time, autocvar_g_nades_entrap_radius); + + settouch(orb, nade_entrap_touch); + orb.colormod = NADE_TYPE_ENTRAP.m_color; +} + +MUTATOR_HOOKFUNCTION(nades, PlayerPhysics_UpdateStats) +{ + entity player = M_ARGV(0, entity); + // these automatically reset, no need to worry + + if(player.nade_entrap_time > time) + STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_nades_entrap_speed; +} + +MUTATOR_HOOKFUNCTION(nades, MonsterMove) +{ + entity mon = M_ARGV(0, entity); + + if (mon.nade_entrap_time > time) + { + M_ARGV(1, float) *= autocvar_g_nades_entrap_speed; // run speed + M_ARGV(2, float) *= autocvar_g_nades_entrap_speed; // walk speed + } + + if (mon.nade_veil_time && mon.nade_veil_time <= time) + { + mon.alpha = mon.nade_veil_prevalpha; + mon.nade_veil_time = 0; + } +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/nades/nade/entrap.qh b/qcsrc/common/mutators/mutator/nades/nade/entrap.qh new file mode 100644 index 000000000..e89c64fba --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/entrap.qh @@ -0,0 +1,24 @@ +#pragma once + +#include + +#ifdef SVQC +bool autocvar_g_nades_entrap; +float autocvar_g_nades_entrap_strength = 0.01; +float autocvar_g_nades_entrap_speed = 0.5; +float autocvar_g_nades_entrap_radius = 500; +float autocvar_g_nades_entrap_time = 10; + +.float nade_entrap_time; + +void nade_entrap_boom(entity this); +#endif // SVQC + +const int PROJECTILE_NADE_ENTRAP = 84; +const int PROJECTILE_NADE_ENTRAP_BURN = 85; + +CLASS(EntrapNade, Nade) + ATTRIB(EntrapNade, m_color, vector, '0.15 0.85 0'); + ATTRIB(EntrapNade, m_name, string, _("Entrap grenade")); + ATTRIB(EntrapNade, m_icon, string, "nade_entrap"); +ENDCLASS(EntrapNade) diff --git a/qcsrc/common/mutators/mutator/nades/nade/heal.qc b/qcsrc/common/mutators/mutator/nades/nade/heal.qc new file mode 100644 index 000000000..9d376762a --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/heal.qc @@ -0,0 +1,41 @@ +#include "heal.qh" + +#ifdef SVQC +void nade_heal_touch(entity this, entity toucher) +{ + float maxhealth; + float health_factor; + + if(IS_PLAYER(toucher) || IS_MONSTER(toucher) || IS_VEHICLE(toucher)) + if(!IS_DEAD(toucher)) + if(!STAT(FROZEN, toucher)) + { + health_factor = autocvar_g_nades_heal_rate*frametime/2; + if ( toucher != this.realowner ) + health_factor *= (SAME_TEAM(toucher,this)) ? autocvar_g_nades_heal_friend : autocvar_g_nades_heal_foe; + + if ( health_factor > 0 ) + { + maxhealth = (IS_MONSTER(toucher)) ? toucher.max_health : g_pickup_healthmega_max; + float hp = GetResource(toucher, RES_HEALTH); + if (hp < maxhealth) + { + if (this.nade_show_particles) + Send_Effect(EFFECT_HEALING, toucher.origin, '0 0 0', 1); + + GiveResourceWithLimit(toucher, RES_HEALTH, health_factor, maxhealth); + } + } + else if ( health_factor < 0 ) + Damage(toucher,this,this.realowner,-health_factor,DEATH_NADE_HEAL.m_id,DMG_NOWEP,toucher.origin,'0 0 0'); + } +} + +void nade_heal_boom(entity this) +{ + entity orb = nades_spawn_orb(this.owner, this.realowner, this.origin, autocvar_g_nades_heal_time, autocvar_g_nades_nade_radius); + + settouch(orb, nade_heal_touch); + orb.colormod = '1 0 0'; +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/nades/nade/heal.qh b/qcsrc/common/mutators/mutator/nades/nade/heal.qh new file mode 100644 index 000000000..49b6f9edb --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/heal.qh @@ -0,0 +1,22 @@ +#pragma once + +#include + +#ifdef SVQC +bool autocvar_g_nades_heal = true; +float autocvar_g_nades_heal_time; +float autocvar_g_nades_heal_rate; +float autocvar_g_nades_heal_friend; +float autocvar_g_nades_heal_foe; + +void nade_heal_boom(entity this); +#endif // SVQC + +const int PROJECTILE_NADE_HEAL = 80; +const int PROJECTILE_NADE_HEAL_BURN = 81; + +CLASS(HealNade, Nade) + ATTRIB(HealNade, m_color, vector, '1 0 0'); + ATTRIB(HealNade, m_name, string, _("Heal grenade")); + ATTRIB(HealNade, m_icon, string, "nade_heal"); +ENDCLASS(HealNade) diff --git a/qcsrc/common/mutators/mutator/nades/nade/ice.qc b/qcsrc/common/mutators/mutator/nades/nade/ice.qc new file mode 100644 index 000000000..63abfdc06 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/ice.qc @@ -0,0 +1,116 @@ +#include "ice.qh" + +#ifdef SVQC +void nade_ice_freeze(entity freezefield, entity frost_target, float freezetime) +{ + frost_target.frozen_by = freezefield.realowner; + Send_Effect(EFFECT_ELECTRO_IMPACT, frost_target.origin, '0 0 0', 1); + Freeze(frost_target, 1 / freezetime, FROZEN_TEMP_DYING, false); + + Drop_Special_Items(frost_target); +} + +void nade_ice_think(entity this) +{ + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + { + delete(this); + return; + } + + if(time >= this.ltime) + { + if ( autocvar_g_nades_ice_explode ) + { + vector expcol_min = nades_PlayerColor(this.realowner, false); + vector expcol_max = nades_PlayerColor(this.realowner, true); + entity expef = EFFECT_NADE_EXPLODE_NEUTRAL; + Send_Effect_Except(expef, this.origin + '0 0 1', '0 0 0', 1, expcol_min, expcol_max, NULL); + sound(this, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); + + normal_nade_boom(this); + } + delete(this); + return; + } + + + this.nextthink = time + 0.1; + + // gaussian + float randomr; + randomr = random(); + randomr = exp(-5 * randomr * randomr) * autocvar_g_nades_nade_radius; + float randomw; + randomw = random() * (2 * M_PI); + vector randomp; + randomp.x = randomr * cos(randomw); + randomp.y = randomr * sin(randomw); + randomp.z = 1; + Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, this.origin + randomp, '0 0 0', 1); + + if(time >= this.nade_special_time) + { + this.nade_special_time = time + 0.7; + + Send_Effect(EFFECT_ELECTRO_IMPACT, this.origin, '0 0 0', 1); + Send_Effect(EFFECT_ICEFIELD, this.origin, '0 0 0', 1); + } + + + float current_freeze_time = this.ltime - time - 0.1; + + FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_nades_nade_radius, it != this && it.takedamage + && !IS_DEAD(it) && GetResource(it, RES_HEALTH) > 0 && current_freeze_time > 0 + && (!it.revival_time || ((time - it.revival_time) >= 1.5)) && !STAT(FROZEN, it), + { + switch (autocvar_g_nades_ice_teamcheck) + { + case 0: break; // affect everyone + default: + case 2: if(SAME_TEAM(it, this.realowner)) continue; // don't affect teammates + // fall through (check case 1 condition too) + case 1: if(it == this.realowner) continue; // don't affect the player who threw the nade + } + nade_ice_freeze(this, it, current_freeze_time); + }); +} + +void nade_ice_boom(entity this) +{ + entity fountain = new(nade_ice_fountain); + fountain.owner = this.owner; + fountain.realowner = this.realowner; + fountain.origin = this.origin; + setorigin(fountain, fountain.origin); + setthink(fountain, nade_ice_think); + fountain.nextthink = time; + fountain.ltime = time + autocvar_g_nades_ice_freeze_time; + fountain.pushltime = fountain.wait = fountain.ltime; + fountain.team = this.team; + set_movetype(fountain, MOVETYPE_TOSS); + fountain.projectiledeathtype = DEATH_NADE_ICE.m_id; + fountain.bot_dodge = false; + setsize(fountain, '-16 -16 -16', '16 16 16'); + fountain.nade_special_time = time + 0.3; + fountain.angles = this.angles; + + if ( autocvar_g_nades_ice_explode ) + { + setmodel(fountain, MDL_PROJECTILE_GRENADE); + entity timer = new(nade_timer); + setmodel(timer, MDL_NADE_TIMER); + setattachment(timer, fountain, ""); + timer.colormap = this.colormap; + timer.glowmod = this.glowmod; + setthink(timer, nade_timer_think); + timer.nextthink = time; + timer.wait = fountain.ltime; + timer.owner = fountain; + timer.skin = 10; + } + else + setmodel(fountain, MDL_Null); +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/nades/nade/ice.qh b/qcsrc/common/mutators/mutator/nades/nade/ice.qh new file mode 100644 index 000000000..2ac7dd2b1 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/ice.qh @@ -0,0 +1,22 @@ +#pragma once + +#include + +#ifdef SVQC +bool autocvar_g_nades_ice = true; +float autocvar_g_nades_ice_freeze_time; +float autocvar_g_nades_ice_health; +bool autocvar_g_nades_ice_explode; +bool autocvar_g_nades_ice_teamcheck; + +void nade_ice_boom(entity this); +#endif // SVQC + +const int PROJECTILE_NADE_ICE = 76; +const int PROJECTILE_NADE_ICE_BURN = 77; + +CLASS(IceNade, Nade) + ATTRIB(IceNade, m_color, vector, '0 0.5 2'); + ATTRIB(IceNade, m_name, string, _("Ice grenade")); + ATTRIB(IceNade, m_icon, string, "nade_ice"); +ENDCLASS(IceNade) diff --git a/qcsrc/common/mutators/mutator/nades/nade/monster.qc b/qcsrc/common/mutators/mutator/nades/nade/monster.qc new file mode 100644 index 000000000..4c5c7d165 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/monster.qc @@ -0,0 +1,21 @@ +#include "monster.qh" + +#ifdef SVQC +#include +#include + +void nade_monster_boom(entity this) +{ + if(!autocvar_g_monsters) + return; + entity e = spawn(); + e.noalign = true; // don't drop to floor + e = spawnmonster(e, this.pokenade_type, MON_Null, this.realowner, this.realowner, this.origin, false, false, 1); + if(!e) + return; // monster failed to be spawned + + if(autocvar_g_nades_pokenade_monster_lifetime > 0) + e.monster_lifetime = time + autocvar_g_nades_pokenade_monster_lifetime; + e.monster_skill = MONSTER_SKILL_INSANE; +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/nades/nade/monster.qh b/qcsrc/common/mutators/mutator/nades/nade/monster.qh new file mode 100644 index 000000000..464b34b55 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/monster.qh @@ -0,0 +1,21 @@ +#pragma once + +#include + +#ifdef SVQC +// TODO: rename these to `monster` +bool autocvar_g_nades_pokenade; +string autocvar_g_nades_pokenade_monster_type; +float autocvar_g_nades_pokenade_monster_lifetime; + +void nade_monster_boom(entity this); +#endif // SVQC + +const int PROJECTILE_NADE_MONSTER = 82; +const int PROJECTILE_NADE_MONSTER_BURN = 83; + +CLASS(MonsterNade, Nade) + ATTRIB(MonsterNade, m_color, vector, '0.25 0.75 0'); + ATTRIB(MonsterNade, m_name, string, _("Monster grenade")); + ATTRIB(MonsterNade, m_icon, string, "nade_monster"); +ENDCLASS(MonsterNade) diff --git a/qcsrc/common/mutators/mutator/nades/nade/napalm.qc b/qcsrc/common/mutators/mutator/nades/nade/napalm.qc new file mode 100644 index 000000000..9a299ed76 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/napalm.qc @@ -0,0 +1,178 @@ +#include "napalm.qh" + +#ifdef SVQC +void napalm_damage(entity this, float dist, float damage, float edgedamage, float burntime) +{ + entity e; + float d; + vector p; + + if ( damage < 0 ) + return; + + RandomSelection_Init(); + for(e = WarpZone_FindRadius(this.origin, dist, true); e; e = e.chain) + if(e.takedamage == DAMAGE_AIM) + if(this.realowner != e || autocvar_g_nades_napalm_selfdamage) + if(!IS_PLAYER(e) || !this.realowner || DIFF_TEAM(e, this)) + if(!STAT(FROZEN, e)) + { + p = e.origin; + p.x += e.mins.x + random() * (e.maxs.x - e.mins.x); + p.y += e.mins.y + random() * (e.maxs.y - e.mins.y); + p.z += e.mins.z + random() * (e.maxs.z - e.mins.z); + d = vlen(WarpZone_UnTransformOrigin(e, this.origin) - p); + if(d < dist) + { + e.fireball_impactvec = p; + RandomSelection_AddEnt(e, 1 / (1 + d), !StatusEffects_active(STATUSEFFECT_Burning, e)); + } + } + if(RandomSelection_chosen_ent) + { + d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, this.origin) - RandomSelection_chosen_ent.fireball_impactvec); + d = damage + (edgedamage - damage) * (d / dist); + Fire_AddDamage(RandomSelection_chosen_ent, this.realowner, d * burntime, burntime, this.projectiledeathtype); + //trailparticles(this, particleeffectnum(EFFECT_FIREBALL_LASER), this.origin, RandomSelection_chosen_ent.fireball_impactvec); + Send_Effect(EFFECT_FIREBALL_LASER, this.origin, RandomSelection_chosen_ent.fireball_impactvec - this.origin, 1); + } +} + +void napalm_ball_think(entity this) +{ + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + { + delete(this); + return; + } + + if(time > this.pushltime) + { + delete(this); + return; + } + + vector midpoint = ((this.absmin + this.absmax) * 0.5); + if(pointcontents(midpoint) == CONTENT_WATER) + { + this.velocity = this.velocity * 0.5; + + if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER) + { this.velocity_z = 200; } + } + + this.angles = vectoangles(this.velocity); + + napalm_damage(this, autocvar_g_nades_napalm_ball_radius,autocvar_g_nades_napalm_ball_damage, + autocvar_g_nades_napalm_ball_damage,autocvar_g_nades_napalm_burntime); + + this.nextthink = time + 0.1; +} + +void nade_napalm_ball(entity this) +{ + entity proj; + vector kick; + + spamsound(this, CH_SHOTS, SND_FIREBALL_FIRE, VOL_BASE, ATTEN_NORM); + + proj = new(grenade); + proj.owner = this.owner; + proj.realowner = this.realowner; + proj.team = this.owner.team; + proj.bot_dodge = true; + proj.bot_dodgerating = autocvar_g_nades_napalm_ball_damage; + set_movetype(proj, MOVETYPE_BOUNCE); + proj.projectiledeathtype = DEATH_NADE_NAPALM.m_id; + PROJECTILE_MAKETRIGGER(proj); + setmodel(proj, MDL_Null); + proj.scale = 1;//0.5; + setsize(proj, '-4 -4 -4', '4 4 4'); + setorigin(proj, this.origin); + setthink(proj, napalm_ball_think); + proj.nextthink = time; + proj.damageforcescale = autocvar_g_nades_napalm_ball_damageforcescale; + proj.effects = EF_LOWPRECISION | EF_FLAME; + + kick.x =(random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread; + kick.y = (random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread; + kick.z = (random()/2+0.5) * autocvar_g_nades_napalm_ball_spread; + proj.velocity = kick; + + proj.pushltime = time + autocvar_g_nades_napalm_ball_lifetime; + + proj.angles = vectoangles(proj.velocity); + proj.flags = FL_PROJECTILE; + IL_PUSH(g_projectiles, proj); + IL_PUSH(g_bot_dodge, proj); + proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC; + + //CSQCProjectile(proj, true, PROJECTILE_NAPALM_FIRE, true); +} + +void napalm_fountain_think(entity this) +{ + + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + { + delete(this); + return; + } + + if(time >= this.ltime) + { + delete(this); + return; + } + + vector midpoint = ((this.absmin + this.absmax) * 0.5); + if(pointcontents(midpoint) == CONTENT_WATER) + { + this.velocity = this.velocity * 0.5; + + if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER) + { this.velocity_z = 200; } + + UpdateCSQCProjectile(this); + } + + napalm_damage(this, autocvar_g_nades_napalm_fountain_radius, autocvar_g_nades_napalm_fountain_damage, + autocvar_g_nades_napalm_fountain_edgedamage, autocvar_g_nades_napalm_burntime); + + this.nextthink = time + 0.1; + if(time >= this.nade_special_time) + { + this.nade_special_time = time + autocvar_g_nades_napalm_fountain_delay; + nade_napalm_ball(this); + } +} + +void nade_napalm_boom(entity this) +{ + for (int c = 0; c < autocvar_g_nades_napalm_ball_count; ++c) + nade_napalm_ball(this); + + entity fountain = new(nade_napalm_fountain); + fountain.owner = this.owner; + fountain.realowner = this.realowner; + fountain.origin = this.origin; + fountain.flags = FL_PROJECTILE; + IL_PUSH(g_projectiles, fountain); + IL_PUSH(g_bot_dodge, fountain); + setorigin(fountain, fountain.origin); + setthink(fountain, napalm_fountain_think); + fountain.nextthink = time; + fountain.ltime = time + autocvar_g_nades_napalm_fountain_lifetime; + fountain.pushltime = fountain.ltime; + fountain.team = this.team; + set_movetype(fountain, MOVETYPE_TOSS); + fountain.projectiledeathtype = DEATH_NADE_NAPALM.m_id; + fountain.bot_dodge = true; + fountain.bot_dodgerating = autocvar_g_nades_napalm_fountain_damage; + fountain.nade_special_time = time; + setsize(fountain, '-16 -16 -16', '16 16 16'); + CSQCProjectile(fountain, true, PROJECTILE_NAPALM_FOUNTAIN, true); +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/nades/nade/napalm.qh b/qcsrc/common/mutators/mutator/nades/nade/napalm.qh new file mode 100644 index 000000000..26f6cc817 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/napalm.qh @@ -0,0 +1,33 @@ +#pragma once + +#include + +#ifdef SVQC +bool autocvar_g_nades_napalm; +int autocvar_g_nades_napalm_ball_count; +float autocvar_g_nades_napalm_ball_spread; +float autocvar_g_nades_napalm_ball_damage; +float autocvar_g_nades_napalm_ball_damageforcescale; +float autocvar_g_nades_napalm_ball_lifetime; +float autocvar_g_nades_napalm_ball_radius; +bool autocvar_g_nades_napalm_blast; +float autocvar_g_nades_napalm_fountain_lifetime; +float autocvar_g_nades_napalm_fountain_delay; +float autocvar_g_nades_napalm_fountain_radius; +float autocvar_g_nades_napalm_fountain_damage; +float autocvar_g_nades_napalm_fountain_edgedamage; +float autocvar_g_nades_napalm_burntime; +bool autocvar_g_nades_napalm_selfdamage; + +void nade_napalm_boom(entity this); +#endif // SVQC + +const int PROJECTILE_NADE_NAPALM = 73; +const int PROJECTILE_NADE_NAPALM_BURN = 74; +const int PROJECTILE_NAPALM_FOUNTAIN = 75; + +CLASS(NapalmNade, Nade) + ATTRIB(NapalmNade, m_color, vector, '2 0.5 0'); + ATTRIB(NapalmNade, m_name, string, _("Napalm grenade")); + ATTRIB(NapalmNade, m_icon, string, "nade_napalm"); +ENDCLASS(NapalmNade) diff --git a/qcsrc/common/mutators/mutator/nades/nade/normal.qc b/qcsrc/common/mutators/mutator/nades/nade/normal.qc new file mode 100644 index 000000000..4136fb59d --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/normal.qc @@ -0,0 +1,11 @@ +#include "normal.qh" + +#ifdef SVQC +void normal_nade_boom(entity this) +{ + RadiusDamage(this, this.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, + autocvar_g_nades_nade_radius, this, NULL, autocvar_g_nades_nade_force, this.projectiledeathtype, DMG_NOWEP, this.enemy); + Damage_DamageInfo(this.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, + autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, this.projectiledeathtype, 0, this); +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/nades/nade/normal.qh b/qcsrc/common/mutators/mutator/nades/nade/normal.qh new file mode 100644 index 000000000..24716f076 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/normal.qh @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef SVQC +void normal_nade_boom(entity this); +#endif // SVQC + +CLASS(NormalNade, Nade) + ATTRIB(NormalNade, m_color, vector, '1 1 1'); + ATTRIB(NormalNade, m_name, string, _("Grenade")); + ATTRIB(NormalNade, m_icon, string, "nade_normal"); +ENDCLASS(NormalNade) diff --git a/qcsrc/common/mutators/mutator/nades/nade/spawn.qc b/qcsrc/common/mutators/mutator/nades/nade/spawn.qc new file mode 100644 index 000000000..ea09f3d75 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/spawn.qc @@ -0,0 +1,34 @@ +#include "spawn.qh" + +#ifdef SVQC +void nade_spawn_boom(entity this) +{ + entity player = this.realowner; + entity spawnloc = new(nade_spawn_loc); + setorigin(spawnloc, this.origin); + setsize(spawnloc, player.mins, player.maxs); + set_movetype(spawnloc, MOVETYPE_NONE); + spawnloc.solid = SOLID_NOT; + spawnloc.drawonlytoclient = player; + spawnloc.effects = EF_STARDUST; + spawnloc.cnt = autocvar_g_nades_spawn_count; + + if(player.nade_spawnloc) + delete(player.nade_spawnloc); + + player.nade_spawnloc = spawnloc; +} + +void nade_spawn_SetSpawnHealth(entity player) +{ + if(autocvar_g_nades_spawn_health_respawn > 0) + SetResource(player, RES_HEALTH, autocvar_g_nades_spawn_health_respawn); +} + +bool nade_spawn_DestroyDamage(entity this, entity attacker) +{ + if(autocvar_g_nades_spawn_destroy_damage > 0 && STAT(NADE_BONUS_TYPE, this) == NADE_TYPE_SPAWN.m_id) + Damage(this.realowner, attacker, attacker, autocvar_g_nades_spawn_destroy_damage, DEATH_TOUCHEXPLODE.m_id, DMG_NOWEP, this.realowner.origin, '0 0 0'); + return false; +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/nades/nade/spawn.qh b/qcsrc/common/mutators/mutator/nades/nade/spawn.qh new file mode 100644 index 000000000..0b97773e3 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/spawn.qh @@ -0,0 +1,19 @@ +#pragma once + +#include + +#ifdef SVQC +bool autocvar_g_nades_spawn = true; + +void nade_spawn_boom(entity this); +void nade_spawn_SetSpawnHealth(entity player); +bool nade_spawn_DestroyDamage(entity this, entity attacker); +#endif // SVQC + +const int PROJECTILE_NADE_SPAWN = 79; + +CLASS(SpawnNade, Nade) + ATTRIB(SpawnNade, m_color, vector, '1 0.9 0'); + ATTRIB(SpawnNade, m_name, string, _("Spawn grenade")); + ATTRIB(SpawnNade, m_icon, string, "nade_spawn"); +ENDCLASS(SpawnNade) diff --git a/qcsrc/common/mutators/mutator/nades/nade/translocate.qc b/qcsrc/common/mutators/mutator/nades/nade/translocate.qc new file mode 100644 index 000000000..4ddf739fd --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/translocate.qc @@ -0,0 +1,39 @@ +#include "translocate.qh" + +#ifdef SVQC +void nade_translocate_boom(entity this) +{ + if(this.realowner.vehicle) + return; + + setsize(this, PL_MIN_CONST-'16 16 16', PL_MAX_CONST+'16 16 16'); + + if(!move_out_of_solid(this)) + { + sprint(this.realowner, "^1Couldn't move the translocator out of solid! origin: ", vtos(this.origin), "\n"); + return; + } + + vector locout = this.origin + '0 0 1' * (1 - this.realowner.mins.z - 24); + tracebox(locout, this.realowner.mins, this.realowner.maxs, locout, MOVE_NOMONSTERS, this.realowner); + locout = trace_endpos; + + makevectors(this.realowner.angles); + + MUTATOR_CALLHOOK(PortalTeleport, this.realowner); + + TeleportPlayer(this, this.realowner, locout, this.realowner.angles, v_forward * vlen(this.realowner.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER); +} + +bool nade_translocate_DestroyDamage(entity this, entity attacker) +{ + if(autocvar_g_nades_translocate_destroy_damage > 0 && STAT(NADE_BONUS_TYPE, this) == NADE_TYPE_TRANSLOCATE.m_id) + { + Damage(this.realowner, attacker, attacker, autocvar_g_nades_translocate_destroy_damage, DEATH_TOUCHEXPLODE.m_id, DMG_NOWEP, this.realowner.origin, '0 0 0'); + W_PrepareExplosionByDamage(this, this.realowner, nade_boom); // Don't change the owner + + return true; + } + return false; +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/nades/nade/translocate.qh b/qcsrc/common/mutators/mutator/nades/nade/translocate.qh new file mode 100644 index 000000000..0ad11a103 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/translocate.qh @@ -0,0 +1,19 @@ +#pragma once + +#include + +#ifdef SVQC +bool autocvar_g_nades_translocate = true; +float autocvar_g_nades_translocate_destroy_damage = 25; + +void nade_translocate_boom(entity this); +bool nade_translocate_DestroyDamage(entity this, entity attacker); +#endif // SVQC + +const int PROJECTILE_NADE_TRANSLOCATE = 78; + +CLASS(TranslocateNade, Nade) + ATTRIB(TranslocateNade, m_color, vector, '1 0 1'); + ATTRIB(TranslocateNade, m_name, string, _("Translocate grenade")); + ATTRIB(TranslocateNade, m_icon, string, "nade_translocate"); +ENDCLASS(TranslocateNade) diff --git a/qcsrc/common/mutators/mutator/nades/nade/veil.qc b/qcsrc/common/mutators/mutator/nades/nade/veil.qc new file mode 100644 index 000000000..dda5e5203 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/veil.qc @@ -0,0 +1,45 @@ +#include "veil.qh" + +#ifdef SVQC +#include "entrap.qh" + +void nade_veil_touch(entity this, entity toucher) +{ + if ( IS_REAL_CLIENT(toucher) || (IS_VEHICLE(toucher) && toucher.owner) ) + { + entity show_tint = (IS_VEHICLE(toucher) && toucher.owner) ? toucher.owner : toucher; + + float tint_alpha = 0.75; + if(SAME_TEAM(toucher, this.realowner)) + { + tint_alpha = 0.45; + if(!show_tint.nade_veil_time) + { + toucher.nade_veil_prevalpha = toucher.alpha; + toucher.alpha = -1; + } + } + show_tint.nade_veil_time = time + 0.1; + } +} + +void nade_veil_boom(entity this) +{ + entity orb = nades_spawn_orb(this.owner, this.realowner, this.origin, autocvar_g_nades_veil_time, autocvar_g_nades_veil_radius); + + settouch(orb, nade_veil_touch); + orb.colormod = NADE_TYPE_VEIL.m_color; +} + +void nade_veil_Apply(entity player) +{ + if(player.nade_veil_time && player.nade_veil_time <= time) + { + player.nade_veil_time = 0; + if(player.vehicle) + player.vehicle.alpha = player.vehicle.nade_veil_prevalpha; + else + player.alpha = player.nade_veil_prevalpha; + } +} +#endif // SVQC diff --git a/qcsrc/common/mutators/mutator/nades/nade/veil.qh b/qcsrc/common/mutators/mutator/nades/nade/veil.qh new file mode 100644 index 000000000..09169da51 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/nade/veil.qh @@ -0,0 +1,25 @@ +#pragma once + +#include + +#ifdef SVQC +bool autocvar_g_nades_veil; +float autocvar_g_nades_veil_time = 8; +float autocvar_g_nades_veil_radius = 300; + +.float nade_veil_time; +.float nade_veil_prevalpha; + +void nade_veil_boom(entity this); +void nade_veil_Apply(entity player); +#endif // SVQC + +const int PROJECTILE_NADE_VEIL = 86; +const int PROJECTILE_NADE_VEIL_BURN = 87; + +CLASS(VeilNade, Nade) + ATTRIB(VeilNade, m_color, vector, '0.65 0.85 0.65'); + ATTRIB(VeilNade, m_name, string, _("Veil grenade")); + ATTRIB(VeilNade, m_icon, string, "nade_veil"); + ATTRIB(VeilNade, m_alpha, float, 0.45); +ENDCLASS(VeilNade) diff --git a/qcsrc/common/mutators/mutator/nades/nades.qc b/qcsrc/common/mutators/mutator/nades/nades.qc index d65c2bd44..7a6b7f08b 100644 --- a/qcsrc/common/mutators/mutator/nades/nades.qc +++ b/qcsrc/common/mutators/mutator/nades/nades.qc @@ -1,7 +1,6 @@ #include "nades.qh" -#include "../overkill/okmachinegun.qh" -#include "../overkill/okshotgun.qh" +#include "nade/_mod.qh" #ifdef SVQC bool autocvar_g_nades_nade_small; @@ -17,1789 +16,25 @@ REPLICATE(cvar_cl_pokenade_type, string, "cl_pokenade_type"); entity Nade_TrailEffect(int proj, int nade_team) { - switch (proj) - { - case PROJECTILE_NADE: return EFFECT_NADE_TRAIL(nade_team); - case PROJECTILE_NADE_BURN: return EFFECT_NADE_TRAIL_BURN(nade_team); - } - - FOREACH(Nades, true, { - for (int j = 0; j < 2; ++j) - { - if (it.m_projectile[j] == proj) - { - string trail = it.m_trail[j].eent_eff_name; - if (trail) return it.m_trail[j]; - break; - } - } - }); - - return EFFECT_Null; -} - -#endif - -#ifdef CSQC -#include -#include - -bool darkness_fadealpha; - -void HUD_DarkBlinking() -{ - vector bottomright = vec2(vid_conwidth, vid_conheight); - drawfill('0 0 0', bottomright, NADE_TYPE_DARKNESS.m_color, darkness_fadealpha, DRAWFLAG_NORMAL); -} - -REGISTER_MUTATOR(cl_nades, true); -MUTATOR_HOOKFUNCTION(cl_nades, HUD_Draw_overlay) -{ - if (STAT(NADE_DARKNESS_TIME) > time) - { - if (!darkness_fadealpha) - sound(csqcplayer, CH_PAIN, SND_BLIND, VOL_BASE, ATTEN_NORM); - darkness_fadealpha = min(0.986, darkness_fadealpha + frametime * 7); - } - else if (darkness_fadealpha > 0) - darkness_fadealpha = max(0, darkness_fadealpha - frametime * 7); - - if (darkness_fadealpha > 0) - { - HUD_DarkBlinking(); - M_ARGV(1, float) = 0; // alpha_multipl 0, don't draw normal overlay - return true; - } - return false; -} - -MUTATOR_HOOKFUNCTION(cl_nades, Ent_Projectile) -{ - entity proj = M_ARGV(0, entity); - - if (proj.cnt == PROJECTILE_NAPALM_FOUNTAIN) - { - proj.modelindex = 0; - proj.traileffect = EFFECT_FIREBALL.m_id; - return true; - } - if (Nade_FromProjectile(proj.cnt) != NADE_TYPE_Null) - { - setmodel(proj, MDL_PROJECTILE_NADE); - entity trail = Nade_TrailEffect(proj.cnt, proj.team); - if (trail.eent_eff_name) proj.traileffect = trail.m_id; - return true; - } -} -MUTATOR_HOOKFUNCTION(cl_nades, EditProjectile) -{ - if (!mut_is_active(MUT_NADES)) return; - - entity proj = M_ARGV(0, entity); - - if (proj.cnt == PROJECTILE_NAPALM_FOUNTAIN) - { - loopsound(proj, CH_SHOTS_SINGLE, SND_FIREBALL_FLY2, VOL_BASE, ATTEN_NORM); - proj.mins = '-16 -16 -16'; - proj.maxs = '16 16 16'; - } - - entity nade_type = Nade_FromProjectile(proj.cnt); - if (nade_type == NADE_TYPE_Null) return; - - if(STAT(NADES_SMALL)) - { - proj.mins = '-8 -8 -8'; - proj.maxs = '8 8 8'; - } - else - { - proj.mins = '-16 -16 -16'; - proj.maxs = '16 16 16'; - } - proj.colormod = nade_type.m_color; - set_movetype(proj, MOVETYPE_BOUNCE); - settouch(proj, func_null); - proj.scale = 1.5; - proj.avelocity = randomvec() * 720; - proj.alphamod = nade_type.m_alpha; - - if (nade_type == NADE_TYPE_TRANSLOCATE || nade_type == NADE_TYPE_SPAWN) - proj.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; - else - proj.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY; -} - -MUTATOR_HOOKFUNCTION(cl_nades, BuildGameplayTipsString) -{ - if (mut_is_active(MUT_NADES)) - { - string key = getcommandkey(_("drop weapon / throw nade"), "dropweapon"); - M_ARGV(0, string) = strcat(M_ARGV(0, string), - "\n", sprintf(_("^3nades^8 are enabled, press ^3%s^8 to use them"), key), "\n"); - } -} - -bool Projectile_isnade(int p) -{ - return Nade_FromProjectile(p) != NADE_TYPE_Null; -} -void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expand_time) -{ - float bonusNades = STAT(NADE_BONUS); - float bonusProgress = STAT(NADE_BONUS_SCORE); - float bonusType = STAT(NADE_BONUS_TYPE); - Nade def = REGISTRY_GET(Nades, max(1, bonusType)); - string nadeIcon = def.m_icon; // use the Normal Nade icon for the random nade, and draw it as rainbow - vector nadeColor; - if(bonusType) - nadeColor = def.m_color; - else - { - nadeColor.x = time % (M_PI * 2); - nadeColor.y = 1; - nadeColor.z = 1; - nadeColor = hsv_to_rgb(nadeColor); - } - - vector iconPos, textPos; - - if(autocvar_hud_panel_ammo_iconalign) - { - iconPos = myPos + eX * 2 * mySize.y; - textPos = myPos; - } - else - { - iconPos = myPos; - textPos = myPos + eX * mySize.y; - } - - if(bonusNades > 0 || bonusProgress > 0) - { - DrawNadeProgressBar(myPos, mySize, bonusProgress, nadeColor); - - if(autocvar_hud_panel_ammo_text) - drawstring_aspect(textPos, ftos(bonusNades), vec2((2/3) * mySize.x, mySize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); - - if(draw_expanding) - drawpic_aspect_skin_expanding(iconPos, nadeIcon, '1 1 0' * mySize.y, (bonusType ? '1 1 1' : nadeColor), panel_fg_alpha, DRAWFLAG_NORMAL, expand_time); - - drawpic_aspect_skin(iconPos, nadeIcon, '1 1 0' * mySize.y, (bonusType ? '1 1 1' : nadeColor), panel_fg_alpha, DRAWFLAG_NORMAL); - } -} -#endif - -#ifdef SVQC - -#include -#include -#include -#include - -.float nade_time_primed; -.float nade_lifetime; - -.entity nade_spawnloc; - -vector nades_PlayerColor(entity this, bool isPants) -{ - if(teamplay) - return Team_ColorRGB(this.team); - - // logic copied from Scoreboard_GetName - int col = (this.colormap >= 1024) ? this.colormap - 1024 : this.clientcolors; - return (isPants) ? colormapPaletteColor(col % 16, true) : colormapPaletteColor(floor(col / 16), false); -} - -void nade_timer_think(entity this) -{ - this.skin = 8 - (this.owner.wait - time) / (this.owner.nade_lifetime / 10); - this.nextthink = time; - if(!this.owner || wasfreed(this.owner)) - delete(this); -} - -void nade_burn_spawn(entity _nade) -{ - CSQCProjectile(_nade, true, REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, _nade)).m_projectile[1], true); -} - -void nade_spawn(entity _nade) -{ - entity timer = new(nade_timer); - setmodel(timer, MDL_NADE_TIMER); - setattachment(timer, _nade, ""); - timer.colormap = _nade.colormap; - timer.glowmod = _nade.glowmod; - setthink(timer, nade_timer_think); - timer.nextthink = time; - timer.wait = _nade.wait; - timer.owner = _nade; - timer.skin = 10; - - _nade.effects |= EF_LOWPRECISION; - - CSQCProjectile(_nade, true, REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, _nade)).m_projectile[0], true); -} - -void normal_nade_boom(entity this) -{ - RadiusDamage(this, this.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, - autocvar_g_nades_nade_radius, this, NULL, autocvar_g_nades_nade_force, this.projectiledeathtype, DMG_NOWEP, this.enemy); - Damage_DamageInfo(this.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, - autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, this.projectiledeathtype, 0, this); -} - -void napalm_damage(entity this, float dist, float damage, float edgedamage, float burntime) -{ - entity e; - float d; - vector p; - - if ( damage < 0 ) - return; - - RandomSelection_Init(); - for(e = WarpZone_FindRadius(this.origin, dist, true); e; e = e.chain) - if(e.takedamage == DAMAGE_AIM) - if(this.realowner != e || autocvar_g_nades_napalm_selfdamage) - if(!IS_PLAYER(e) || !this.realowner || DIFF_TEAM(e, this)) - if(!STAT(FROZEN, e)) - { - p = e.origin; - p.x += e.mins.x + random() * (e.maxs.x - e.mins.x); - p.y += e.mins.y + random() * (e.maxs.y - e.mins.y); - p.z += e.mins.z + random() * (e.maxs.z - e.mins.z); - d = vlen(WarpZone_UnTransformOrigin(e, this.origin) - p); - if(d < dist) - { - e.fireball_impactvec = p; - RandomSelection_AddEnt(e, 1 / (1 + d), !StatusEffects_active(STATUSEFFECT_Burning, e)); - } - } - if(RandomSelection_chosen_ent) - { - d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, this.origin) - RandomSelection_chosen_ent.fireball_impactvec); - d = damage + (edgedamage - damage) * (d / dist); - Fire_AddDamage(RandomSelection_chosen_ent, this.realowner, d * burntime, burntime, this.projectiledeathtype); - //trailparticles(this, particleeffectnum(EFFECT_FIREBALL_LASER), this.origin, RandomSelection_chosen_ent.fireball_impactvec); - Send_Effect(EFFECT_FIREBALL_LASER, this.origin, RandomSelection_chosen_ent.fireball_impactvec - this.origin, 1); - } -} - - -void napalm_ball_think(entity this) -{ - if(round_handler_IsActive()) - if(!round_handler_IsRoundStarted()) - { - delete(this); - return; - } - - if(time > this.pushltime) - { - delete(this); - return; - } - - vector midpoint = ((this.absmin + this.absmax) * 0.5); - if(pointcontents(midpoint) == CONTENT_WATER) - { - this.velocity = this.velocity * 0.5; - - if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER) - { this.velocity_z = 200; } - } - - this.angles = vectoangles(this.velocity); - - napalm_damage(this, autocvar_g_nades_napalm_ball_radius,autocvar_g_nades_napalm_ball_damage, - autocvar_g_nades_napalm_ball_damage,autocvar_g_nades_napalm_burntime); - - this.nextthink = time + 0.1; -} - - -void nade_napalm_ball(entity this) -{ - entity proj; - vector kick; - - spamsound(this, CH_SHOTS, SND_FIREBALL_FIRE, VOL_BASE, ATTEN_NORM); - - proj = new(grenade); - proj.owner = this.owner; - proj.realowner = this.realowner; - proj.team = this.owner.team; - proj.bot_dodge = true; - proj.bot_dodgerating = autocvar_g_nades_napalm_ball_damage; - set_movetype(proj, MOVETYPE_BOUNCE); - proj.projectiledeathtype = DEATH_NADE_NAPALM.m_id; - PROJECTILE_MAKETRIGGER(proj); - setmodel(proj, MDL_Null); - proj.scale = 1;//0.5; - setsize(proj, '-4 -4 -4', '4 4 4'); - setorigin(proj, this.origin); - setthink(proj, napalm_ball_think); - proj.nextthink = time; - proj.damageforcescale = autocvar_g_nades_napalm_ball_damageforcescale; - proj.effects = EF_LOWPRECISION | EF_FLAME; - - kick.x =(random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread; - kick.y = (random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread; - kick.z = (random()/2+0.5) * autocvar_g_nades_napalm_ball_spread; - proj.velocity = kick; - - proj.pushltime = time + autocvar_g_nades_napalm_ball_lifetime; - - proj.angles = vectoangles(proj.velocity); - proj.flags = FL_PROJECTILE; - IL_PUSH(g_projectiles, proj); - IL_PUSH(g_bot_dodge, proj); - proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC; - - //CSQCProjectile(proj, true, PROJECTILE_NAPALM_FIRE, true); -} - - -void napalm_fountain_think(entity this) -{ - - if(round_handler_IsActive()) - if(!round_handler_IsRoundStarted()) - { - delete(this); - return; - } - - if(time >= this.ltime) - { - delete(this); - return; - } - - vector midpoint = ((this.absmin + this.absmax) * 0.5); - if(pointcontents(midpoint) == CONTENT_WATER) - { - this.velocity = this.velocity * 0.5; - - if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER) - { this.velocity_z = 200; } - - UpdateCSQCProjectile(this); - } - - napalm_damage(this, autocvar_g_nades_napalm_fountain_radius, autocvar_g_nades_napalm_fountain_damage, - autocvar_g_nades_napalm_fountain_edgedamage, autocvar_g_nades_napalm_burntime); - - this.nextthink = time + 0.1; - if(time >= this.nade_special_time) - { - this.nade_special_time = time + autocvar_g_nades_napalm_fountain_delay; - nade_napalm_ball(this); - } -} - -void nade_napalm_boom(entity this) -{ - for (int c = 0; c < autocvar_g_nades_napalm_ball_count; ++c) - nade_napalm_ball(this); - - entity fountain = new(nade_napalm_fountain); - fountain.owner = this.owner; - fountain.realowner = this.realowner; - fountain.origin = this.origin; - fountain.flags = FL_PROJECTILE; - IL_PUSH(g_projectiles, fountain); - IL_PUSH(g_bot_dodge, fountain); - setorigin(fountain, fountain.origin); - setthink(fountain, napalm_fountain_think); - fountain.nextthink = time; - fountain.ltime = time + autocvar_g_nades_napalm_fountain_lifetime; - fountain.pushltime = fountain.ltime; - fountain.team = this.team; - set_movetype(fountain, MOVETYPE_TOSS); - fountain.projectiledeathtype = DEATH_NADE_NAPALM.m_id; - fountain.bot_dodge = true; - fountain.bot_dodgerating = autocvar_g_nades_napalm_fountain_damage; - fountain.nade_special_time = time; - setsize(fountain, '-16 -16 -16', '16 16 16'); - CSQCProjectile(fountain, true, PROJECTILE_NAPALM_FOUNTAIN, true); -} - -void nade_ice_freeze(entity freezefield, entity frost_target, float freezetime) -{ - frost_target.frozen_by = freezefield.realowner; - Send_Effect(EFFECT_ELECTRO_IMPACT, frost_target.origin, '0 0 0', 1); - Freeze(frost_target, 1 / freezetime, FROZEN_TEMP_DYING, false); - - Drop_Special_Items(frost_target); -} - -void nade_ice_think(entity this) -{ - if(round_handler_IsActive()) - if(!round_handler_IsRoundStarted()) - { - delete(this); - return; - } - - if(time >= this.ltime) - { - if ( autocvar_g_nades_ice_explode ) - { - vector expcol_min = nades_PlayerColor(this.realowner, false); - vector expcol_max = nades_PlayerColor(this.realowner, true); - entity expef = EFFECT_NADE_EXPLODE_NEUTRAL; - Send_Effect_Except(expef, this.origin + '0 0 1', '0 0 0', 1, expcol_min, expcol_max, NULL); - sound(this, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); - - normal_nade_boom(this); - } - delete(this); - return; - } - - - this.nextthink = time + 0.1; - - // gaussian - float randomr; - randomr = random(); - randomr = exp(-5 * randomr * randomr) * autocvar_g_nades_nade_radius; - float randomw; - randomw = random() * (2 * M_PI); - vector randomp; - randomp.x = randomr * cos(randomw); - randomp.y = randomr * sin(randomw); - randomp.z = 1; - Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, this.origin + randomp, '0 0 0', 1); - - if(time >= this.nade_special_time) - { - this.nade_special_time = time + 0.7; - - Send_Effect(EFFECT_ELECTRO_IMPACT, this.origin, '0 0 0', 1); - Send_Effect(EFFECT_ICEFIELD, this.origin, '0 0 0', 1); - } - - - float current_freeze_time = this.ltime - time - 0.1; - - FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_nades_nade_radius, it != this && it.takedamage - && !IS_DEAD(it) && GetResource(it, RES_HEALTH) > 0 && current_freeze_time > 0 - && (!it.revival_time || ((time - it.revival_time) >= 1.5)) && !STAT(FROZEN, it), - { - switch (autocvar_g_nades_ice_teamcheck) - { - case 0: break; // affect everyone - default: - case 2: if(SAME_TEAM(it, this.realowner)) continue; // don't affect teammates - // fall through (check case 1 condition too) - case 1: if(it == this.realowner) continue; // don't affect the player who threw the nade - } - nade_ice_freeze(this, it, current_freeze_time); - }); -} - -void nade_ice_boom(entity this) -{ - entity fountain = new(nade_ice_fountain); - fountain.owner = this.owner; - fountain.realowner = this.realowner; - fountain.origin = this.origin; - setorigin(fountain, fountain.origin); - setthink(fountain, nade_ice_think); - fountain.nextthink = time; - fountain.ltime = time + autocvar_g_nades_ice_freeze_time; - fountain.pushltime = fountain.wait = fountain.ltime; - fountain.team = this.team; - set_movetype(fountain, MOVETYPE_TOSS); - fountain.projectiledeathtype = DEATH_NADE_ICE.m_id; - fountain.bot_dodge = false; - setsize(fountain, '-16 -16 -16', '16 16 16'); - fountain.nade_special_time = time + 0.3; - fountain.angles = this.angles; - - if ( autocvar_g_nades_ice_explode ) - { - setmodel(fountain, MDL_PROJECTILE_GRENADE); - entity timer = new(nade_timer); - setmodel(timer, MDL_NADE_TIMER); - setattachment(timer, fountain, ""); - timer.colormap = this.colormap; - timer.glowmod = this.glowmod; - setthink(timer, nade_timer_think); - timer.nextthink = time; - timer.wait = fountain.ltime; - timer.owner = fountain; - timer.skin = 10; - } - else - setmodel(fountain, MDL_Null); -} - -void nade_translocate_boom(entity this) -{ - if(this.realowner.vehicle) - return; - - setsize(this, PL_MIN_CONST-'16 16 16', PL_MAX_CONST+'16 16 16'); - - if(!move_out_of_solid(this)) - { - sprint(this.realowner, "^1Couldn't move the translocator out of solid! origin: ", vtos(this.origin), "\n"); - return; - } - - vector locout = this.origin + '0 0 1' * (1 - this.realowner.mins.z - 24); - tracebox(locout, this.realowner.mins, this.realowner.maxs, locout, MOVE_NOMONSTERS, this.realowner); - locout = trace_endpos; - - makevectors(this.realowner.angles); - - MUTATOR_CALLHOOK(PortalTeleport, this.realowner); - - TeleportPlayer(this, this.realowner, locout, this.realowner.angles, v_forward * vlen(this.realowner.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER); -} - -void nade_spawn_boom(entity this) -{ - entity player = this.realowner; - entity spawnloc = new(nade_spawn_loc); - setorigin(spawnloc, this.origin); - setsize(spawnloc, player.mins, player.maxs); - set_movetype(spawnloc, MOVETYPE_NONE); - spawnloc.solid = SOLID_NOT; - spawnloc.drawonlytoclient = player; - spawnloc.effects = EF_STARDUST; - spawnloc.cnt = autocvar_g_nades_spawn_count; - - if(player.nade_spawnloc) - delete(player.nade_spawnloc); - - player.nade_spawnloc = spawnloc; -} - -void nades_orb_think(entity this) -{ - if(time >= this.ltime) - { - delete(this); - return; - } - - this.nextthink = time; - - if(time >= this.nade_special_time) - { - this.nade_special_time = time+0.25; - this.nade_show_particles = 1; - } - else - this.nade_show_particles = 0; -} - -entity nades_spawn_orb(entity own, entity realown, vector org, float orb_ltime, float orb_rad) -{ - // NOTE: this function merely places an orb - // you must add a custom touch function to the returned entity if desired - // also set .colormod if you wish to have it colorized - entity orb = new(nades_spawn_orb); - orb.owner = own; - orb.realowner = realown; - setorigin(orb, org); - - orb.orb_lifetime = orb_ltime; // required for timers - orb.ltime = time + orb.orb_lifetime; - orb.bot_dodge = false; - orb.team = realown.team; - orb.solid = SOLID_TRIGGER; - - setmodel(orb, MDL_NADE_ORB); - orb.skin = 1; - orb.orb_radius = orb_rad; // required for fading - vector size = '1 1 1' * orb.orb_radius / 2; - setsize(orb, -size, size); - - Net_LinkEntity(orb, true, 0, orb_send); - orb.SendFlags |= 1; - - setthink(orb, nades_orb_think); - orb.nextthink = time; - - return orb; -} - -void nade_entrap_touch(entity this, entity toucher) -{ - if(DIFF_TEAM(toucher, this.realowner)) // TODO: what if realowner changes team or disconnects? - { - if (!isPushable(toucher)) - return; - - float pushdeltatime = time - toucher.lastpushtime; - if (pushdeltatime > 0.15) pushdeltatime = 0; - toucher.lastpushtime = time; - if(!pushdeltatime) return; - - // div0: ticrate independent, 1 = identity (not 20) - toucher.velocity = toucher.velocity * (autocvar_g_nades_entrap_strength ** pushdeltatime); - - #ifdef SVQC - UpdateCSQCProjectile(toucher); - #endif - } - - if ( IS_REAL_CLIENT(toucher) || (IS_VEHICLE(toucher) && toucher.owner) ) - { - entity show_tint = (IS_VEHICLE(toucher) && toucher.owner) ? toucher.owner : toucher; - show_tint.nade_entrap_time = time + 0.1; - } -} - -void nade_entrap_boom(entity this) -{ - entity orb = nades_spawn_orb(this.owner, this.realowner, this.origin, autocvar_g_nades_entrap_time, autocvar_g_nades_entrap_radius); - - settouch(orb, nade_entrap_touch); - orb.colormod = NADE_TYPE_ENTRAP.m_color; -} - -void nade_heal_touch(entity this, entity toucher) -{ - float maxhealth; - float health_factor; - - if(IS_PLAYER(toucher) || IS_MONSTER(toucher) || IS_VEHICLE(toucher)) - if(!IS_DEAD(toucher)) - if(!STAT(FROZEN, toucher)) - { - health_factor = autocvar_g_nades_heal_rate*frametime/2; - if ( toucher != this.realowner ) - health_factor *= (SAME_TEAM(toucher,this)) ? autocvar_g_nades_heal_friend : autocvar_g_nades_heal_foe; - - if ( health_factor > 0 ) - { - maxhealth = (IS_MONSTER(toucher)) ? toucher.max_health : g_pickup_healthmega_max; - float hp = GetResource(toucher, RES_HEALTH); - if (hp < maxhealth) - { - if (this.nade_show_particles) - Send_Effect(EFFECT_HEALING, toucher.origin, '0 0 0', 1); - - GiveResourceWithLimit(toucher, RES_HEALTH, health_factor, maxhealth); - } - } - else if ( health_factor < 0 ) - Damage(toucher,this,this.realowner,-health_factor,DEATH_NADE_HEAL.m_id,DMG_NOWEP,toucher.origin,'0 0 0'); - } -} - -void nade_heal_boom(entity this) -{ - entity orb = nades_spawn_orb(this.owner, this.realowner, this.origin, autocvar_g_nades_heal_time, autocvar_g_nades_nade_radius); - - settouch(orb, nade_heal_touch); - orb.colormod = '1 0 0'; -} - -void nade_monster_boom(entity this) -{ - if(!autocvar_g_monsters) - return; - entity e = spawn(); - e.noalign = true; // don't drop to floor - e = spawnmonster(e, this.pokenade_type, MON_Null, this.realowner, this.realowner, this.origin, false, false, 1); - if(!e) - return; // monster failed to be spawned - - if(autocvar_g_nades_pokenade_monster_lifetime > 0) - e.monster_lifetime = time + autocvar_g_nades_pokenade_monster_lifetime; - e.monster_skill = MONSTER_SKILL_INSANE; -} - -void nade_veil_touch(entity this, entity toucher) -{ - if ( IS_REAL_CLIENT(toucher) || (IS_VEHICLE(toucher) && toucher.owner) ) - { - entity show_tint = (IS_VEHICLE(toucher) && toucher.owner) ? toucher.owner : toucher; - - float tint_alpha = 0.75; - if(SAME_TEAM(toucher, this.realowner)) - { - tint_alpha = 0.45; - if(!show_tint.nade_veil_time) - { - toucher.nade_veil_prevalpha = toucher.alpha; - toucher.alpha = -1; - } - } - show_tint.nade_veil_time = time + 0.1; - } -} - -void nade_veil_boom(entity this) -{ - entity orb = nades_spawn_orb(this.owner, this.realowner, this.origin, autocvar_g_nades_veil_time, autocvar_g_nades_veil_radius); - - settouch(orb, nade_veil_touch); - orb.colormod = NADE_TYPE_VEIL.m_color; -} - -void nade_ammo_touch(entity this, entity toucher) -{ - float maxammo = 999; - float ammo_factor; - float amshells = GetResource(toucher, RES_SHELLS); - float ambullets = GetResource(toucher, RES_BULLETS); - float amrockets = GetResource(toucher, RES_ROCKETS); - float amcells = GetResource(toucher, RES_CELLS); - if(IS_PLAYER(toucher) || IS_MONSTER(toucher)) - if(!IS_DEAD(toucher)) - if(!STAT(FROZEN, toucher)) - { - ammo_factor = autocvar_g_nades_ammo_rate*frametime/2; - if ( toucher != this.realowner ) - ammo_factor *= (SAME_TEAM(toucher, this)) ? autocvar_g_nades_ammo_friend : autocvar_g_nades_ammo_foe; - -#define CHECK_AMMO_RESOURCE_LIMIT(amresource, res_resource) \ - if (amresource < maxammo) \ - GiveResourceWithLimit(toucher, res_resource, ammo_factor, maxammo); - -#define DROP_AMMO_RESOURCE(amresource, res_resource) \ - if (amresource > 0) \ - SetResource(toucher, res_resource, amresource + ammo_factor); - - if ( ammo_factor > 0 ) - { - CHECK_AMMO_RESOURCE_LIMIT(amshells, RES_SHELLS); - CHECK_AMMO_RESOURCE_LIMIT(ambullets, RES_BULLETS); - CHECK_AMMO_RESOURCE_LIMIT(amrockets, RES_ROCKETS); - CHECK_AMMO_RESOURCE_LIMIT(amcells, RES_CELLS); - - if (this.nade_show_particles) - Send_Effect(EFFECT_HEALING, toucher.origin, '0 0 0', 1); - } - else if ( ammo_factor < 0 ) - { - //Foe drops ammo points - DROP_AMMO_RESOURCE(amshells, RES_SHELLS); - DROP_AMMO_RESOURCE(ambullets, RES_BULLETS); - DROP_AMMO_RESOURCE(amrockets, RES_ROCKETS); - DROP_AMMO_RESOURCE(amcells, RES_CELLS); - - return; - } - } -#undef CHECK_AMMO_RESOURCE_LIMIT -#undef DROP_AMMO_RESOURCE - - if ( IS_REAL_CLIENT(toucher) || (IS_VEHICLE(toucher) && toucher.owner) ) - { - entity show_tint = (IS_VEHICLE(toucher) && toucher.owner) ? toucher.owner : toucher; - show_tint.nade_ammo_time = time + 0.1; - } -} - -void nade_ammo_boom(entity this) -{ - entity orb = nades_spawn_orb(this.owner, this.realowner, this.origin, autocvar_g_nades_ammo_time, autocvar_g_nades_nade_radius); - - settouch(orb, nade_ammo_touch); - orb.colormod = '0.66 0.33 0'; -} - -void nade_darkness_think(entity this) -{ - if(round_handler_IsActive()) - if(!round_handler_IsRoundStarted()) - { - delete(this); - return; - } - - if(time >= this.ltime) - { - if ( autocvar_g_nades_darkness_explode ) - { - vector expcol_min = nades_PlayerColor(this.realowner, false); - vector expcol_max = nades_PlayerColor(this.realowner, true); - entity expef = EFFECT_NADE_EXPLODE_NEUTRAL; - Send_Effect_Except(expef, this.origin + '0 0 1', '0 0 0', 1, expcol_min, expcol_max, NULL); - sound(this, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); - - normal_nade_boom(this); - } - else - Send_Effect(EFFECT_SPAWN_PURPLE, this.origin + '0 0 1', '0 0 0', 1); - - delete(this); - return; - } - - this.nextthink = time + 0.1; - - // gaussian - float randomr; - randomr = random(); - randomr = exp(-5 * randomr * randomr) * autocvar_g_nades_nade_radius; - float randomw; - randomw = random() * (2 * M_PI); - vector randomp; - randomp.x = randomr * cos(randomw); - randomp.y = randomr * sin(randomw); - randomp.z = 1; - Send_Effect(EFFECT_DARKFIELD, this.origin + randomp, '0 0 0', 1); - - if(time >= this.nade_special_time) - { - this.nade_special_time = time + 0.7; - Send_Effect(EFFECT_DARKFIELD, this.origin, '0 0 0', 1); - } - - - float current_dark_time = this.ltime - time - 0.1; - - FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_nades_nade_radius, it != this && it.takedamage - && !IS_DEAD(it) && GetResource(it, RES_HEALTH) > 0 && current_dark_time > 0 && IS_REAL_CLIENT(it), - { - switch (autocvar_g_nades_darkness_teamcheck) - { - case 0: break; // affect everyone - default: - case 2: if(SAME_TEAM(it, this.realowner)) continue; // don't affect teammates - // fall through (check case 1 condition too) - case 1: if(it == this.realowner) continue; // don't affect the player who threw the nade - } - STAT(NADE_DARKNESS_TIME, it) = time + 0.1; - }); -} - -void nade_darkness_boom(entity this) -{ - entity fountain = new(nade_darkness_fountain); - fountain.owner = this.owner; - fountain.realowner = this.realowner; - fountain.origin = this.origin; - setorigin(fountain, fountain.origin); - setthink(fountain, nade_darkness_think); - fountain.nextthink = time; - fountain.ltime = time + autocvar_g_nades_darkness_time; - fountain.pushltime = fountain.wait = fountain.ltime; - fountain.team = this.team; - set_movetype(fountain, MOVETYPE_TOSS); - fountain.projectiledeathtype = DEATH_NADE.m_id; - fountain.bot_dodge = false; - setsize(fountain, '-16 -16 -16', '16 16 16'); - fountain.nade_special_time = time + 0.3; - fountain.angles = this.angles; - - if ( autocvar_g_nades_darkness_explode ) - { - setmodel(fountain, MDL_PROJECTILE_GRENADE); - entity timer = new(nade_timer); - setmodel(timer, MDL_NADE_TIMER); - setattachment(timer, fountain, ""); - timer.colormap = this.colormap; - timer.glowmod = this.glowmod; - setthink(timer, nade_timer_think); - timer.nextthink = time; - timer.wait = fountain.ltime; - timer.owner = fountain; - timer.skin = 10; - } - else - setmodel(fountain, MDL_Null); -} - -void nade_boom(entity this) -{ - entity expef = NULL; - bool nade_blast = true; - vector expcol_min = '0 0 0', expcol_max = '0 0 0'; - -#define SET_NADE_EFFECT(nade_type, blast, exp_effect, exp_color_min, exp_color_max) \ - case nade_type: \ - nade_blast = blast; \ - expef = exp_effect; \ - expcol_min = exp_color_min; \ - expcol_max = exp_color_max; \ - break - - switch ( REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, this)) ) - { - SET_NADE_EFFECT(NADE_TYPE_NAPALM, autocvar_g_nades_napalm_blast, EFFECT_EXPLOSION_MEDIUM, '0 0 0', '0 0 0'); - SET_NADE_EFFECT(NADE_TYPE_ICE, false, EFFECT_ELECTRO_COMBO, '0 0 0', '0 0 0'); - SET_NADE_EFFECT(NADE_TYPE_TRANSLOCATE, false, NULL, '0 0 0', '0 0 0'); - SET_NADE_EFFECT(NADE_TYPE_MONSTER, true, (!autocvar_g_monsters) ? EFFECT_NADE_EXPLODE_NEUTRAL : NULL, nades_PlayerColor(this.realowner, false), nades_PlayerColor(this.realowner, true)); - SET_NADE_EFFECT(NADE_TYPE_SPAWN, false, EFFECT_SPAWN_NEUTRAL, nades_PlayerColor(this.realowner, false), nades_PlayerColor(this.realowner, true)); - SET_NADE_EFFECT(NADE_TYPE_HEAL, false, EFFECT_SPAWN_NEUTRAL, '1 0 0', '1 0 0'); - SET_NADE_EFFECT(NADE_TYPE_ENTRAP, false, EFFECT_SPAWN_NEUTRAL, '1 1 0', '1 1 0'); - SET_NADE_EFFECT(NADE_TYPE_VEIL, false, EFFECT_SPAWN_NEUTRAL, '0 0 0', '0 0 0'); - SET_NADE_EFFECT(NADE_TYPE_AMMO, false, EFFECT_SPAWN_NEUTRAL, '0.66 0.33 0', '0.66 0.33 0'); - SET_NADE_EFFECT(NADE_TYPE_DARKNESS, false, EFFECT_EXPLOSION_MEDIUM, '0 0 0', '0 0 0'); - SET_NADE_EFFECT(NADE_TYPE_NORMAL, true, EFFECT_NADE_EXPLODE_NEUTRAL, nades_PlayerColor(this.realowner, false), nades_PlayerColor(this.realowner, true)); - default: expef = EFFECT_NADE_EXPLODE_NEUTRAL; expcol_min = nades_PlayerColor(this.realowner, false); expcol_max = nades_PlayerColor(this.realowner, true); break; - } -#undef SET_NADE_EFFECT - - if(expef) - Send_Effect_Except(expef, findbetterlocation(this.origin, 8), '0 0 0', 1, expcol_min, expcol_max, NULL); - - sound(this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM); - sound(this, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); - - this.event_damage = func_null; // prevent somehow calling damage in the next call - - if(nade_blast) - normal_nade_boom(this); - - if(this.takedamage) - switch ( REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, this)) ) - { - case NADE_TYPE_NAPALM: nade_napalm_boom(this); break; - case NADE_TYPE_ICE: nade_ice_boom(this); break; - case NADE_TYPE_TRANSLOCATE: nade_translocate_boom(this); break; - case NADE_TYPE_SPAWN: nade_spawn_boom(this); break; - case NADE_TYPE_HEAL: nade_heal_boom(this); break; - case NADE_TYPE_MONSTER: nade_monster_boom(this); break; - case NADE_TYPE_ENTRAP: nade_entrap_boom(this); break; - case NADE_TYPE_VEIL: nade_veil_boom(this); break; - case NADE_TYPE_AMMO: nade_ammo_boom(this); break; - case NADE_TYPE_DARKNESS: nade_darkness_boom(this); break; - } - - IL_EACH(g_projectiles, it.classname == "grapplinghook" && it.aiment == this, - { - RemoveHook(it); - }); - - delete(this); -} - -void spawn_held_nade(entity player, entity nowner, float ntime, int ntype, string pntype); -void nade_pickup(entity this, entity thenade) -{ - spawn_held_nade(this, thenade.realowner, autocvar_g_nades_pickup_time, STAT(NADE_BONUS_TYPE, thenade), thenade.pokenade_type); - - // set refire so player can't even - this.nade_refire = time + autocvar_g_nades_nade_refire; - STAT(NADE_TIMER, this) = 0; - - if(this.nade) - this.nade.nade_time_primed = thenade.nade_time_primed; -} - -bool CanThrowNade(entity this); -void nade_touch(entity this, entity toucher) -{ - if(toucher) - UpdateCSQCProjectile(this); - - if(toucher == this.realowner) - return; // no this impacts - - if(autocvar_g_nades_pickup) - if(time >= this.spawnshieldtime) - if(!toucher.nade && GetResource(this, RES_HEALTH) == this.max_health) // no boosted shot pickups, thank you very much - if(CanThrowNade(toucher)) // prevent some obvious things, like dead players - if(IS_REAL_CLIENT(toucher)) // above checks for IS_PLAYER, don't need to do it here - { - nade_pickup(toucher, this); - sound(this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX)); - delete(this); - return; - } - /*float is_weapclip = 0; - if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW) - if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)) - if (!(trace_dphitcontents & DPCONTENTS_OPAQUE)) - is_weapclip = 1;*/ - if(ITEM_TOUCH_NEEDKILL()) // || is_weapclip) - { - IL_EACH(g_projectiles, it.classname == "grapplinghook" && it.aiment == this, - { - RemoveHook(it); - }); - delete(this); - return; - } - - PROJECTILE_TOUCH(this, toucher); - - //setsize(this, '-2 -2 -2', '2 2 2'); - //UpdateCSQCProjectile(this); - if(GetResource(this, RES_HEALTH) == this.max_health) - { - spamsound(this, CH_SHOTS, SND_GRENADE_BOUNCE_RANDOM(), VOL_BASE, ATTEN_NORM); - return; - } - - this.enemy = toucher; - nade_boom(this); -} - -void nade_beep(entity this) -{ - sound(this, CH_SHOTS_SINGLE, SND_NADE_BEEP, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX)); - setthink(this, nade_boom); - this.nextthink = max(this.wait, time); -} - -void nade_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) -{ - if(ITEM_DAMAGE_NEEDKILL(deathtype)) - { - this.takedamage = DAMAGE_NO; - nade_boom(this); - return; - } - - if(STAT(NADE_BONUS_TYPE, this) == NADE_TYPE_TRANSLOCATE.m_id || STAT(NADE_BONUS_TYPE, this) == NADE_TYPE_SPAWN.m_id) - return; - - if (MUTATOR_CALLHOOK(Nade_Damage, this, DEATH_WEAPONOF(deathtype), force, damage)) {} - else if(DEATH_ISWEAPON(deathtype, WEP_BLASTER)) - { - force *= 1.5; - damage = 0; - } - else if(DEATH_ISWEAPON(deathtype, WEP_VORTEX) || DEATH_ISWEAPON(deathtype, WEP_VAPORIZER) || DEATH_ISWEAPON(deathtype, WEP_OVERKILL_NEX)) - { - force *= 6; - damage = this.max_health * 0.55; - } - else if(DEATH_ISWEAPON(deathtype, WEP_MACHINEGUN) || DEATH_ISWEAPON(deathtype, WEP_OVERKILL_MACHINEGUN)) - damage = this.max_health * 0.1; - else if(DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN) || DEATH_ISWEAPON(deathtype, WEP_OVERKILL_SHOTGUN)) // WEAPONTODO - { - if(!(deathtype & HITTYPE_SECONDARY)) - damage = this.max_health * 1.15; - } - - // melee slaps - entity death_weapon = DEATH_WEAPONOF(deathtype); - if(((deathtype & HITTYPE_SECONDARY) ? (death_weapon.spawnflags & WEP_TYPE_MELEE_SEC) : (death_weapon.spawnflags & WEP_TYPE_MELEE_PRI))) - { - damage = this.max_health * 0.1; - force *= 10; - } - - this.velocity += force; - UpdateCSQCProjectile(this); - - if(damage <= 0 || ((IS_ONGROUND(this)) && IS_PLAYER(attacker))) - return; - - float hp = GetResource(this, RES_HEALTH); - if(hp == this.max_health) - { - sound(this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX)); - this.nextthink = max(time + this.nade_lifetime, time); - setthink(this, nade_beep); - } - - hp -= damage; - SetResource(this, RES_HEALTH, hp); - - if(STAT(NADE_BONUS_TYPE, this) != NADE_TYPE_TRANSLOCATE.m_id && STAT(NADE_BONUS_TYPE, this) != NADE_TYPE_SPAWN.m_id) - if(STAT(NADE_BONUS_TYPE, this) != NADE_TYPE_HEAL.m_id || IS_PLAYER(attacker)) - this.realowner = attacker; - - if(hp <= 0) - { - if(autocvar_g_nades_spawn_destroy_damage > 0 && STAT(NADE_BONUS_TYPE, this) == NADE_TYPE_SPAWN.m_id) - Damage(this.realowner, attacker, attacker, autocvar_g_nades_spawn_destroy_damage, DEATH_TOUCHEXPLODE.m_id, DMG_NOWEP, this.realowner.origin, '0 0 0'); - - if(autocvar_g_nades_translocate_destroy_damage > 0 && STAT(NADE_BONUS_TYPE, this) == NADE_TYPE_TRANSLOCATE.m_id) - { - Damage(this.realowner, attacker, attacker, autocvar_g_nades_translocate_destroy_damage, DEATH_TOUCHEXPLODE.m_id, DMG_NOWEP, this.realowner.origin, '0 0 0'); - W_PrepareExplosionByDamage(this, this.realowner, nade_boom); // Don't change the owner - - return; - } - - W_PrepareExplosionByDamage(this, attacker, nade_boom); - } - else - nade_burn_spawn(this); -} - -void toss_nade(entity e, bool set_owner, vector _velocity, float _time) -{ - if(e.nade == NULL) - return; - - entity _nade = e.nade; - e.nade = NULL; - - if(e.fake_nade) - delete(e.fake_nade); - e.fake_nade = NULL; - - Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER, CPID_NADES); - - makevectors(e.v_angle); - - // NOTE: always throw from first weapon entity? - W_SetupShot(e, _nade.weaponentity_fld, false, false, SND_Null, CH_WEAPON_A, 0, DEATH_NADE.m_id); - - vector offset = (v_forward * autocvar_g_nades_throw_offset.x) - + (v_right * autocvar_g_nades_throw_offset.y) - + (v_up * autocvar_g_nades_throw_offset.z); - - setorigin(_nade, w_shotorg + offset); - //setmodel(_nade, MDL_PROJECTILE_NADE); - //setattachment(_nade, NULL, ""); - PROJECTILE_MAKETRIGGER(_nade); - if(STAT(NADES_SMALL, e)) - setsize(_nade, '-8 -8 -8', '8 8 8'); - else - setsize(_nade, '-16 -16 -16', '16 16 16'); - set_movetype(_nade, MOVETYPE_BOUNCE); - - tracebox(_nade.origin, _nade.mins, _nade.maxs, _nade.origin, MOVE_NOMONSTERS, _nade); - if (trace_startsolid) - setorigin(_nade, e.origin); - - if(e.v_angle.x >= 70 && e.v_angle.x <= 110 && PHYS_INPUT_BUTTON_CROUCH(e)) - _nade.velocity = '0 0 100'; - else if(autocvar_g_nades_nade_newton_style == 1) - _nade.velocity = e.velocity + _velocity; - else if(autocvar_g_nades_nade_newton_style == 2) - _nade.velocity = _velocity; - else - _nade.velocity = W_CalculateProjectileVelocity(e, e.velocity, _velocity, true); - - if(set_owner) - _nade.realowner = e; - - settouch(_nade, nade_touch); - _nade.spawnshieldtime = time + 0.1; // prevent instantly picking up again - SetResource(_nade, RES_HEALTH, autocvar_g_nades_nade_health); - _nade.max_health = GetResource(_nade, RES_HEALTH); - _nade.takedamage = DAMAGE_AIM; - _nade.event_damage = nade_damage; - setcefc(_nade, func_null); - _nade.exteriormodeltoclient = NULL; - _nade.traileffectnum = 0; - _nade.teleportable = true; - _nade.pushable = true; - _nade.gravity = 1; - _nade.missile_flags = MIF_SPLASH | MIF_ARC; - _nade.damagedbycontents = true; - IL_PUSH(g_damagedbycontents, _nade); - _nade.angles = vectoangles(_nade.velocity); - _nade.flags = FL_PROJECTILE; - IL_PUSH(g_projectiles, _nade); - IL_PUSH(g_bot_dodge, _nade); - _nade.projectiledeathtype = DEATH_NADE.m_id; - _nade.toss_time = time; - _nade.solid = SOLID_CORPSE; //((STAT(NADE_BONUS_TYPE, _nade) == NADE_TYPE_TRANSLOCATE) ? SOLID_CORPSE : SOLID_BBOX); - - if(STAT(NADE_BONUS_TYPE, _nade) == NADE_TYPE_TRANSLOCATE.m_id || STAT(NADE_BONUS_TYPE, _nade) == NADE_TYPE_SPAWN.m_id) - _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; - else - _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY; - - nade_spawn(_nade); - - if(_time) - { - setthink(_nade, nade_boom); - _nade.nextthink = _time; - } - - e.nade_refire = time + autocvar_g_nades_nade_refire; - STAT(NADE_TIMER, e) = 0; -} - -void nades_GiveBonus(entity player, float score) -{ - if (autocvar_g_nades) - if (autocvar_g_nades_bonus) - if (IS_REAL_CLIENT(player)) - if (IS_PLAYER(player) && STAT(NADE_BONUS, player) < autocvar_g_nades_bonus_max) - if (!STAT(FROZEN, player)) - if (!IS_DEAD(player)) - { - if ( STAT(NADE_BONUS_SCORE, player) < 1 ) - STAT(NADE_BONUS_SCORE, player) += score/autocvar_g_nades_bonus_score_max; - - if ( STAT(NADE_BONUS_SCORE, player) >= 1 ) - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_NADE_BONUS); - play2(player, SND(NADE_BONUS)); - STAT(NADE_BONUS, player)++; - STAT(NADE_BONUS_SCORE, player) -= 1; - } - } -} - -/** Remove all bonus nades from a player */ -void nades_RemoveBonus(entity player) -{ - STAT(NADE_BONUS, player) = STAT(NADE_BONUS_SCORE, player) = 0; -} - -MUTATOR_HOOKFUNCTION(nades, PutClientInServer) -{ - entity player = M_ARGV(0, entity); - - nades_RemoveBonus(player); -} - -bool nade_customize(entity this, entity client) -{ - //if(IS_SPEC(client)) { return false; } - if(client == this.exteriormodeltoclient || (IS_SPEC(client) && client.enemy == this.exteriormodeltoclient)) - { - // somewhat hide the model, but keep the glow - //this.effects = 0; - if(this.traileffectnum) - this.traileffectnum = 0; - this.alpha = -1; - } - else - { - //this.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; - if(!this.traileffectnum) - { - entity nade = REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, this)); - this.traileffectnum = _particleeffectnum(Nade_TrailEffect(nade.m_projectile[0], this.team).eent_eff_name); - } - this.alpha = 1; - } - - return true; -} - -void spawn_held_nade(entity player, entity nowner, float ntime, int ntype, string pntype) -{ - entity n = new(nade), fn = new(fake_nade); - - n.pokenade_type = pntype; - - if(ntype == 0) // random nade - ntype = floor(random() * (REGISTRY_COUNT(Nades) - 1)) + 1; - Nade def = REGISTRY_GET(Nades, ntype); - if(def == NADE_TYPE_Null) - def = NADE_TYPE_NORMAL; - - STAT(NADE_BONUS_TYPE, n) = def.m_id; - - .entity weaponentity = weaponentities[0]; // TODO: unhardcode - - setmodel(n, MDL_PROJECTILE_NADE); - //setattachment(n, player, "bip01 l hand"); - n.exteriormodeltoclient = player; - setcefc(n, nade_customize); - n.traileffectnum = _particleeffectnum(Nade_TrailEffect(def.m_projectile[0], player.team).eent_eff_name); - n.colormod = def.m_color; - n.realowner = nowner; - n.colormap = player.colormap; - n.glowmod = player.glowmod; - n.wait = time + max(0, ntime); - n.nade_time_primed = time; - setthink(n, nade_beep); - n.nextthink = max(n.wait - 3, time); - n.projectiledeathtype = DEATH_NADE.m_id; - n.weaponentity_fld = weaponentity; - n.nade_lifetime = ntime; - n.alpha = def.m_alpha; - - setmodel(fn, MDL_NADE_VIEW); - //setattachment(fn, player.(weaponentity), ""); - fn.viewmodelforclient = player; - fn.realowner = fn.owner = player; - fn.colormod = def.m_color; - fn.colormap = player.colormap; - fn.glowmod = player.glowmod; - setthink(fn, SUB_Remove); - fn.nextthink = n.wait; - fn.weaponentity_fld = weaponentity; - fn.alpha = def.m_alpha; - - player.nade = n; - player.fake_nade = fn; -} - -void nade_prime(entity this) -{ - if(autocvar_g_nades_bonus_only && !STAT(NADE_BONUS, this)) - return; // only allow bonus nades - - // TODO: handle old nade if it exists? - if(this.nade) - delete(this.nade); - this.nade = NULL; - - if(this.fake_nade) - delete(this.fake_nade); - this.fake_nade = NULL; - - int ntype; - string pntype = this.pokenade_type; - - if(StatusEffects_active(STATUSEFFECT_Strength, this) && autocvar_g_nades_bonus_onstrength) - ntype = STAT(NADE_BONUS_TYPE, this); - else if (STAT(NADE_BONUS, this) >= 1) - { - ntype = STAT(NADE_BONUS_TYPE, this); - pntype = this.pokenade_type; - STAT(NADE_BONUS, this) -= 1; - } - else - { - ntype = ((autocvar_g_nades_client_select) ? CS_CVAR(this).cvar_cl_nade_type : autocvar_g_nades_nade_type); - pntype = ((autocvar_g_nades_client_select) ? CS_CVAR(this).cvar_cl_pokenade_type : autocvar_g_nades_pokenade_monster_type); - } - - spawn_held_nade(this, this, autocvar_g_nades_nade_lifetime, ntype, pntype); -} - -bool CanThrowNade(entity this) -{ - return !(this.vehicle || !autocvar_g_nades || IS_DEAD(this) || !IS_PLAYER(this) || weaponLocked(this)); -} - -.bool nade_altbutton; - -void nades_CheckThrow(entity this) -{ - if(!CanThrowNade(this)) - return; - - entity held_nade = this.nade; - if (!held_nade) - { - this.nade_altbutton = true; - if(time > this.nade_refire) - { - nade_prime(this); - this.nade_refire = time + autocvar_g_nades_nade_refire; - } - } - else - { - this.nade_altbutton = false; - if (time >= held_nade.nade_time_primed + 1) { - makevectors(this.v_angle); - float _force = time - held_nade.nade_time_primed; - _force /= autocvar_g_nades_nade_lifetime; - _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce)); - vector dir = (v_forward * 0.75 + v_up * 0.2 + v_right * 0.05); - dir = W_CalculateSpread(dir, autocvar_g_nades_spread, autocvar_g_weaponspreadfactor, autocvar_g_projectiles_spread_style); - toss_nade(this, true, dir * _force, 0); - } - } -} - -void nades_Clear(entity player) -{ - if(player.nade) - delete(player.nade); - if(player.fake_nade) - delete(player.fake_nade); - - player.nade = player.fake_nade = NULL; - STAT(NADE_TIMER, player) = 0; -} - -int nades_CheckTypes(entity player, int cl_ntype) -{ - // TODO check what happens without this patch -#define CL_NADE_TYPE_CHECK(nade_ent, nade_cvar) \ - case nade_ent.m_id: if (nade_cvar) return cl_ntype - - switch (cl_ntype) - { - case 0: return 0; // random nade - CL_NADE_TYPE_CHECK(NADE_TYPE_NAPALM, autocvar_g_nades_napalm); - CL_NADE_TYPE_CHECK(NADE_TYPE_ICE, autocvar_g_nades_ice); - CL_NADE_TYPE_CHECK(NADE_TYPE_TRANSLOCATE, autocvar_g_nades_translocate); - CL_NADE_TYPE_CHECK(NADE_TYPE_SPAWN, autocvar_g_nades_spawn); - CL_NADE_TYPE_CHECK(NADE_TYPE_HEAL, autocvar_g_nades_heal); - CL_NADE_TYPE_CHECK(NADE_TYPE_MONSTER, autocvar_g_nades_pokenade); - CL_NADE_TYPE_CHECK(NADE_TYPE_ENTRAP, autocvar_g_nades_entrap); - CL_NADE_TYPE_CHECK(NADE_TYPE_VEIL, autocvar_g_nades_veil); - CL_NADE_TYPE_CHECK(NADE_TYPE_AMMO, autocvar_g_nades_ammo); - CL_NADE_TYPE_CHECK(NADE_TYPE_DARKNESS, autocvar_g_nades_darkness); - } - return NADE_TYPE_NORMAL.m_id; // default to NADE_TYPE_NORMAL for unknown nade types -#undef CL_NADE_TYPE_CHECK -} - -MUTATOR_HOOKFUNCTION(nades, VehicleEnter) -{ - entity player = M_ARGV(0, entity); - - if(player.nade) - toss_nade(player, true, '0 0 100', max(player.nade.wait, time + 0.05)); -} - -CLASS(NadeOffhand, OffhandWeapon) - METHOD(NadeOffhand, offhand_think, void(NadeOffhand this, entity player, bool key_pressed)) - { - entity held_nade = player.nade; - - if (!CanThrowNade(player)) return; - if (!(time > player.nade_refire)) return; - if (key_pressed) { - if (!held_nade) { - nade_prime(player); - held_nade = player.nade; - } - } else if (time >= held_nade.nade_time_primed + 1) { - if (held_nade) { - makevectors(player.v_angle); - float _force = time - held_nade.nade_time_primed; - _force /= autocvar_g_nades_nade_lifetime; - _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce)); - vector dir = (v_forward * 0.7 + v_up * 0.2 + v_right * 0.1); - dir = W_CalculateSpread(dir, autocvar_g_nades_spread, autocvar_g_weaponspreadfactor, autocvar_g_projectiles_spread_style); - toss_nade(player, false, dir * _force, 0); - } - } - } -ENDCLASS(NadeOffhand) -NadeOffhand OFFHAND_NADE; -REGISTER_MUTATOR(nades, autocvar_g_nades) -{ - MUTATOR_ONADD - { - OFFHAND_NADE = NEW(NadeOffhand); - } - return 0; -} - -MUTATOR_HOOKFUNCTION(nades, ForbidThrowCurrentWeapon, CBC_ORDER_LAST) -{ - entity player = M_ARGV(0, entity); - - if (player.offhand != OFFHAND_NADE || (STAT(WEAPONS, player) & WEPSET(HOOK)) || autocvar_g_nades_override_dropweapon) { - nades_CheckThrow(player); - return true; - } -} - -#ifdef IN_REVIVING_RANGE - #undef IN_REVIVING_RANGE -#endif - -// returns true if player is reviving it -#define IN_REVIVING_RANGE(player, it, revive_extra_size) \ - (it != player && !IS_DEAD(it) && SAME_TEAM(it, player) \ - && boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax)) - -MUTATOR_HOOKFUNCTION(nades, PlayerPreThink) -{ - entity player = M_ARGV(0, entity); - - if (!IS_PLAYER(player)) { return; } - - if (player.nade && (player.offhand != OFFHAND_NADE || (STAT(WEAPONS, player) & WEPSET(HOOK)))) - OFFHAND_NADE.offhand_think(OFFHAND_NADE, player, player.nade_altbutton); - - entity held_nade = player.nade; - if (held_nade) - { - STAT(NADE_TIMER, player) = bound(0, (time - held_nade.nade_time_primed) / held_nade.nade_lifetime, 1); - // LOG_TRACEF("%d %d", STAT(NADE_TIMER, player), time - held_nade.nade_time_primed); - makevectors(player.angles); - held_nade.velocity = player.velocity; - setorigin(held_nade, player.origin + player.view_ofs + v_forward * 8 + v_right * -8 + v_up * 0); - held_nade.angles_y = player.angles.y; - - if (time + 0.1 >= held_nade.wait) - { - toss_nade(player, false, '0 0 0', time + 0.05); - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_NADE_THROW); - } - } - - if(IS_PLAYER(player)) - { - if ( autocvar_g_nades_bonus && autocvar_g_nades ) - { - entity key; - float key_count = 0; - FOR_EACH_KH_KEY(key) if(key.owner == player) { ++key_count; } - - float time_score; - if(GameRules_scoring_is_vip(player)) - time_score = autocvar_g_nades_bonus_score_time_flagcarrier; - else - time_score = autocvar_g_nades_bonus_score_time; - - if(key_count) - time_score = autocvar_g_nades_bonus_score_time_flagcarrier * key_count; // multiply by the number of keys the player is holding - - if(autocvar_g_nades_bonus_client_select) - { - STAT(NADE_BONUS_TYPE, player) = nades_CheckTypes(player, CS_CVAR(player).cvar_cl_nade_type); - player.pokenade_type = CS_CVAR(player).cvar_cl_pokenade_type; - } - else - { - STAT(NADE_BONUS_TYPE, player) = autocvar_g_nades_bonus_type; - player.pokenade_type = autocvar_g_nades_pokenade_monster_type; - } - - STAT(NADE_BONUS_TYPE, player) = bound(0, STAT(NADE_BONUS_TYPE, player), REGISTRY_COUNT(Nades)); - - if(STAT(NADE_BONUS_SCORE, player) >= 0 && autocvar_g_nades_bonus_score_max) - nades_GiveBonus(player, time_score / autocvar_g_nades_bonus_score_max); - } - else - { - STAT(NADE_BONUS, player) = STAT(NADE_BONUS_SCORE, player) = 0; - } - - if(player.nade_veil_time && player.nade_veil_time <= time) - { - player.nade_veil_time = 0; - if(player.vehicle) - player.vehicle.alpha = player.vehicle.nade_veil_prevalpha; - else - player.alpha = player.nade_veil_prevalpha; - } - } - - if (!(frametime && IS_PLAYER(player))) - return true; - - entity revivers_last = NULL; - entity revivers_first = NULL; - - bool player_is_reviving = false; - int n = 0; - vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; - FOREACH_CLIENT(IS_PLAYER(it) && IN_REVIVING_RANGE(player, it, revive_extra_size), { - // check if player is reviving anyone - if (STAT(FROZEN, it) == FROZEN_TEMP_DYING) - { - if ((STAT(FROZEN, player) == FROZEN_TEMP_DYING)) - continue; - if (!IN_REVIVING_RANGE(player, it, revive_extra_size)) - continue; - player_is_reviving = true; - break; - } - - if (!(STAT(FROZEN, player) == FROZEN_TEMP_DYING)) - continue; // both player and it are NOT frozen - if (revivers_last) - revivers_last.chain = it; - revivers_last = it; - if (!revivers_first) - revivers_first = it; - ++n; - }); - if (revivers_last) - revivers_last.chain = NULL; - - if (!n) // no teammate nearby - { - // freezetag already resets revive progress - if (!g_freezetag && !STAT(FROZEN, player) && !player_is_reviving) - STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody - } - else if (n > 0 && STAT(FROZEN, player) == FROZEN_TEMP_DYING) // OK, there is at least one teammate reviving us - { - STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); - // undo what PlayerPreThink did - STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * player.revive_speed, 1); - SetResource(player, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * start_health)); - - if(STAT(REVIVE_PROGRESS, player) >= 1) - { - Unfreeze(player, false); - - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, revivers_first.netname); - Send_Notification(NOTIF_ONE, revivers_first, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname); - } - - for(entity it = revivers_first; it; it = it.chain) - STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player); - } -} - -MUTATOR_HOOKFUNCTION(nades, PlayerPhysics_UpdateStats) -{ - entity player = M_ARGV(0, entity); - // these automatically reset, no need to worry - - if(player.nade_entrap_time > time) - STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_nades_entrap_speed; -} - -MUTATOR_HOOKFUNCTION(nades, MonsterMove) -{ - entity mon = M_ARGV(0, entity); - - if (mon.nade_entrap_time > time) - { - M_ARGV(1, float) *= autocvar_g_nades_entrap_speed; // run speed - M_ARGV(2, float) *= autocvar_g_nades_entrap_speed; // walk speed - } - - if (mon.nade_veil_time && mon.nade_veil_time <= time) - { - mon.alpha = mon.nade_veil_prevalpha; - mon.nade_veil_time = 0; - } -} - -MUTATOR_HOOKFUNCTION(nades, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - if (StatusEffects_active(STATUSEFFECT_SpawnShield, player)) - player.nade_refire = StatusEffects_gettime(STATUSEFFECT_SpawnShield, player); - else - player.nade_refire = time; - - if (!autocvar_g_nades_onspawn) - player.nade_refire += autocvar_g_nades_nade_refire; - - if(autocvar_g_nades_bonus_client_select) - STAT(NADE_BONUS_TYPE, player) = CS_CVAR(player).cvar_cl_nade_type; - - STAT(NADE_TIMER, player) = 0; - - if (!player.offhand) player.offhand = OFFHAND_NADE; - - if(player.nade_spawnloc) + switch (proj) { - setorigin(player, player.nade_spawnloc.origin); - player.nade_spawnloc.cnt -= 1; - - if(player.nade_spawnloc.cnt <= 0) - { - delete(player.nade_spawnloc); - player.nade_spawnloc = NULL; - } - - if(autocvar_g_nades_spawn_health_respawn > 0) - SetResource(player, RES_HEALTH, autocvar_g_nades_spawn_health_respawn); + case PROJECTILE_NADE: return EFFECT_NADE_TRAIL(nade_team); + case PROJECTILE_NADE_BURN: return EFFECT_NADE_TRAIL_BURN(nade_team); } -} - -MUTATOR_HOOKFUNCTION(nades, PlayerDies, CBC_ORDER_LAST) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - - if(frag_target.nade) - if(!STAT(FROZEN, frag_target) || !autocvar_g_freezetag_revive_nade) - toss_nade(frag_target, true, '0 0 100', max(frag_target.nade.wait, time + 0.05)); - if(IS_PLAYER(frag_attacker)) - { - float killcount_bonus = ((CS(frag_attacker).killcount >= 1) ? bound(0, autocvar_g_nades_bonus_score_minor * CS(frag_attacker).killcount, autocvar_g_nades_bonus_score_medium) - : autocvar_g_nades_bonus_score_minor); - if (SAME_TEAM(frag_attacker, frag_target) || frag_attacker == frag_target) - nades_RemoveBonus(frag_attacker); - else if(GameRules_scoring_is_vip(frag_target)) - nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_medium); - else if(autocvar_g_nades_bonus_score_spree && CS(frag_attacker).killcount > 1) + FOREACH(Nades, true, { + for (int j = 0; j < 2; ++j) { - #define SPREE_ITEM(counta,countb,center,normal,gentle) \ - case counta: { nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_spree); break; } - switch(CS(frag_attacker).killcount) + if (it.m_projectile[j] == proj) { - KILL_SPREE_LIST - default: nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); break; + string trail = it.m_trail[j].eent_eff_name; + if (trail) return it.m_trail[j]; + break; } - #undef SPREE_ITEM } - else - nades_GiveBonus(frag_attacker, killcount_bonus); - } - - nades_RemoveBonus(frag_target); -} - -MUTATOR_HOOKFUNCTION(nades, Damage_Calculate) -{ - entity frag_inflictor = M_ARGV(0, entity); - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - float frag_deathtype = M_ARGV(3, float); - - if(autocvar_g_freezetag_revive_nade && STAT(FROZEN, frag_target) && frag_attacker == frag_target && frag_deathtype == DEATH_NADE.m_id) - if(time - frag_inflictor.toss_time <= 0.1) - { - Unfreeze(frag_target, false); - SetResource(frag_target, RES_HEALTH, autocvar_g_freezetag_revive_nade_health); - Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3); - M_ARGV(4, float) = 0; - M_ARGV(6, vector) = '0 0 0'; - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_NADE, frag_target.netname); - Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF); - } -} - -MUTATOR_HOOKFUNCTION(nades, MonsterDies) -{ - entity frag_target = M_ARGV(0, entity); - entity frag_attacker = M_ARGV(1, entity); - - if(IS_PLAYER(frag_attacker)) - if(DIFF_TEAM(frag_attacker, frag_target)) - if(!(frag_target.spawnflags & MONSTERFLAG_SPAWNED)) - nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); -} - -MUTATOR_HOOKFUNCTION(nades, DropSpecialItems) -{ - entity frag_target = M_ARGV(0, entity); - - if(frag_target.nade) - toss_nade(frag_target, true, '0 0 0', time + 0.05); -} - -void nades_RemovePlayer(entity this) -{ - nades_Clear(this); - nades_RemoveBonus(this); -} - -MUTATOR_HOOKFUNCTION(nades, MakePlayerObserver) { entity player = M_ARGV(0, entity); nades_RemovePlayer(player); } -MUTATOR_HOOKFUNCTION(nades, ClientDisconnect) { entity player = M_ARGV(0, entity); nades_RemovePlayer(player); } -MUTATOR_HOOKFUNCTION(nades, reset_map_global) -{ - FOREACH_CLIENT(IS_PLAYER(it), - { - nades_RemovePlayer(it); }); -} - -MUTATOR_HOOKFUNCTION(nades, SpectateCopy) -{ - entity spectatee = M_ARGV(0, entity); - entity client = M_ARGV(1, entity); - - STAT(NADE_TIMER, client) = STAT(NADE_TIMER, spectatee); - STAT(NADE_BONUS_TYPE, client) = STAT(NADE_BONUS_TYPE, spectatee); - client.pokenade_type = spectatee.pokenade_type; - STAT(NADE_BONUS, client) = STAT(NADE_BONUS, spectatee); - STAT(NADE_BONUS_SCORE, client) = STAT(NADE_BONUS_SCORE, spectatee); -} - -MUTATOR_HOOKFUNCTION(nades, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Nades"); -} -MUTATOR_HOOKFUNCTION(nades, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":Nades"); + return EFFECT_Null; } -#endif +#endif // GAMEQC diff --git a/qcsrc/common/mutators/mutator/nades/nades.qh b/qcsrc/common/mutators/mutator/nades/nades.qh index bf21b759a..b328d2ed7 100644 --- a/qcsrc/common/mutators/mutator/nades/nades.qh +++ b/qcsrc/common/mutators/mutator/nades/nades.qh @@ -2,115 +2,6 @@ #include -#ifdef SVQC -bool autocvar_g_nades; -bool autocvar_g_nades_override_dropweapon = true; -vector autocvar_g_nades_throw_offset; -bool autocvar_g_nades_onspawn; -int autocvar_g_nades_spawn_count; -float autocvar_g_nades_spawn_health_respawn; -float autocvar_g_nades_spawn_destroy_damage = 25; -bool autocvar_g_nades_client_select; -bool autocvar_g_nades_pickup = true; -float autocvar_g_nades_pickup_time = 2; -float autocvar_g_nades_nade_lifetime; -float autocvar_g_nades_nade_minforce; -float autocvar_g_nades_nade_maxforce; -float autocvar_g_nades_nade_health; -float autocvar_g_nades_nade_refire; -float autocvar_g_nades_nade_damage; -float autocvar_g_nades_nade_edgedamage; -float autocvar_g_nades_nade_radius; -float autocvar_g_nades_nade_force; -int autocvar_g_nades_nade_newton_style; -bool autocvar_g_nades_napalm; -int autocvar_g_nades_napalm_ball_count; -float autocvar_g_nades_napalm_ball_spread; -float autocvar_g_nades_napalm_ball_damage; -float autocvar_g_nades_napalm_ball_damageforcescale; -float autocvar_g_nades_napalm_ball_lifetime; -float autocvar_g_nades_napalm_ball_radius; -bool autocvar_g_nades_napalm_blast; -float autocvar_g_nades_napalm_fountain_lifetime; -float autocvar_g_nades_napalm_fountain_delay; -float autocvar_g_nades_napalm_fountain_radius; -float autocvar_g_nades_napalm_fountain_damage; -float autocvar_g_nades_napalm_fountain_edgedamage; -float autocvar_g_nades_napalm_burntime; -bool autocvar_g_nades_napalm_selfdamage; -int autocvar_g_nades_nade_type; -int autocvar_g_nades_bonus_type; -bool autocvar_g_nades_bonus; -bool autocvar_g_nades_bonus_onstrength; -bool autocvar_g_nades_bonus_client_select; -bool autocvar_g_nades_bonus_only; -int autocvar_g_nades_bonus_max; -int autocvar_g_nades_bonus_score_max; -int autocvar_g_nades_bonus_score_time; -int autocvar_g_nades_bonus_score_time_flagcarrier; -int autocvar_g_nades_bonus_score_minor; -int autocvar_g_nades_bonus_score_low; -int autocvar_g_nades_bonus_score_high; -int autocvar_g_nades_bonus_score_medium; -int autocvar_g_nades_bonus_score_spree; -bool autocvar_g_nades_ice = true; -float autocvar_g_nades_ice_freeze_time; -float autocvar_g_nades_ice_health; -bool autocvar_g_nades_ice_explode; -bool autocvar_g_nades_ice_teamcheck; -bool autocvar_g_nades_translocate = true; -float autocvar_g_nades_translocate_destroy_damage = 25; -bool autocvar_g_nades_spawn = true; -bool autocvar_g_nades_heal = true; -float autocvar_g_nades_heal_time; -float autocvar_g_nades_heal_rate; -float autocvar_g_nades_heal_friend; -float autocvar_g_nades_heal_foe; -bool autocvar_g_nades_entrap; -float autocvar_g_nades_entrap_strength = 0.01; -float autocvar_g_nades_entrap_speed = 0.5; -float autocvar_g_nades_entrap_radius = 500; -float autocvar_g_nades_entrap_time = 10; -bool autocvar_g_nades_veil; -float autocvar_g_nades_veil_time = 8; -float autocvar_g_nades_veil_radius = 300; -bool autocvar_g_nades_ammo; -float autocvar_g_nades_ammo_time; -float autocvar_g_nades_ammo_rate; -float autocvar_g_nades_ammo_friend; -float autocvar_g_nades_ammo_foe; -bool autocvar_g_nades_darkness; -bool autocvar_g_nades_darkness_explode; -bool autocvar_g_nades_darkness_teamcheck; -float autocvar_g_nades_darkness_time; -bool autocvar_g_nades_pokenade; -string autocvar_g_nades_pokenade_monster_type; -float autocvar_g_nades_pokenade_monster_lifetime; -#endif - -// use slots 70-100 -const int PROJECTILE_NADE = 71; -const int PROJECTILE_NADE_BURN = 72; -const int PROJECTILE_NADE_NAPALM = 73; -const int PROJECTILE_NADE_NAPALM_BURN = 74; -const int PROJECTILE_NAPALM_FOUNTAIN = 75; -const int PROJECTILE_NADE_ICE = 76; -const int PROJECTILE_NADE_ICE_BURN = 77; -const int PROJECTILE_NADE_TRANSLOCATE = 78; -const int PROJECTILE_NADE_SPAWN = 79; -const int PROJECTILE_NADE_HEAL = 80; -const int PROJECTILE_NADE_HEAL_BURN = 81; -const int PROJECTILE_NADE_MONSTER = 82; -const int PROJECTILE_NADE_MONSTER_BURN = 83; -const int PROJECTILE_NADE_ENTRAP = 84; -const int PROJECTILE_NADE_ENTRAP_BURN = 85; -const int PROJECTILE_NADE_VEIL = 86; -const int PROJECTILE_NADE_VEIL_BURN = 87; -const int PROJECTILE_NADE_AMMO = 88; -const int PROJECTILE_NADE_AMMO_BURN = 89; -const int PROJECTILE_NADE_DARKNESS = 90; -const int PROJECTILE_NADE_DARKNESS_BURN = 91; - REGISTRY(Nades, BITS(4)) REGISTER_REGISTRY(Nades) REGISTRY_CHECK(Nades) @@ -133,6 +24,20 @@ ENDCLASS(Nade) REGISTER_NADE(Null, NEW(Nade)); REGISTRY_DEFINE_GET(Nades, NADE_TYPE_Null) +// use slots 70-100 +const int PROJECTILE_NADE = 71; +const int PROJECTILE_NADE_BURN = 72; +// the rest of these are defined in their individual files like nade/napalm.qh + +#ifdef GAMEQC +#include "effects.inc" + +#define NADE_PROJECTILE(i, projectile, trail) MACRO_BEGIN \ + this.m_projectile[i] = projectile; \ + this.m_trail[i] = trail; \ +MACRO_END +#endif // GAMEQC + Nade Nade_FromProjectile(int proj) { FOREACH(Nades, true, { @@ -141,10 +46,6 @@ Nade Nade_FromProjectile(int proj) return NADE_TYPE_Null; } -#ifdef GAMEQC -#include "effects.inc" -#endif - #include "all.inc" .float orb_lifetime; @@ -154,50 +55,3 @@ Nade Nade_FromProjectile(int proj) REPLICATE_INIT(int, cvar_cl_nade_type); REPLICATE_INIT(string, cvar_cl_pokenade_type); #endif - -#ifdef SVQC - -.entity nade; -.entity fake_nade; -.float nade_refire; -.float nade_special_time; -.string pokenade_type; -.entity nade_damage_target; -.int cvar_cl_nade_type; -.string cvar_cl_pokenade_type; -.float toss_time; -.float nade_show_particles; -.float nade_veil_time; -.float nade_veil_prevalpha; -.float nade_entrap_time; -.float nade_ammo_time; -.float nade_darkness_time; - -bool orb_send(entity this, entity to, int sf); - -// Remove nades that are being thrown -void nades_Clear(entity player); - -// Give a bonus grenade to a player -void nades_GiveBonus(entity player, float score); - -/** - * called to adjust nade damage and force on hit - */ -#define EV_Nade_Damage(i, o) \ - /** nade */ i(entity, MUTATOR_ARGV_0_entity) \ - /** weapon */ i(entity, MUTATOR_ARGV_1_entity) \ - /** force */ i(vector, MUTATOR_ARGV_2_vector) \ - /**/ o(vector, MUTATOR_ARGV_2_vector) \ - /** damage */ i(float, MUTATOR_ARGV_3_float) \ - /**/ o(float, MUTATOR_ARGV_3_float) \ - /**/ -MUTATOR_HOOKABLE(Nade_Damage, EV_Nade_Damage); - -#endif - -#ifdef CSQC -bool Projectile_isnade(int proj); // TODO: remove - -void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expand_time); // TODO: mutator -#endif diff --git a/qcsrc/common/mutators/mutator/nades/sv_nades.qc b/qcsrc/common/mutators/mutator/nades/sv_nades.qc new file mode 100644 index 000000000..618f502d7 --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/sv_nades.qc @@ -0,0 +1,951 @@ +#include "sv_nades.qh" + +#include +#include +#include +#include +#include + +.float nade_time_primed; +.float nade_lifetime; + +.entity nade_spawnloc; + +vector nades_PlayerColor(entity this, bool isPants) +{ + if(teamplay) + return Team_ColorRGB(this.team); + + // logic copied from Scoreboard_GetName + int col = (this.colormap >= 1024) ? this.colormap - 1024 : this.clientcolors; + return (isPants) ? colormapPaletteColor(col % 16, true) : colormapPaletteColor(floor(col / 16), false); +} + +void nade_timer_think(entity this) +{ + this.skin = 8 - (this.owner.wait - time) / (this.owner.nade_lifetime / 10); + this.nextthink = time; + if(!this.owner || wasfreed(this.owner)) + delete(this); +} + +void nade_burn_spawn(entity _nade) +{ + CSQCProjectile(_nade, true, REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, _nade)).m_projectile[1], true); +} + +void nade_spawn(entity _nade) +{ + entity timer = new(nade_timer); + setmodel(timer, MDL_NADE_TIMER); + setattachment(timer, _nade, ""); + timer.colormap = _nade.colormap; + timer.glowmod = _nade.glowmod; + setthink(timer, nade_timer_think); + timer.nextthink = time; + timer.wait = _nade.wait; + timer.owner = _nade; + timer.skin = 10; + + _nade.effects |= EF_LOWPRECISION; + + CSQCProjectile(_nade, true, REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, _nade)).m_projectile[0], true); +} + + +void nades_orb_think(entity this) +{ + if(time >= this.ltime) + { + delete(this); + return; + } + + this.nextthink = time; + + if(time >= this.nade_special_time) + { + this.nade_special_time = time+0.25; + this.nade_show_particles = 1; + } + else + this.nade_show_particles = 0; +} + +entity nades_spawn_orb(entity own, entity realown, vector org, float orb_ltime, float orb_rad) +{ + // NOTE: this function merely places an orb + // you must add a custom touch function to the returned entity if desired + // also set .colormod if you wish to have it colorized + entity orb = new(nades_spawn_orb); + orb.owner = own; + orb.realowner = realown; + setorigin(orb, org); + + orb.orb_lifetime = orb_ltime; // required for timers + orb.ltime = time + orb.orb_lifetime; + orb.bot_dodge = false; + orb.team = realown.team; + orb.solid = SOLID_TRIGGER; + + setmodel(orb, MDL_NADE_ORB); + orb.skin = 1; + orb.orb_radius = orb_rad; // required for fading + vector size = '1 1 1' * orb.orb_radius / 2; + setsize(orb, -size, size); + + Net_LinkEntity(orb, true, 0, orb_send); + orb.SendFlags |= 1; + + setthink(orb, nades_orb_think); + orb.nextthink = time; + + return orb; +} + + +void nade_boom(entity this) +{ + entity expef = NULL; + bool nade_blast = true; + vector expcol_min = '0 0 0', expcol_max = '0 0 0'; + +#define SET_NADE_EFFECT(nade_type, blast, exp_effect, exp_color_min, exp_color_max) \ + case nade_type: \ + nade_blast = blast; \ + expef = exp_effect; \ + expcol_min = exp_color_min; \ + expcol_max = exp_color_max; \ + break + + switch ( REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, this)) ) + { + SET_NADE_EFFECT(NADE_TYPE_NAPALM, autocvar_g_nades_napalm_blast, EFFECT_EXPLOSION_MEDIUM, '0 0 0', '0 0 0'); + SET_NADE_EFFECT(NADE_TYPE_ICE, false, EFFECT_ELECTRO_COMBO, '0 0 0', '0 0 0'); + SET_NADE_EFFECT(NADE_TYPE_TRANSLOCATE, false, NULL, '0 0 0', '0 0 0'); + SET_NADE_EFFECT(NADE_TYPE_MONSTER, true, (!autocvar_g_monsters) ? EFFECT_NADE_EXPLODE_NEUTRAL : NULL, nades_PlayerColor(this.realowner, false), nades_PlayerColor(this.realowner, true)); + SET_NADE_EFFECT(NADE_TYPE_SPAWN, false, EFFECT_SPAWN_NEUTRAL, nades_PlayerColor(this.realowner, false), nades_PlayerColor(this.realowner, true)); + SET_NADE_EFFECT(NADE_TYPE_HEAL, false, EFFECT_SPAWN_NEUTRAL, '1 0 0', '1 0 0'); + SET_NADE_EFFECT(NADE_TYPE_ENTRAP, false, EFFECT_SPAWN_NEUTRAL, '1 1 0', '1 1 0'); + SET_NADE_EFFECT(NADE_TYPE_VEIL, false, EFFECT_SPAWN_NEUTRAL, '0 0 0', '0 0 0'); + SET_NADE_EFFECT(NADE_TYPE_AMMO, false, EFFECT_SPAWN_NEUTRAL, '0.66 0.33 0', '0.66 0.33 0'); + SET_NADE_EFFECT(NADE_TYPE_DARKNESS, false, EFFECT_EXPLOSION_MEDIUM, '0 0 0', '0 0 0'); + SET_NADE_EFFECT(NADE_TYPE_NORMAL, true, EFFECT_NADE_EXPLODE_NEUTRAL, nades_PlayerColor(this.realowner, false), nades_PlayerColor(this.realowner, true)); + default: expef = EFFECT_NADE_EXPLODE_NEUTRAL; expcol_min = nades_PlayerColor(this.realowner, false); expcol_max = nades_PlayerColor(this.realowner, true); break; + } +#undef SET_NADE_EFFECT + + if(expef) + Send_Effect_Except(expef, findbetterlocation(this.origin, 8), '0 0 0', 1, expcol_min, expcol_max, NULL); + + sound(this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM); + sound(this, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); + + this.event_damage = func_null; // prevent somehow calling damage in the next call + + if(nade_blast) + normal_nade_boom(this); + + if(this.takedamage) + switch ( REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, this)) ) + { + case NADE_TYPE_NAPALM: nade_napalm_boom(this); break; + case NADE_TYPE_ICE: nade_ice_boom(this); break; + case NADE_TYPE_TRANSLOCATE: nade_translocate_boom(this); break; + case NADE_TYPE_SPAWN: nade_spawn_boom(this); break; + case NADE_TYPE_HEAL: nade_heal_boom(this); break; + case NADE_TYPE_MONSTER: nade_monster_boom(this); break; + case NADE_TYPE_ENTRAP: nade_entrap_boom(this); break; + case NADE_TYPE_VEIL: nade_veil_boom(this); break; + case NADE_TYPE_AMMO: nade_ammo_boom(this); break; + case NADE_TYPE_DARKNESS: nade_darkness_boom(this); break; + } + + IL_EACH(g_projectiles, it.classname == "grapplinghook" && it.aiment == this, + { + RemoveHook(it); + }); + + delete(this); +} + +void spawn_held_nade(entity player, entity nowner, float ntime, int ntype, string pntype); +void nade_pickup(entity this, entity thenade) +{ + spawn_held_nade(this, thenade.realowner, autocvar_g_nades_pickup_time, STAT(NADE_BONUS_TYPE, thenade), thenade.pokenade_type); + + // set refire so player can't even + this.nade_refire = time + autocvar_g_nades_nade_refire; + STAT(NADE_TIMER, this) = 0; + + if(this.nade) + this.nade.nade_time_primed = thenade.nade_time_primed; +} + +bool CanThrowNade(entity this); +void nade_touch(entity this, entity toucher) +{ + if(toucher) + UpdateCSQCProjectile(this); + + if(toucher == this.realowner) + return; // no this impacts + + if(autocvar_g_nades_pickup) + if(time >= this.spawnshieldtime) + if(!toucher.nade && GetResource(this, RES_HEALTH) == this.max_health) // no boosted shot pickups, thank you very much + if(CanThrowNade(toucher)) // prevent some obvious things, like dead players + if(IS_REAL_CLIENT(toucher)) // above checks for IS_PLAYER, don't need to do it here + { + nade_pickup(toucher, this); + sound(this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX)); + delete(this); + return; + } + /*float is_weapclip = 0; + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW) + if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)) + if (!(trace_dphitcontents & DPCONTENTS_OPAQUE)) + is_weapclip = 1;*/ + if(ITEM_TOUCH_NEEDKILL()) // || is_weapclip) + { + IL_EACH(g_projectiles, it.classname == "grapplinghook" && it.aiment == this, + { + RemoveHook(it); + }); + delete(this); + return; + } + + PROJECTILE_TOUCH(this, toucher); + + //setsize(this, '-2 -2 -2', '2 2 2'); + //UpdateCSQCProjectile(this); + if(GetResource(this, RES_HEALTH) == this.max_health) + { + spamsound(this, CH_SHOTS, SND_GRENADE_BOUNCE_RANDOM(), VOL_BASE, ATTEN_NORM); + return; + } + + this.enemy = toucher; + nade_boom(this); +} + +void nade_beep(entity this) +{ + sound(this, CH_SHOTS_SINGLE, SND_NADE_BEEP, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX)); + setthink(this, nade_boom); + this.nextthink = max(this.wait, time); +} + +void nade_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) +{ + if(ITEM_DAMAGE_NEEDKILL(deathtype)) + { + this.takedamage = DAMAGE_NO; + nade_boom(this); + return; + } + + if(STAT(NADE_BONUS_TYPE, this) == NADE_TYPE_TRANSLOCATE.m_id || STAT(NADE_BONUS_TYPE, this) == NADE_TYPE_SPAWN.m_id) + return; + + if (MUTATOR_CALLHOOK(Nade_Damage, this, DEATH_WEAPONOF(deathtype), force, damage)) {} + else if(DEATH_ISWEAPON(deathtype, WEP_BLASTER)) + { + force *= 1.5; + damage = 0; + } + else if(DEATH_ISWEAPON(deathtype, WEP_VORTEX) || DEATH_ISWEAPON(deathtype, WEP_VAPORIZER) || DEATH_ISWEAPON(deathtype, WEP_OVERKILL_NEX)) + { + force *= 6; + damage = this.max_health * 0.55; + } + else if(DEATH_ISWEAPON(deathtype, WEP_MACHINEGUN) || DEATH_ISWEAPON(deathtype, WEP_OVERKILL_MACHINEGUN)) + damage = this.max_health * 0.1; + else if(DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN) || DEATH_ISWEAPON(deathtype, WEP_OVERKILL_SHOTGUN)) // WEAPONTODO + { + if(!(deathtype & HITTYPE_SECONDARY)) + damage = this.max_health * 1.15; + } + + // melee slaps + entity death_weapon = DEATH_WEAPONOF(deathtype); + if(((deathtype & HITTYPE_SECONDARY) ? (death_weapon.spawnflags & WEP_TYPE_MELEE_SEC) : (death_weapon.spawnflags & WEP_TYPE_MELEE_PRI))) + { + damage = this.max_health * 0.1; + force *= 10; + } + + this.velocity += force; + UpdateCSQCProjectile(this); + + if(damage <= 0 || ((IS_ONGROUND(this)) && IS_PLAYER(attacker))) + return; + + float hp = GetResource(this, RES_HEALTH); + if(hp == this.max_health) + { + sound(this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX)); + this.nextthink = max(time + this.nade_lifetime, time); + setthink(this, nade_beep); + } + + hp -= damage; + SetResource(this, RES_HEALTH, hp); + + if(STAT(NADE_BONUS_TYPE, this) != NADE_TYPE_TRANSLOCATE.m_id && STAT(NADE_BONUS_TYPE, this) != NADE_TYPE_SPAWN.m_id) + if(STAT(NADE_BONUS_TYPE, this) != NADE_TYPE_HEAL.m_id || IS_PLAYER(attacker)) + this.realowner = attacker; + + if(hp <= 0) + { + if(nade_spawn_DestroyDamage(this, attacker)) + return; + if(nade_translocate_DestroyDamage(this, attacker)) + return; + + W_PrepareExplosionByDamage(this, attacker, nade_boom); + } + else + nade_burn_spawn(this); +} + +void toss_nade(entity e, bool set_owner, vector _velocity, float _time) +{ + if(e.nade == NULL) + return; + + entity _nade = e.nade; + e.nade = NULL; + + if(e.fake_nade) + delete(e.fake_nade); + e.fake_nade = NULL; + + Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER, CPID_NADES); + + makevectors(e.v_angle); + + // NOTE: always throw from first weapon entity? + W_SetupShot(e, _nade.weaponentity_fld, false, false, SND_Null, CH_WEAPON_A, 0, DEATH_NADE.m_id); + + vector offset = (v_forward * autocvar_g_nades_throw_offset.x) + + (v_right * autocvar_g_nades_throw_offset.y) + + (v_up * autocvar_g_nades_throw_offset.z); + + setorigin(_nade, w_shotorg + offset); + //setmodel(_nade, MDL_PROJECTILE_NADE); + //setattachment(_nade, NULL, ""); + PROJECTILE_MAKETRIGGER(_nade); + if(STAT(NADES_SMALL, e)) + setsize(_nade, '-8 -8 -8', '8 8 8'); + else + setsize(_nade, '-16 -16 -16', '16 16 16'); + set_movetype(_nade, MOVETYPE_BOUNCE); + + tracebox(_nade.origin, _nade.mins, _nade.maxs, _nade.origin, MOVE_NOMONSTERS, _nade); + if (trace_startsolid) + setorigin(_nade, e.origin); + + if(e.v_angle.x >= 70 && e.v_angle.x <= 110 && PHYS_INPUT_BUTTON_CROUCH(e)) + _nade.velocity = '0 0 100'; + else if(autocvar_g_nades_nade_newton_style == 1) + _nade.velocity = e.velocity + _velocity; + else if(autocvar_g_nades_nade_newton_style == 2) + _nade.velocity = _velocity; + else + _nade.velocity = W_CalculateProjectileVelocity(e, e.velocity, _velocity, true); + + if(set_owner) + _nade.realowner = e; + + settouch(_nade, nade_touch); + _nade.spawnshieldtime = time + 0.1; // prevent instantly picking up again + SetResource(_nade, RES_HEALTH, autocvar_g_nades_nade_health); + _nade.max_health = GetResource(_nade, RES_HEALTH); + _nade.takedamage = DAMAGE_AIM; + _nade.event_damage = nade_damage; + setcefc(_nade, func_null); + _nade.exteriormodeltoclient = NULL; + _nade.traileffectnum = 0; + _nade.teleportable = true; + _nade.pushable = true; + _nade.gravity = 1; + _nade.missile_flags = MIF_SPLASH | MIF_ARC; + _nade.damagedbycontents = true; + IL_PUSH(g_damagedbycontents, _nade); + _nade.angles = vectoangles(_nade.velocity); + _nade.flags = FL_PROJECTILE; + IL_PUSH(g_projectiles, _nade); + IL_PUSH(g_bot_dodge, _nade); + _nade.projectiledeathtype = DEATH_NADE.m_id; + _nade.toss_time = time; + _nade.solid = SOLID_CORPSE; //((STAT(NADE_BONUS_TYPE, _nade) == NADE_TYPE_TRANSLOCATE) ? SOLID_CORPSE : SOLID_BBOX); + + if(STAT(NADE_BONUS_TYPE, _nade) == NADE_TYPE_TRANSLOCATE.m_id || STAT(NADE_BONUS_TYPE, _nade) == NADE_TYPE_SPAWN.m_id) + _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + else + _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY; + + nade_spawn(_nade); + + if(_time) + { + setthink(_nade, nade_boom); + _nade.nextthink = _time; + } + + e.nade_refire = time + autocvar_g_nades_nade_refire; + STAT(NADE_TIMER, e) = 0; +} + +void nades_GiveBonus(entity player, float score) +{ + if (autocvar_g_nades) + if (autocvar_g_nades_bonus) + if (IS_REAL_CLIENT(player)) + if (IS_PLAYER(player) && STAT(NADE_BONUS, player) < autocvar_g_nades_bonus_max) + if (!STAT(FROZEN, player)) + if (!IS_DEAD(player)) + { + if ( STAT(NADE_BONUS_SCORE, player) < 1 ) + STAT(NADE_BONUS_SCORE, player) += score/autocvar_g_nades_bonus_score_max; + + if ( STAT(NADE_BONUS_SCORE, player) >= 1 ) + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_NADE_BONUS); + play2(player, SND(NADE_BONUS)); + STAT(NADE_BONUS, player)++; + STAT(NADE_BONUS_SCORE, player) -= 1; + } + } +} + +/** Remove all bonus nades from a player */ +void nades_RemoveBonus(entity player) +{ + STAT(NADE_BONUS, player) = STAT(NADE_BONUS_SCORE, player) = 0; +} + +MUTATOR_HOOKFUNCTION(nades, PutClientInServer) +{ + entity player = M_ARGV(0, entity); + + nades_RemoveBonus(player); +} + +bool nade_customize(entity this, entity client) +{ + //if(IS_SPEC(client)) { return false; } + if(client == this.exteriormodeltoclient || (IS_SPEC(client) && client.enemy == this.exteriormodeltoclient)) + { + // somewhat hide the model, but keep the glow + //this.effects = 0; + if(this.traileffectnum) + this.traileffectnum = 0; + this.alpha = -1; + } + else + { + //this.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + if(!this.traileffectnum) + { + entity nade = REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, this)); + this.traileffectnum = _particleeffectnum(Nade_TrailEffect(nade.m_projectile[0], this.team).eent_eff_name); + } + this.alpha = 1; + } + + return true; +} + +void spawn_held_nade(entity player, entity nowner, float ntime, int ntype, string pntype) +{ + entity n = new(nade), fn = new(fake_nade); + + n.pokenade_type = pntype; + + if(ntype == 0) // random nade + ntype = floor(random() * (REGISTRY_COUNT(Nades) - 1)) + 1; + Nade def = REGISTRY_GET(Nades, ntype); + if(def == NADE_TYPE_Null) + def = NADE_TYPE_NORMAL; + + STAT(NADE_BONUS_TYPE, n) = def.m_id; + + .entity weaponentity = weaponentities[0]; // TODO: unhardcode + + setmodel(n, MDL_PROJECTILE_NADE); + //setattachment(n, player, "bip01 l hand"); + n.exteriormodeltoclient = player; + setcefc(n, nade_customize); + n.traileffectnum = _particleeffectnum(Nade_TrailEffect(def.m_projectile[0], player.team).eent_eff_name); + n.colormod = def.m_color; + n.realowner = nowner; + n.colormap = player.colormap; + n.glowmod = player.glowmod; + n.wait = time + max(0, ntime); + n.nade_time_primed = time; + setthink(n, nade_beep); + n.nextthink = max(n.wait - 3, time); + n.projectiledeathtype = DEATH_NADE.m_id; + n.weaponentity_fld = weaponentity; + n.nade_lifetime = ntime; + n.alpha = def.m_alpha; + + setmodel(fn, MDL_NADE_VIEW); + //setattachment(fn, player.(weaponentity), ""); + fn.viewmodelforclient = player; + fn.realowner = fn.owner = player; + fn.colormod = def.m_color; + fn.colormap = player.colormap; + fn.glowmod = player.glowmod; + setthink(fn, SUB_Remove); + fn.nextthink = n.wait; + fn.weaponentity_fld = weaponentity; + fn.alpha = def.m_alpha; + + player.nade = n; + player.fake_nade = fn; +} + +void nade_prime(entity this) +{ + if(autocvar_g_nades_bonus_only && !STAT(NADE_BONUS, this)) + return; // only allow bonus nades + + // TODO: handle old nade if it exists? + if(this.nade) + delete(this.nade); + this.nade = NULL; + + if(this.fake_nade) + delete(this.fake_nade); + this.fake_nade = NULL; + + int ntype; + string pntype = this.pokenade_type; + + if(StatusEffects_active(STATUSEFFECT_Strength, this) && autocvar_g_nades_bonus_onstrength) + ntype = STAT(NADE_BONUS_TYPE, this); + else if (STAT(NADE_BONUS, this) >= 1) + { + ntype = STAT(NADE_BONUS_TYPE, this); + pntype = this.pokenade_type; + STAT(NADE_BONUS, this) -= 1; + } + else + { + ntype = ((autocvar_g_nades_client_select) ? CS_CVAR(this).cvar_cl_nade_type : autocvar_g_nades_nade_type); + pntype = ((autocvar_g_nades_client_select) ? CS_CVAR(this).cvar_cl_pokenade_type : autocvar_g_nades_pokenade_monster_type); + } + + spawn_held_nade(this, this, autocvar_g_nades_nade_lifetime, ntype, pntype); +} + +bool CanThrowNade(entity this) +{ + return !(this.vehicle || !autocvar_g_nades || IS_DEAD(this) || !IS_PLAYER(this) || weaponLocked(this)); +} + +.bool nade_altbutton; + +void nades_CheckThrow(entity this) +{ + if(!CanThrowNade(this)) + return; + + entity held_nade = this.nade; + if (!held_nade) + { + this.nade_altbutton = true; + if(time > this.nade_refire) + { + nade_prime(this); + this.nade_refire = time + autocvar_g_nades_nade_refire; + } + } + else + { + this.nade_altbutton = false; + if (time >= held_nade.nade_time_primed + 1) { + makevectors(this.v_angle); + float _force = time - held_nade.nade_time_primed; + _force /= autocvar_g_nades_nade_lifetime; + _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce)); + vector dir = (v_forward * 0.75 + v_up * 0.2 + v_right * 0.05); + dir = W_CalculateSpread(dir, autocvar_g_nades_spread, autocvar_g_weaponspreadfactor, autocvar_g_projectiles_spread_style); + toss_nade(this, true, dir * _force, 0); + } + } +} + +void nades_Clear(entity player) +{ + if(player.nade) + delete(player.nade); + if(player.fake_nade) + delete(player.fake_nade); + + player.nade = player.fake_nade = NULL; + STAT(NADE_TIMER, player) = 0; +} + +int nades_CheckTypes(entity player, int cl_ntype) +{ + // TODO check what happens without this patch +#define CL_NADE_TYPE_CHECK(nade_ent, nade_cvar) \ + case nade_ent.m_id: if (nade_cvar) return cl_ntype + + switch (cl_ntype) + { + case 0: return 0; // random nade + CL_NADE_TYPE_CHECK(NADE_TYPE_NAPALM, autocvar_g_nades_napalm); + CL_NADE_TYPE_CHECK(NADE_TYPE_ICE, autocvar_g_nades_ice); + CL_NADE_TYPE_CHECK(NADE_TYPE_TRANSLOCATE, autocvar_g_nades_translocate); + CL_NADE_TYPE_CHECK(NADE_TYPE_SPAWN, autocvar_g_nades_spawn); + CL_NADE_TYPE_CHECK(NADE_TYPE_HEAL, autocvar_g_nades_heal); + CL_NADE_TYPE_CHECK(NADE_TYPE_MONSTER, autocvar_g_nades_pokenade); + CL_NADE_TYPE_CHECK(NADE_TYPE_ENTRAP, autocvar_g_nades_entrap); + CL_NADE_TYPE_CHECK(NADE_TYPE_VEIL, autocvar_g_nades_veil); + CL_NADE_TYPE_CHECK(NADE_TYPE_AMMO, autocvar_g_nades_ammo); + CL_NADE_TYPE_CHECK(NADE_TYPE_DARKNESS, autocvar_g_nades_darkness); + } + return NADE_TYPE_NORMAL.m_id; // default to NADE_TYPE_NORMAL for unknown nade types +#undef CL_NADE_TYPE_CHECK +} + +MUTATOR_HOOKFUNCTION(nades, VehicleEnter) +{ + entity player = M_ARGV(0, entity); + + if(player.nade) + toss_nade(player, true, '0 0 100', max(player.nade.wait, time + 0.05)); +} + +CLASS(NadeOffhand, OffhandWeapon) + METHOD(NadeOffhand, offhand_think, void(NadeOffhand this, entity player, bool key_pressed)) + { + entity held_nade = player.nade; + + if (!CanThrowNade(player)) return; + if (!(time > player.nade_refire)) return; + if (key_pressed) { + if (!held_nade) { + nade_prime(player); + held_nade = player.nade; + } + } else if (time >= held_nade.nade_time_primed + 1) { + if (held_nade) { + makevectors(player.v_angle); + float _force = time - held_nade.nade_time_primed; + _force /= autocvar_g_nades_nade_lifetime; + _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce)); + vector dir = (v_forward * 0.7 + v_up * 0.2 + v_right * 0.1); + dir = W_CalculateSpread(dir, autocvar_g_nades_spread, autocvar_g_weaponspreadfactor, autocvar_g_projectiles_spread_style); + toss_nade(player, false, dir * _force, 0); + } + } + } +ENDCLASS(NadeOffhand) +NadeOffhand OFFHAND_NADE; +REGISTER_MUTATOR(nades, autocvar_g_nades) +{ + MUTATOR_ONADD + { + OFFHAND_NADE = NEW(NadeOffhand); + } + return 0; +} + +MUTATOR_HOOKFUNCTION(nades, ForbidThrowCurrentWeapon, CBC_ORDER_LAST) +{ + entity player = M_ARGV(0, entity); + + if (player.offhand != OFFHAND_NADE || (STAT(WEAPONS, player) & WEPSET(HOOK)) || autocvar_g_nades_override_dropweapon) { + nades_CheckThrow(player); + return true; + } +} + +#ifdef IN_REVIVING_RANGE + #undef IN_REVIVING_RANGE +#endif + +// returns true if player is reviving it +#define IN_REVIVING_RANGE(player, it, revive_extra_size) \ + (it != player && !IS_DEAD(it) && SAME_TEAM(it, player) \ + && boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax)) + +MUTATOR_HOOKFUNCTION(nades, PlayerPreThink) +{ + entity player = M_ARGV(0, entity); + + if (!IS_PLAYER(player)) { return; } + + if (player.nade && (player.offhand != OFFHAND_NADE || (STAT(WEAPONS, player) & WEPSET(HOOK)))) + OFFHAND_NADE.offhand_think(OFFHAND_NADE, player, player.nade_altbutton); + + entity held_nade = player.nade; + if (held_nade) + { + STAT(NADE_TIMER, player) = bound(0, (time - held_nade.nade_time_primed) / held_nade.nade_lifetime, 1); + // LOG_TRACEF("%d %d", STAT(NADE_TIMER, player), time - held_nade.nade_time_primed); + makevectors(player.angles); + held_nade.velocity = player.velocity; + setorigin(held_nade, player.origin + player.view_ofs + v_forward * 8 + v_right * -8 + v_up * 0); + held_nade.angles_y = player.angles.y; + + if (time + 0.1 >= held_nade.wait) + { + toss_nade(player, false, '0 0 0', time + 0.05); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_NADE_THROW); + } + } + + if(IS_PLAYER(player)) + { + if ( autocvar_g_nades_bonus && autocvar_g_nades ) + { + entity key; + float key_count = 0; + FOR_EACH_KH_KEY(key) if(key.owner == player) { ++key_count; } + + float time_score; + if(GameRules_scoring_is_vip(player)) + time_score = autocvar_g_nades_bonus_score_time_flagcarrier; + else + time_score = autocvar_g_nades_bonus_score_time; + + if(key_count) + time_score = autocvar_g_nades_bonus_score_time_flagcarrier * key_count; // multiply by the number of keys the player is holding + + if(autocvar_g_nades_bonus_client_select) + { + STAT(NADE_BONUS_TYPE, player) = nades_CheckTypes(player, CS_CVAR(player).cvar_cl_nade_type); + player.pokenade_type = CS_CVAR(player).cvar_cl_pokenade_type; + } + else + { + STAT(NADE_BONUS_TYPE, player) = autocvar_g_nades_bonus_type; + player.pokenade_type = autocvar_g_nades_pokenade_monster_type; + } + + STAT(NADE_BONUS_TYPE, player) = bound(0, STAT(NADE_BONUS_TYPE, player), REGISTRY_COUNT(Nades)); + + if(STAT(NADE_BONUS_SCORE, player) >= 0 && autocvar_g_nades_bonus_score_max) + nades_GiveBonus(player, time_score / autocvar_g_nades_bonus_score_max); + } + else + { + STAT(NADE_BONUS, player) = STAT(NADE_BONUS_SCORE, player) = 0; + } + + nade_veil_Apply(player); + } + + if (!(frametime && IS_PLAYER(player))) + return true; + + entity revivers_last = NULL; + entity revivers_first = NULL; + + bool player_is_reviving = false; + int n = 0; + vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; + FOREACH_CLIENT(IS_PLAYER(it) && IN_REVIVING_RANGE(player, it, revive_extra_size), { + // check if player is reviving anyone + if (STAT(FROZEN, it) == FROZEN_TEMP_DYING) + { + if ((STAT(FROZEN, player) == FROZEN_TEMP_DYING)) + continue; + if (!IN_REVIVING_RANGE(player, it, revive_extra_size)) + continue; + player_is_reviving = true; + break; + } + + if (!(STAT(FROZEN, player) == FROZEN_TEMP_DYING)) + continue; // both player and it are NOT frozen + if (revivers_last) + revivers_last.chain = it; + revivers_last = it; + if (!revivers_first) + revivers_first = it; + ++n; + }); + if (revivers_last) + revivers_last.chain = NULL; + + if (!n) // no teammate nearby + { + // freezetag already resets revive progress + if (!g_freezetag && !STAT(FROZEN, player) && !player_is_reviving) + STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody + } + else if (n > 0 && STAT(FROZEN, player) == FROZEN_TEMP_DYING) // OK, there is at least one teammate reviving us + { + STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); + // undo what PlayerPreThink did + STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * player.revive_speed, 1); + SetResource(player, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * start_health)); + + if(STAT(REVIVE_PROGRESS, player) >= 1) + { + Unfreeze(player, false); + + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, revivers_first.netname); + Send_Notification(NOTIF_ONE, revivers_first, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname); + } + + for(entity it = revivers_first; it; it = it.chain) + STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player); + } +} + +MUTATOR_HOOKFUNCTION(nades, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + if (StatusEffects_active(STATUSEFFECT_SpawnShield, player)) + player.nade_refire = StatusEffects_gettime(STATUSEFFECT_SpawnShield, player); + else + player.nade_refire = time; + + if (!autocvar_g_nades_onspawn) + player.nade_refire += autocvar_g_nades_nade_refire; + + if(autocvar_g_nades_bonus_client_select) + STAT(NADE_BONUS_TYPE, player) = CS_CVAR(player).cvar_cl_nade_type; + + STAT(NADE_TIMER, player) = 0; + + if (!player.offhand) player.offhand = OFFHAND_NADE; + + if(player.nade_spawnloc) + { + setorigin(player, player.nade_spawnloc.origin); + player.nade_spawnloc.cnt -= 1; + + if(player.nade_spawnloc.cnt <= 0) + { + delete(player.nade_spawnloc); + player.nade_spawnloc = NULL; + } + + nade_spawn_SetSpawnHealth(player); + } +} + +MUTATOR_HOOKFUNCTION(nades, PlayerDies, CBC_ORDER_LAST) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + + if(frag_target.nade) + if(!STAT(FROZEN, frag_target) || !autocvar_g_freezetag_revive_nade) + toss_nade(frag_target, true, '0 0 100', max(frag_target.nade.wait, time + 0.05)); + + if(IS_PLAYER(frag_attacker)) + { + float killcount_bonus = ((CS(frag_attacker).killcount >= 1) ? bound(0, autocvar_g_nades_bonus_score_minor * CS(frag_attacker).killcount, autocvar_g_nades_bonus_score_medium) + : autocvar_g_nades_bonus_score_minor); + if (SAME_TEAM(frag_attacker, frag_target) || frag_attacker == frag_target) + nades_RemoveBonus(frag_attacker); + else if(GameRules_scoring_is_vip(frag_target)) + nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_medium); + else if(autocvar_g_nades_bonus_score_spree && CS(frag_attacker).killcount > 1) + { + #define SPREE_ITEM(counta,countb,center,normal,gentle) \ + case counta: { nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_spree); break; } + switch(CS(frag_attacker).killcount) + { + KILL_SPREE_LIST + default: nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); break; + } + #undef SPREE_ITEM + } + else + nades_GiveBonus(frag_attacker, killcount_bonus); + } + + nades_RemoveBonus(frag_target); +} + +MUTATOR_HOOKFUNCTION(nades, Damage_Calculate) +{ + entity frag_inflictor = M_ARGV(0, entity); + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(3, float); + + if(autocvar_g_freezetag_revive_nade && STAT(FROZEN, frag_target) && frag_attacker == frag_target && frag_deathtype == DEATH_NADE.m_id) + if(time - frag_inflictor.toss_time <= 0.1) + { + Unfreeze(frag_target, false); + SetResource(frag_target, RES_HEALTH, autocvar_g_freezetag_revive_nade_health); + Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3); + M_ARGV(4, float) = 0; + M_ARGV(6, vector) = '0 0 0'; + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_NADE, frag_target.netname); + Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF); + } +} + +MUTATOR_HOOKFUNCTION(nades, MonsterDies) +{ + entity frag_target = M_ARGV(0, entity); + entity frag_attacker = M_ARGV(1, entity); + + if(IS_PLAYER(frag_attacker)) + if(DIFF_TEAM(frag_attacker, frag_target)) + if(!(frag_target.spawnflags & MONSTERFLAG_SPAWNED)) + nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); +} + +MUTATOR_HOOKFUNCTION(nades, DropSpecialItems) +{ + entity frag_target = M_ARGV(0, entity); + + if(frag_target.nade) + toss_nade(frag_target, true, '0 0 0', time + 0.05); +} + +void nades_RemovePlayer(entity this) +{ + nades_Clear(this); + nades_RemoveBonus(this); +} + +MUTATOR_HOOKFUNCTION(nades, MakePlayerObserver) { entity player = M_ARGV(0, entity); nades_RemovePlayer(player); } +MUTATOR_HOOKFUNCTION(nades, ClientDisconnect) { entity player = M_ARGV(0, entity); nades_RemovePlayer(player); } +MUTATOR_HOOKFUNCTION(nades, reset_map_global) +{ + FOREACH_CLIENT(IS_PLAYER(it), + { + nades_RemovePlayer(it); + }); +} + +MUTATOR_HOOKFUNCTION(nades, SpectateCopy) +{ + entity spectatee = M_ARGV(0, entity); + entity client = M_ARGV(1, entity); + + STAT(NADE_TIMER, client) = STAT(NADE_TIMER, spectatee); + STAT(NADE_BONUS_TYPE, client) = STAT(NADE_BONUS_TYPE, spectatee); + client.pokenade_type = spectatee.pokenade_type; + STAT(NADE_BONUS, client) = STAT(NADE_BONUS, spectatee); + STAT(NADE_BONUS_SCORE, client) = STAT(NADE_BONUS_SCORE, spectatee); +} + +MUTATOR_HOOKFUNCTION(nades, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Nades"); +} + +MUTATOR_HOOKFUNCTION(nades, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":Nades"); +} diff --git a/qcsrc/common/mutators/mutator/nades/sv_nades.qh b/qcsrc/common/mutators/mutator/nades/sv_nades.qh new file mode 100644 index 000000000..0dd01dcbf --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/sv_nades.qh @@ -0,0 +1,73 @@ +#pragma once + +#include "nades.qh" + +bool autocvar_g_nades; +bool autocvar_g_nades_override_dropweapon = true; +vector autocvar_g_nades_throw_offset; +bool autocvar_g_nades_onspawn; +int autocvar_g_nades_spawn_count; +float autocvar_g_nades_spawn_health_respawn; +float autocvar_g_nades_spawn_destroy_damage = 25; +bool autocvar_g_nades_pickup = true; +float autocvar_g_nades_pickup_time = 2; +float autocvar_g_nades_nade_lifetime; +float autocvar_g_nades_nade_minforce; +float autocvar_g_nades_nade_maxforce; +float autocvar_g_nades_nade_health; +float autocvar_g_nades_nade_refire; +float autocvar_g_nades_nade_damage; +float autocvar_g_nades_nade_edgedamage; +float autocvar_g_nades_nade_radius; +float autocvar_g_nades_nade_force; +int autocvar_g_nades_nade_newton_style; +int autocvar_g_nades_nade_type; +bool autocvar_g_nades_client_select; + +bool autocvar_g_nades_bonus; +int autocvar_g_nades_bonus_type; +bool autocvar_g_nades_bonus_client_select; +bool autocvar_g_nades_bonus_onstrength; +bool autocvar_g_nades_bonus_only; +int autocvar_g_nades_bonus_max; +int autocvar_g_nades_bonus_score_max; +int autocvar_g_nades_bonus_score_time; +int autocvar_g_nades_bonus_score_time_flagcarrier; +int autocvar_g_nades_bonus_score_minor; +int autocvar_g_nades_bonus_score_low; +int autocvar_g_nades_bonus_score_high; +int autocvar_g_nades_bonus_score_medium; +int autocvar_g_nades_bonus_score_spree; + + +.entity nade; +.entity fake_nade; +.float nade_refire; +.float nade_special_time; +.string pokenade_type; +.entity nade_damage_target; +.int cvar_cl_nade_type; +.string cvar_cl_pokenade_type; +.float toss_time; +.float nade_show_particles; + +bool orb_send(entity this, entity to, int sf); + +// Remove nades that are being thrown +void nades_Clear(entity player); + +// Give a bonus grenade to a player +void nades_GiveBonus(entity player, float score); + +/** + * called to adjust nade damage and force on hit + */ +#define EV_Nade_Damage(i, o) \ + /** nade */ i(entity, MUTATOR_ARGV_0_entity) \ + /** weapon */ i(entity, MUTATOR_ARGV_1_entity) \ + /** force */ i(vector, MUTATOR_ARGV_2_vector) \ + /**/ o(vector, MUTATOR_ARGV_2_vector) \ + /** damage */ i(float, MUTATOR_ARGV_3_float) \ + /**/ o(float, MUTATOR_ARGV_3_float) \ + /**/ +MUTATOR_HOOKABLE(Nade_Damage, EV_Nade_Damage); diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc index afcbaf27e..c55a10255 100644 --- a/qcsrc/server/client.qc +++ b/qcsrc/server/client.qc @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/qcsrc/server/compat/quake3.qc b/qcsrc/server/compat/quake3.qc index 6e40357df..9565c5b7e 100644 --- a/qcsrc/server/compat/quake3.qc +++ b/qcsrc/server/compat/quake3.qc @@ -4,8 +4,7 @@ #include #include #include -#include -#include +#include #include #include #include diff --git a/qcsrc/server/damage.qc b/qcsrc/server/damage.qc index 9e00293d3..b67afb712 100644 --- a/qcsrc/server/damage.qc +++ b/qcsrc/server/damage.qc @@ -8,8 +8,7 @@ #include #include #include -#include -#include +#include #include #include #include diff --git a/qcsrc/server/items/items.qc b/qcsrc/server/items/items.qc index 593d238a0..8e42d8865 100644 --- a/qcsrc/server/items/items.qc +++ b/qcsrc/server/items/items.qc @@ -7,8 +7,7 @@ #include #include #include -#include -#include +#include #include #include #include -- 2.39.5