From 74ce314c8063504b3e5e70b9c30bcb6062dee49e Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 18 Apr 2025 20:56:05 +0000 Subject: [PATCH] Add a Frozen status effect for ice nades separate from the frozen state in Freeze Tag Entities with the Frozen status effect can be damaged and will die if their health reaches 0. They are not healed when the effect is removed Flags and other items can be interacted with (besides vehicles) and are not lost when becoming frozen --- mutators.cfg | 1 - notifications.cfg | 5 - qcsrc/client/mutators/events.qh | 3 + qcsrc/client/view.qc | 14 +- qcsrc/common/deathtypes/all.inc | 1 - qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc | 2 +- .../gamemode/freezetag/cl_freezetag.qc | 33 ++ .../gamemode/freezetag/sv_freezetag.qc | 318 ++++++++++++++++-- .../gamemode/freezetag/sv_freezetag.qh | 13 + .../gamemode/keepaway/sv_keepaway.qc | 2 +- .../gamemodes/gamemode/nexball/sv_nexball.qc | 3 +- .../gamemodes/gamemode/nexball/sv_weapon.qc | 3 +- .../gamemode/onslaught/sv_onslaught.qc | 55 ++- qcsrc/common/gamemodes/gamemode/tka/sv_tka.qc | 1 - qcsrc/common/monsters/monster/mage.qc | 3 +- qcsrc/common/monsters/sv_monsters.qc | 59 +--- .../common/mutators/mutator/buffs/sv_buffs.qc | 7 - .../common/mutators/mutator/nades/nade/ice.qc | 10 +- .../common/mutators/mutator/nades/nade/ice.qh | 1 - .../common/mutators/mutator/nades/sv_nades.qc | 69 +--- .../mutators/mutator/overkill/sv_overkill.qc | 2 +- .../status_effects/cl_status_effects.qc | 3 + .../status_effects/status_effect/_mod.inc | 1 + .../status_effects/status_effect/_mod.qh | 1 + .../status_effects/status_effect/frozen.qc | 78 +++++ .../status_effects/status_effect/frozen.qh | 11 + .../mutator/status_effects/status_effects.qh | 2 +- .../status_effects/sv_status_effects.qc | 7 + .../mutators/mutator/walljump/walljump.qc | 2 +- qcsrc/common/notifications/all.inc | 7 +- qcsrc/common/physics/player.qc | 2 +- qcsrc/common/physics/player.qh | 2 +- qcsrc/common/stats.qh | 2 +- qcsrc/common/turrets/sv_turrets.qc | 2 - qcsrc/common/vehicles/sv_vehicles.qc | 3 +- qcsrc/common/weapons/weapon/fireball.qc | 2 +- qcsrc/server/bot/default/aim.qc | 3 - qcsrc/server/bot/default/havocbot/havocbot.qc | 6 +- qcsrc/server/bot/default/navigation.qc | 1 + qcsrc/server/client.qc | 48 +-- qcsrc/server/clientkill.qc | 2 +- qcsrc/server/command/common.qc | 1 - qcsrc/server/command/vote.qc | 2 - qcsrc/server/damage.qc | 155 +-------- qcsrc/server/damage.qh | 22 -- qcsrc/server/hook.qc | 6 +- qcsrc/server/items/items.qc | 1 - qcsrc/server/main.qc | 1 + qcsrc/server/mutators/events.qh | 26 +- qcsrc/server/player.qc | 20 +- qcsrc/server/utils.qh | 2 +- qcsrc/server/weapons/accuracy.qc | 3 +- qcsrc/server/weapons/weaponsystem.qc | 2 +- 53 files changed, 529 insertions(+), 502 deletions(-) create mode 100644 qcsrc/common/mutators/mutator/status_effects/status_effect/frozen.qc create mode 100644 qcsrc/common/mutators/mutator/status_effects/status_effect/frozen.qh diff --git a/mutators.cfg b/mutators.cfg index cd6ac0c870..634744e738 100644 --- a/mutators.cfg +++ b/mutators.cfg @@ -263,7 +263,6 @@ set g_nades_napalm_fountain_radius 130 "distance from the fountain" // Ice (3) set g_nades_ice 1 "Ice nade: freezes and reduces health; \"1\" = allow client selection of this nade type" // script-ignore set g_nades_ice_freeze_time 3 "how long the ice field will last" -set g_nades_ice_health 0 "how much health the player will have after being unfrozen" set g_nades_ice_explode 0 "whether the ice nade should explode again once the ice field dissipated" set g_nades_ice_teamcheck 2 "\"0\" = freezes everyone including the player who threw the nade, \"1\" = freezes enemies and teammates, \"2\" = freezes only enemies" diff --git a/notifications.cfg b/notifications.cfg index 1f891f7cec..98db7e9eba 100644 --- a/notifications.cfg +++ b/notifications.cfg @@ -139,7 +139,6 @@ seta notification_INFO_DEATH_MURDER_MONSTER "1" "\"0\" = off, \"1\" = print to c seta notification_INFO_DEATH_MURDER_NADE "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_MURDER_NADE_HEAL "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_MURDER_NADE_ICE "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" -seta notification_INFO_DEATH_MURDER_NADE_ICE_FREEZE "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_MURDER_NADE_NAPALM "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_MURDER_SHOOTING_STAR "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_MURDER_SLIME "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" @@ -181,7 +180,6 @@ seta notification_INFO_DEATH_SELF_MON_ZOMBIE_MELEE "1" "\"0\" = off, \"1\" = pri seta notification_INFO_DEATH_SELF_NADE "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_SELF_NADE_HEAL "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_SELF_NADE_ICE "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" -seta notification_INFO_DEATH_SELF_NADE_ICE_FREEZE "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_SELF_NADE_NAPALM "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_SELF_NOAMMO "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_SELF_ROT "1" "\"0\" = off, \"1\" = print to console, \"2\" = print to console and chatbox (if notification_allow_chatboxprint is enabled)" @@ -438,7 +436,6 @@ seta notification_CENTER_DEATH_SELF_LAVA "1" "\"0\" = off, \"1\" = centerprint" seta notification_CENTER_DEATH_SELF_MONSTER "1" "\"0\" = off, \"1\" = centerprint" seta notification_CENTER_DEATH_SELF_NADE "1" "\"0\" = off, \"1\" = centerprint" seta notification_CENTER_DEATH_SELF_NADE_HEAL "1" "\"0\" = off, \"1\" = centerprint" -seta notification_CENTER_DEATH_SELF_NADE_ICE_FREEZE "1" "\"0\" = off, \"1\" = centerprint" seta notification_CENTER_DEATH_SELF_NADE_NAPALM "1" "\"0\" = off, \"1\" = centerprint" seta notification_CENTER_DEATH_SELF_NOAMMO "1" "\"0\" = off, \"1\" = centerprint" seta notification_CENTER_DEATH_SELF_ROT "1" "\"0\" = off, \"1\" = centerprint" @@ -596,7 +593,6 @@ seta notification_DEATH_MURDER_MONSTER "1" "enable this multiple notification" seta notification_DEATH_MURDER_NADE "1" "enable this multiple notification" seta notification_DEATH_MURDER_NADE_HEAL "1" "enable this multiple notification" seta notification_DEATH_MURDER_NADE_ICE "1" "enable this multiple notification" -seta notification_DEATH_MURDER_NADE_ICE_FREEZE "1" "enable this multiple notification" seta notification_DEATH_MURDER_NADE_NAPALM "1" "enable this multiple notification" seta notification_DEATH_MURDER_SHOOTING_STAR "1" "enable this multiple notification" seta notification_DEATH_MURDER_SLIME "1" "enable this multiple notification" @@ -638,7 +634,6 @@ seta notification_DEATH_SELF_MON_ZOMBIE_MELEE "1" "enable this multiple notifica seta notification_DEATH_SELF_NADE "1" "enable this multiple notification" seta notification_DEATH_SELF_NADE_HEAL "1" "enable this multiple notification" seta notification_DEATH_SELF_NADE_ICE "1" "enable this multiple notification" -seta notification_DEATH_SELF_NADE_ICE_FREEZE "1" "enable this multiple notification" seta notification_DEATH_SELF_NADE_NAPALM "1" "enable this multiple notification" seta notification_DEATH_SELF_NOAMMO "1" "enable this multiple notification" seta notification_DEATH_SELF_ROT "1" "enable this multiple notification" diff --git a/qcsrc/client/mutators/events.qh b/qcsrc/client/mutators/events.qh index 6f6f34399f..0b5a9a6aaa 100644 --- a/qcsrc/client/mutators/events.qh +++ b/qcsrc/client/mutators/events.qh @@ -116,6 +116,9 @@ MUTATOR_HOOKABLE(Ent_Init, EV_NO_ARGS); /**/ MUTATOR_HOOKABLE(HUD_Draw_overlay, EV_HUD_Draw_overlay); +/** return true to hide the damage HUD overlay */ +MUTATOR_HOOKABLE(HUD_Damage_show, EV_NO_ARGS); + MUTATOR_HOOKABLE(HUD_Powerups_add, EV_NO_ARGS); /** return true to show the physics HUD panel when optional mode is enabled */ diff --git a/qcsrc/client/view.qc b/qcsrc/client/view.qc index 6fc7d063a5..238fd4c87c 100644 --- a/qcsrc/client/view.qc +++ b/qcsrc/client/view.qc @@ -741,7 +741,7 @@ int WantEventchase(entity this, bool want_vehiclechase) return 1; if(MUTATOR_CALLHOOK(WantEventchase, this)) return 1; - if(autocvar_cl_eventchase_frozen && STAT(FROZEN)) + if(autocvar_cl_eventchase_frozen && StatusEffects_active(STATUSEFFECT_Frozen, this)) return 1; if(autocvar_cl_eventchase_death && (STAT(HEALTH) <= 0)) { @@ -997,20 +997,12 @@ void HUD_Draw(entity this) if (alpha_multipl > 0) drawfill('0 0 0', vec2(vid_conwidth, vid_conheight), col, autocvar_hud_colorflash_alpha * alpha_multipl, DRAWFLAG_ADDITIVE); } - else if(STAT(FROZEN)) - { - vector col = '0.25 0.90 1'; - float col_fade = max(0, STAT(REVIVE_PROGRESS) * 2 - 1); - float alpha_fade = 0.3 + 0.7 * (1 - max(0, STAT(REVIVE_PROGRESS) * 4 - 3)); - if(col_fade) - col += vec3(col_fade, -col_fade, -col_fade); - drawfill('0 0 0', vec2(vid_conwidth, vid_conheight), col, autocvar_hud_colorflash_alpha * alpha_fade, DRAWFLAG_ADDITIVE); - } } HUD_Scale_Enable(); if(!intermission) { + // TODO: mutator hook for these? maybe something more generic! if(STAT(NADE_TIMER) && autocvar_cl_nade_timer) // give nade top priority, as it's a matter of life and death { vector col = '0.25 0.90 1' + vec3(STAT(NADE_TIMER), -STAT(NADE_TIMER), -STAT(NADE_TIMER)); @@ -1244,7 +1236,7 @@ void HUD_Contents() // provides some effects to the postprocessing function void HUD_Damage() { - if(!autocvar_hud_damage || STAT(FROZEN)) + if(!autocvar_hud_damage || MUTATOR_CALLHOOK(HUD_Damage_show)) return; vector splash_pos = '0 0 0', splash_size = '0 0 0'; diff --git a/qcsrc/common/deathtypes/all.inc b/qcsrc/common/deathtypes/all.inc index 19de656e0d..f5e93ed9ff 100644 --- a/qcsrc/common/deathtypes/all.inc +++ b/qcsrc/common/deathtypes/all.inc @@ -22,7 +22,6 @@ REGISTER_DEATHTYPE(MONSTER_ZOMBIE_MELEE, DEATH_SELF_MON_ZOMBIE_MELEE, DEAT REGISTER_DEATHTYPE(NADE, DEATH_SELF_NADE, DEATH_MURDER_NADE, NULL, NULL, "") REGISTER_DEATHTYPE(NADE_NAPALM, DEATH_SELF_NADE_NAPALM, DEATH_MURDER_NADE_NAPALM, NULL, NULL, "") REGISTER_DEATHTYPE(NADE_ICE, DEATH_SELF_NADE_ICE, DEATH_MURDER_NADE_ICE, NULL, NULL, "") -REGISTER_DEATHTYPE(NADE_ICE_FREEZE, DEATH_SELF_NADE_ICE_FREEZE, DEATH_MURDER_NADE_ICE_FREEZE, NULL, NULL, "") REGISTER_DEATHTYPE(NADE_HEAL, DEATH_SELF_NADE_HEAL, DEATH_MURDER_NADE_HEAL, NULL, NULL, "") REGISTER_DEATHTYPE(NOAMMO, DEATH_SELF_NOAMMO, NULL, NULL, NULL, "") REGISTER_DEATHTYPE(ROT, DEATH_SELF_ROT, NULL, NULL, NULL, "") diff --git a/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc b/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc index 2035956944..43e03d65fa 100644 --- a/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc +++ b/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc @@ -1142,8 +1142,8 @@ METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher)) } // special touch behaviors + // TODO: mutator hook to prevent picking up objectives if(IS_INDEPENDENT_PLAYER(toucher)) { return; } - else if(STAT(FROZEN, toucher)) { return; } else if(IS_VEHICLE(toucher)) { if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner) diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/cl_freezetag.qc b/qcsrc/common/gamemodes/gamemode/freezetag/cl_freezetag.qc index df4931a371..8a3eeeb014 100644 --- a/qcsrc/common/gamemodes/gamemode/freezetag/cl_freezetag.qc +++ b/qcsrc/common/gamemodes/gamemode/freezetag/cl_freezetag.qc @@ -13,3 +13,36 @@ void HUD_Mod_FreezeTag(vector myPos, vector mySize) HUD_Mod_CA_Draw(myPos, mySize, autocvar_hud_panel_modicons_freezetag_layout); } + +REGISTER_MUTATOR(cl_ft, true); + +MUTATOR_HOOKFUNCTION(cl_ft, WantEventchase) +{ + if(autocvar_cl_eventchase_frozen && ISGAMETYPE(FREEZETAG) && STAT(FROZEN)) + return true; + return false; +} + +MUTATOR_HOOKFUNCTION(cl_ft, HUD_Draw_overlay) +{ + if(STAT(FROZEN) && ISGAMETYPE(FREEZETAG)) + { + vector col = '0.25 0.90 1'; + float col_fade = max(0, STAT(REVIVE_PROGRESS) * 2 - 1); + float alpha_fade = 0.3 + 0.7 * (1 - max(0, STAT(REVIVE_PROGRESS) * 4 - 3)); + if(col_fade) + col += vec3(col_fade, -col_fade, -col_fade); + + M_ARGV(0, vector) = col; + M_ARGV(1, float) = alpha_fade; + return true; + } + return false; +} + +MUTATOR_HOOKFUNCTION(cl_ft, HUD_Damage_show) +{ + if(ISGAMETYPE(FREEZETAG) && STAT(FROZEN)) + return true; + return false; +} diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc b/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc index a9f0143a87..e67936089a 100644 --- a/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc +++ b/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc @@ -28,7 +28,7 @@ void freezetag_count_alive_players() FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it), { ++total_players; - if (GetResource(it, RES_HEALTH) < 1 || STAT(FROZEN, it) == FROZEN_NORMAL) + if (GetResource(it, RES_HEALTH) < 1 || STAT(FROZEN, it)) { continue; } @@ -153,7 +153,7 @@ entity freezetag_LastPlayerForTeam(entity this) { entity last_pl = NULL; FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), { - if (STAT(FROZEN, it) != FROZEN_NORMAL && GetResource(it, RES_HEALTH) >= 1) + if (!STAT(FROZEN, it) && GetResource(it, RES_HEALTH) >= 1) { if (!last_pl) last_pl = it; @@ -196,26 +196,114 @@ void freezetag_Add_Score(entity targ, entity attacker) // else nothing - got frozen by the game type rules themselves } +void freezetag_Ice_Think(entity this) +{ + if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this) + { + delete(this); + return; + } + vector ice_org = this.owner.origin - '0 0 16'; + if (this.origin != ice_org) + setorigin(this, ice_org); + this.nextthink = time; +} + // to be called when the player is frozen by freezetag (on death, spectator join etc), gives the score void freezetag_Freeze(entity targ, entity attacker) { - if(STAT(FROZEN, targ)) + if(!IS_PLAYER(targ) || STAT(FROZEN, targ)) return; targ.freezetag_frozen_time = time; if (autocvar_g_freezetag_revive_auto && autocvar_g_freezetag_frozen_maxtime > 0) targ.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime; - Freeze(targ, 0, FROZEN_NORMAL, true); + STAT(FROZEN, targ) = true; + STAT(REVIVE_PROGRESS, targ) = 0; + SetResource(targ, RES_HEALTH, 1); + targ.revive_speed = 0; + if(targ.bot_attack) + IL_REMOVE(g_bot_targets, targ); + targ.bot_attack = false; + targ.freeze_time = time; + + entity ice = new(ice); + ice.owner = targ; + ice.scale = targ.scale; + // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player + setthink(ice, freezetag_Ice_Think); + ice.nextthink = time; + ice.frame = floor(random() * 21); // ice model has 20 different looking frames + setmodel(ice, MDL_ICE); + ice.alpha = 1; + ice.colormod = Team_ColorRGB(targ.team); + ice.glowmod = ice.colormod; + targ.iceblock = ice; + targ.revival_time = 0; + + freezetag_Ice_Think(ice); + + RemoveGrapplingHooks(targ); + + FOREACH_CLIENT(IS_PLAYER(it), + { + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + if(it.(weaponentity).hook.aiment == targ) + RemoveHook(it.(weaponentity).hook); + } + }); + + WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT); freezetag_count_alive_players(); freezetag_Add_Score(targ, attacker); } +void freezetag_Unfreeze(entity targ, bool reset_health) +{ + if(!STAT(FROZEN, targ)) + return; + + if (reset_health) + SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health)); + + targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen; + + STAT(FROZEN, targ) = false; + STAT(REVIVE_PROGRESS, targ) = 0; + targ.revival_time = time; + if(!targ.bot_attack) + IL_PUSH(g_bot_targets, targ); + targ.bot_attack = true; + + WaypointSprite_Kill(targ.waypointsprite_attached); + + FOREACH_CLIENT(IS_PLAYER(it), + { + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + if(it.(weaponentity).hook.aiment == targ) + RemoveHook(it.(weaponentity).hook); + } + }); + + // remove the ice block + if(targ.iceblock) + delete(targ.iceblock); + targ.iceblock = NULL; + + targ.freezetag_frozen_time = 0; + targ.freezetag_frozen_timeout = 0; +} + bool freezetag_isEliminated(entity e) { - if(IS_PLAYER(e) && (STAT(FROZEN, e) == FROZEN_NORMAL || IS_DEAD(e))) + if(IS_PLAYER(e) && (STAT(FROZEN, e) || IS_DEAD(e))) return true; return false; } @@ -233,7 +321,7 @@ void havocbot_goalrating_ft_freeplayers(entity this, float ratingscale, vector o entity best_pl = NULL; float best_dist2 = FLOAT_MAX; FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), { - if (STAT(FROZEN, it) == FROZEN_NORMAL) + if (STAT(FROZEN, it)) { if(vdist(it.origin - org, >, sradius)) continue; @@ -269,12 +357,12 @@ void havocbot_role_ft_offense(entity this) // Count how many players on team are unfrozen. int unfrozen = 0; - FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && STAT(FROZEN, it) != FROZEN_NORMAL, { + FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !STAT(FROZEN, it), { unfrozen++; }); // If only one left on team or if role has timed out then start trying to free players. - if ((!unfrozen && STAT(FROZEN, this) != FROZEN_NORMAL) || time > this.havocbot_role_timeout) + if ((!unfrozen && !STAT(FROZEN, this)) || time > this.havocbot_role_timeout) { LOG_TRACE("changing role to freeing"); this.havocbot_role = havocbot_role_ft_freeing; @@ -331,9 +419,9 @@ void havocbot_role_ft_freeing(entity this) void ft_RemovePlayer(entity this) { - if (STAT(FROZEN, this) != FROZEN_NORMAL) + if (!STAT(FROZEN, this)) freezetag_LastPlayerForTeam_Notify(this); - Unfreeze(this, false); + freezetag_Unfreeze(this, false); SetResourceExplicit(this, RES_HEALTH, 0); // neccessary to correctly count alive players freezetag_count_alive_players(); @@ -354,6 +442,29 @@ MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver) ft_RemovePlayer(player); } +MUTATOR_HOOKFUNCTION(ft, SpectateCopy) +{ + entity spectatee = M_ARGV(0, entity); + entity client = M_ARGV(1, entity); + + STAT(FROZEN, client) = STAT(FROZEN, spectatee); + STAT(REVIVE_PROGRESS, client) = STAT(REVIVE_PROGRESS, spectatee); +} + +MUTATOR_HOOKFUNCTION(ft, ClientKill) +{ + entity player = M_ARGV(0, entity); + + return STAT(FROZEN, player); +} + +MUTATOR_HOOKFUNCTION(ft, PlayerDied) +{ + entity player = M_ARGV(0, entity); + + freezetag_Unfreeze(player, false); +} + MUTATOR_HOOKFUNCTION(ft, PlayerDies) { entity frag_attacker = M_ARGV(1, entity); @@ -363,8 +474,8 @@ MUTATOR_HOOKFUNCTION(ft, PlayerDies) if(round_handler_IsActive()) if(round_handler_CountdownRunning()) { - if (STAT(FROZEN, frag_target) == FROZEN_NORMAL) - Unfreeze(frag_target, true); + if (STAT(FROZEN, frag_target)) + freezetag_Unfreeze(frag_target, true); freezetag_count_alive_players(); frag_target.respawn_time = time; frag_target.respawn_flags |= RESPAWN_FORCE; @@ -390,7 +501,7 @@ MUTATOR_HOOKFUNCTION(ft, PlayerDies) { // can't use freezetag_Add_Score here since it doesn't assign any points // if the attacker is not a player (e.g. triggerhurt) by design - if ((STAT(FROZEN, frag_target) != FROZEN_NORMAL) && !IS_PLAYER(frag_attacker)) + if (!STAT(FROZEN, frag_target) && !IS_PLAYER(frag_attacker)) GameRules_scoring_add(frag_target, SCORE, -1); // by restoring some health right after player death (soft-kill) @@ -412,7 +523,7 @@ MUTATOR_HOOKFUNCTION(ft, PlayerDies) frag_target.punchvector = '0 0 0'; } - if (STAT(FROZEN, frag_target) == FROZEN_NORMAL) + if (STAT(FROZEN, frag_target)) return true; freezetag_Freeze(frag_target, frag_attacker); @@ -436,6 +547,8 @@ MUTATOR_HOOKFUNCTION(ft, PlayerSpawn) { entity player = M_ARGV(0, entity); + freezetag_Unfreeze(player, false); + if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players return true; // do nothing, round is starting right now @@ -462,6 +575,21 @@ MUTATOR_HOOKFUNCTION(ft, PutClientInServer) eliminatedPlayers.SendFlags |= 1; } +MUTATOR_HOOKFUNCTION(ft, PlayerAnim) +{ + entity player = M_ARGV(0, entity); + + if(STAT(FROZEN, player)) + M_ARGV(1, int) |= ANIMSTATE_FROZEN; +} + +MUTATOR_HOOKFUNCTION(ft, reset_map_global) +{ + FOREACH_CLIENT(IS_PLAYER(it) && STAT(FROZEN, it), { + freezetag_Unfreeze(it, false); + }); +} + MUTATOR_HOOKFUNCTION(ft, reset_map_players) { FOREACH_CLIENT(IS_PLAYER(it), { @@ -481,24 +609,39 @@ MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST) return true; } -MUTATOR_HOOKFUNCTION(ft, Unfreeze) +MUTATOR_HOOKFUNCTION(ft, LockWeapon) { - entity targ = M_ARGV(0, entity); - targ.freezetag_frozen_time = 0; - targ.freezetag_frozen_timeout = 0; + entity player = M_ARGV(0, entity); + return STAT(FROZEN, player); +} + +MUTATOR_HOOKFUNCTION(ft, PlayerDamaged) +{ + entity frag_target = M_ARGV(1, entity); + return STAT(FROZEN, frag_target); +} + +MUTATOR_HOOKFUNCTION(ft, AccuracyTargetValid) +{ + entity frag_target = M_ARGV(1, entity); + + // damage to frozen players is good only if it happens in the frame they get frozen + if (STAT(FROZEN, frag_target) && time > frag_target.freeze_time) + return MUT_ACCADD_INDIFFERENT; + return MUT_ACCADD_VALID; } MUTATOR_HOOKFUNCTION(ft, Damage_Calculate) { entity frag_attacker = M_ARGV(1, entity); entity frag_target = M_ARGV(2, entity); - //float frag_deathtype = M_ARGV(3, float); - //float frag_damage = M_ARGV(4, float); + float frag_deathtype = M_ARGV(3, float); + float frag_damage = M_ARGV(4, float); vector frag_force = M_ARGV(6, vector); frag_target.freezetag_frozen_armor = GetResource(frag_target, RES_ARMOR); - if (STAT(FROZEN, frag_target) == FROZEN_NORMAL && autocvar_g_freezetag_revive_auto_reducible + if (STAT(FROZEN, frag_target) && autocvar_g_freezetag_revive_auto_reducible && autocvar_g_freezetag_frozen_maxtime > 0 && autocvar_g_freezetag_revive_auto) { float t = 0; @@ -525,6 +668,58 @@ MUTATOR_HOOKFUNCTION(ft, Damage_Calculate) frag_target.freezetag_frozen_timeout = time; } } + + if(STAT(FROZEN, frag_target) && !ITEM_DAMAGE_NEEDKILL(frag_deathtype) + && frag_deathtype != DEATH_TEAMCHANGE.m_id && frag_deathtype != DEATH_AUTOTEAMCHANGE.m_id) + { + if(autocvar_g_frozen_revive_falldamage > 0 && frag_deathtype == DEATH_FALL.m_id && frag_damage >= autocvar_g_frozen_revive_falldamage) + { + freezetag_Unfreeze(frag_target, false); + SetResource(frag_target, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health); + Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, frag_target.netname); + Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF); + } + + frag_damage = 0; + frag_force *= autocvar_g_frozen_force; + } + + if(IS_PLAYER(frag_target) && STAT(FROZEN, frag_target) + && ITEM_DAMAGE_NEEDKILL(frag_deathtype) && !autocvar_g_frozen_damage_trigger) + { + Send_Effect(EFFECT_TELEPORT, frag_target.origin, '0 0 0', 1); + + entity spot = SelectSpawnPoint(frag_target, false); + if(spot) + { + frag_damage = 0; + frag_target.deadflag = DEAD_NO; + + frag_target.angles = spot.angles; + + frag_target.effects = 0; + frag_target.effects |= EF_TELEPORT_BIT; + + frag_target.angles_z = 0; // never spawn tilted even if the spot says to + frag_target.fixangle = true; // turn this way immediately + frag_target.velocity = '0 0 0'; + frag_target.avelocity = '0 0 0'; + frag_target.punchangle = '0 0 0'; + frag_target.punchvector = '0 0 0'; + frag_target.oldvelocity = frag_target.velocity; + + frag_target.spawnorigin = spot.origin; + setorigin(frag_target, spot.origin + '0 0 1' * (1 - frag_target.mins.z - 24)); + // don't reset back to last position, even if new position is stuck in solid + frag_target.oldorigin = frag_target.origin; + + Send_Effect(EFFECT_TELEPORT, frag_target.origin, '0 0 0', 1); + } + } + + M_ARGV(4, float) = frag_damage; + M_ARGV(6, vector) = frag_force; } #ifdef IN_REVIVING_RANGE @@ -545,7 +740,7 @@ MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) return true; entity player = M_ARGV(0, entity); - //if (STAT(FROZEN, player) == FROZEN_NORMAL) + //if (STAT(FROZEN, player)) //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout) //player.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (player.freezetag_frozen_timeout - time) / (player.freezetag_frozen_timeout - player.freezetag_frozen_time); @@ -562,9 +757,9 @@ MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; FOREACH_CLIENT(IS_PLAYER(it), { // check if player is reviving anyone - if (STAT(FROZEN, it) == FROZEN_NORMAL) + if (STAT(FROZEN, it)) { - if ((STAT(FROZEN, player) == FROZEN_NORMAL)) + if ((STAT(FROZEN, player))) continue; if (!IN_REVIVING_RANGE(player, it, revive_extra_size)) continue; @@ -572,13 +767,13 @@ MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) break; } - if (!(STAT(FROZEN, player) == FROZEN_NORMAL)) + if (!(STAT(FROZEN, player))) continue; // both player and it are NOT frozen if (!IN_REVIVING_RANGE(player, it, revive_extra_size)) continue; // found a teammate that is reviving player - if (autocvar_g_freezetag_revive_time_to_score > 0 && STAT(FROZEN, player) == FROZEN_NORMAL) + if (autocvar_g_freezetag_revive_time_to_score > 0 && STAT(FROZEN, player)) { it.freezetag_revive_time += frametime / autocvar_g_freezetag_revive_time_to_score; while (it.freezetag_revive_time > 1) @@ -599,12 +794,12 @@ MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) // allow normal revival during automatic revival // (if we wouldn't allow it then freezetag_frozen_timeout should be checked too in the previous loop) - //if (STAT(FROZEN, player) == FROZEN_NORMAL) // redundant check + //if (STAT(FROZEN, player)) // redundant check if (!n && player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout) n = -1; float base_progress = 0; - if (STAT(FROZEN, player) == FROZEN_NORMAL && autocvar_g_freezetag_revive_auto + if (STAT(FROZEN, player) && autocvar_g_freezetag_revive_auto && autocvar_g_freezetag_frozen_maxtime > 0 && autocvar_g_freezetag_revive_auto_progress) { // NOTE if auto-revival is in progress, manual revive speed is reduced so that it always takes the same amount of time @@ -614,7 +809,7 @@ MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) if (!n) // no teammate nearby { float clearspeed = autocvar_g_freezetag_revive_clearspeed; - if (STAT(FROZEN, player) == FROZEN_NORMAL) + if (STAT(FROZEN, player)) { if (autocvar_g_freezetag_revive_time_to_score > 0) { @@ -634,7 +829,7 @@ MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) else if (!STAT(FROZEN, player) && !player_is_reviving) STAT(REVIVE_PROGRESS, player) = base_progress; // thawing nobody } - else if (STAT(FROZEN, player) == FROZEN_NORMAL) // OK, there is at least one teammate reviving us + else if (STAT(FROZEN, player)) // OK, there is at least one teammate reviving us { float spd = autocvar_g_freezetag_revive_speed_t2s; if (autocvar_g_freezetag_revive_time_to_score <= 0) @@ -644,7 +839,7 @@ MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) if(STAT(REVIVE_PROGRESS, player) >= 1) { float frozen_time = time - player.freezetag_frozen_time; - Unfreeze(player, false); + freezetag_Unfreeze(player, false); SetResourceExplicit(player, RES_HEALTH, ((warmup_stage) ? warmup_start_health : start_health)); player.spawnshieldtime = time + autocvar_g_freezetag_revive_spawnshield; freezetag_count_alive_players(); @@ -684,7 +879,7 @@ MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player); } - if (STAT(FROZEN, player) == FROZEN_NORMAL) + if (STAT(FROZEN, player)) { entity player_wp = player.waypointsprite_attached; if (n > 0 || (n == 0 && STAT(REVIVE_PROGRESS, player) > 0.95)) @@ -705,6 +900,31 @@ MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) return true; } +MUTATOR_HOOKFUNCTION(ft, PlayerRegen) +{ + entity player = M_ARGV(0, entity); + + return STAT(FROZEN, player); +} + +MUTATOR_HOOKFUNCTION(ft, ItemTouch) +{ + if(MUTATOR_RETURNVALUE) return false; + + entity toucher = M_ARGV(1, entity); + + if(STAT(FROZEN, toucher)) + return MUT_ITEMTOUCH_RETURN; + return MUT_ITEMTOUCH_CONTINUE; +} + +MUTATOR_HOOKFUNCTION(ft, BuffTouch) +{ + entity toucher = M_ARGV(1, entity); + + return STAT(FROZEN, toucher); +} + MUTATOR_HOOKFUNCTION(ft, SetStartItems) { start_items &= ~(IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS); @@ -720,6 +940,38 @@ MUTATOR_HOOKFUNCTION(ft, SetStartItems) start_ammo_fuel = warmup_start_ammo_fuel = autocvar_g_ft_start_ammo_fuel; } +MUTATOR_HOOKFUNCTION(ft, AllowMobSpawning) +{ + M_ARGV(1, string) = "You can't spawn monsters while frozen"; + return true; +} + +MUTATOR_HOOKFUNCTION(ft, MonsterValidTarget) +{ + entity targ = M_ARGV(1, entity); + + return !STAT(FROZEN, targ); +} + +MUTATOR_HOOKFUNCTION(ft, TurretValidateTarget) +{ + entity targ = M_ARGV(1, entity); + + if(STAT(FROZEN, targ)) + { + M_ARGV(3, float) = -6; + return true; + } + return false; +} + +MUTATOR_HOOKFUNCTION(ft, BotShouldAttack) +{ + entity targ = M_ARGV(1, entity); + + return !STAT(FROZEN, targ); +} + MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole) { entity bot = M_ARGV(0, entity); @@ -759,7 +1011,7 @@ MUTATOR_HOOKFUNCTION(ft, FragCenterMessage) int kill_count_to_attacker = M_ARGV(3, int); int kill_count_to_target = M_ARGV(4, int); - if(STAT(FROZEN, frag_target) == FROZEN_NORMAL) + if(STAT(FROZEN, frag_target)) return; // target was already frozen, so this is just pushing them off the cliff Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping)); diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh b/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh index 049f037147..f760331dfb 100644 --- a/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh +++ b/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh @@ -48,3 +48,16 @@ float autocvar_g_freezetag_revive_time_to_score = 1.5; bool autocvar_g_freezetag_revive_nade; float autocvar_g_freezetag_revive_nade_health; float autocvar_g_freezetag_revive_spawnshield = 1; +float autocvar_g_frozen_revive_falldamage; +int autocvar_g_frozen_revive_falldamage_health; +bool autocvar_g_frozen_damage_trigger; +float autocvar_g_frozen_force; + +// frozen (eliminated) state +.float revival_time; // time at which player was last revived +.float revive_speed; // NOTE: multiplier (anything above 1 is instaheal) +.float freeze_time; +.entity iceblock; +.entity frozen_by; // for ice fields + +void freezetag_Unfreeze(entity targ, bool reset_health); diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qc b/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qc index 3bff5c560a..9451bf7deb 100644 --- a/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qc +++ b/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qc @@ -121,9 +121,9 @@ void ka_TouchEvent(entity this, entity toucher) // runs any time that the ball c ka_RespawnBall(this); return; } + // TODO: mutator hook to prevent picking up objectives if(IS_INDEPENDENT_PLAYER(toucher)) { return; } if(IS_DEAD(toucher)) { return; } - if(STAT(FROZEN, toucher)) { return; } if (!IS_PLAYER(toucher)) { // The ball just touched an object, most likely the world Send_Effect(EFFECT_BALL_SPARKS, this.origin, '0 0 0', 1); diff --git a/qcsrc/common/gamemodes/gamemode/nexball/sv_nexball.qc b/qcsrc/common/gamemodes/gamemode/nexball/sv_nexball.qc index c8a82b0c1b..5c05d4b30f 100644 --- a/qcsrc/common/gamemodes/gamemode/nexball/sv_nexball.qc +++ b/qcsrc/common/gamemodes/gamemode/nexball/sv_nexball.qc @@ -341,7 +341,8 @@ void basketball_touch(entity this, entity toucher) football_touch(this, toucher); return; } - if(!this.cnt && IS_PLAYER(toucher) && !STAT(FROZEN, toucher) && !IS_DEAD(toucher) && (toucher != this.nb_dropper || time > this.nb_droptime + autocvar_g_nexball_delay_collect)) + // TODO: mutator hook to prevent picking up objectives + if(!this.cnt && IS_PLAYER(toucher) && !IS_DEAD(toucher) && (toucher != this.nb_dropper || time > this.nb_droptime + autocvar_g_nexball_delay_collect)) { if(GetResource(toucher, RES_HEALTH) < 1) return; diff --git a/qcsrc/common/gamemodes/gamemode/nexball/sv_weapon.qc b/qcsrc/common/gamemodes/gamemode/nexball/sv_weapon.qc index 7bb6c98c1d..76e04d72f7 100644 --- a/qcsrc/common/gamemodes/gamemode/nexball/sv_weapon.qc +++ b/qcsrc/common/gamemodes/gamemode/nexball/sv_weapon.qc @@ -82,8 +82,9 @@ void W_Nexball_Touch(entity this, entity toucher) //this.enemy = NULL; PROJECTILE_TOUCH(this, toucher); + // TODO: mutator hook to prevent picking up objectives if(attacker.team != toucher.team || autocvar_g_nexball_basketball_teamsteal) - if((ball = toucher.ballcarried) && !STAT(FROZEN, toucher) && !IS_DEAD(toucher) && (IS_PLAYER(attacker))) + if((ball = toucher.ballcarried) && !IS_DEAD(toucher) && (IS_PLAYER(attacker))) { toucher.velocity = toucher.velocity + normalize(this.velocity) * toucher.damageforcescale * autocvar_g_balance_nexball_secondary_force; UNSET_ONGROUND(toucher); diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc b/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc index 5838c6242d..5d84257892 100644 --- a/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc +++ b/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc @@ -693,7 +693,6 @@ void ons_ControlPoint_Touch(entity this, entity toucher) } if(!IS_PLAYER(toucher)) { return; } - if(STAT(FROZEN, toucher)) { return; } if(IS_DEAD(toucher)) { return; } if ( SAME_TEAM(this,toucher) ) @@ -1934,46 +1933,40 @@ MUTATOR_HOOKFUNCTION(ons, SV_ParseClientCommand) if ( IS_PLAYER(player) ) { - if ( !STAT(FROZEN, player) ) - { - entity source_point = ons_Nearest_ControlPoint(player, player.origin, autocvar_g_onslaught_teleport_radius); + entity source_point = ons_Nearest_ControlPoint(player, player.origin, autocvar_g_onslaught_teleport_radius); - if ( !source_point && GetResource(player, RES_HEALTH) > 0 ) - { - sprint(player, "\nYou need to be next to a control point\n"); - return true; - } + if ( !source_point && GetResource(player, RES_HEALTH) > 0 ) + { + sprint(player, "\nYou need to be next to a control point\n"); + return true; + } + entity closest_target = ons_Nearest_ControlPoint_2D(player, pos, autocvar_g_onslaught_click_radius); - entity closest_target = ons_Nearest_ControlPoint_2D(player, pos, autocvar_g_onslaught_click_radius); + if ( closest_target == NULL ) + { + sprint(player, "\nNo control point found\n"); + return true; + } - if ( closest_target == NULL ) + if ( GetResource(player, RES_HEALTH) <= 0 ) + { + player.ons_spawn_by = closest_target; + player.respawn_flags = player.respawn_flags | RESPAWN_FORCE; + } + else + { + if ( source_point == closest_target ) { - sprint(player, "\nNo control point found\n"); + sprint(player, "\nTeleporting to the same point\n"); return true; } - if ( GetResource(player, RES_HEALTH) <= 0 ) - { - player.ons_spawn_by = closest_target; - player.respawn_flags = player.respawn_flags | RESPAWN_FORCE; - } - else - { - if ( source_point == closest_target ) - { - sprint(player, "\nTeleporting to the same point\n"); - return true; - } - - if ( !ons_Teleport(player,closest_target,autocvar_g_onslaught_teleport_radius,true) ) - sprint(player, "\nUnable to teleport there\n"); - } - - return true; + if ( !ons_Teleport(player,closest_target,autocvar_g_onslaught_teleport_radius,true) ) + sprint(player, "\nUnable to teleport there\n"); } - sprint(player, "\nNo teleportation for you\n"); + return true; } return true; diff --git a/qcsrc/common/gamemodes/gamemode/tka/sv_tka.qc b/qcsrc/common/gamemodes/gamemode/tka/sv_tka.qc index 7f68e72672..969146a561 100644 --- a/qcsrc/common/gamemodes/gamemode/tka/sv_tka.qc +++ b/qcsrc/common/gamemodes/gamemode/tka/sv_tka.qc @@ -114,7 +114,6 @@ void tka_TouchEvent(entity this, entity toucher) // runs any time that the ball } if(IS_INDEPENDENT_PLAYER(toucher)) { return; } if(IS_DEAD(toucher)) { return; } - if(STAT(FROZEN, toucher)) { return; } if (!IS_PLAYER(toucher)) { // The ball just touched an object, most likely the world Send_Effect(EFFECT_BALL_SPARKS, this.origin, '0 0 0', 1); diff --git a/qcsrc/common/monsters/monster/mage.qc b/qcsrc/common/monsters/monster/mage.qc index 32a6da4de5..b42ab4f637 100644 --- a/qcsrc/common/monsters/monster/mage.qc +++ b/qcsrc/common/monsters/monster/mage.qc @@ -84,7 +84,8 @@ void M_Mage_Defend_Shield(entity this); bool M_Mage_Defend_Heal_Check(entity this, entity targ) { - if(targ == NULL) + // TODO: mutator hook to choose valid healing targets? + if(!targ) return false; if(GetResource(targ, RES_HEALTH) <= 0) return false; diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc index a0db8a8097..4a0b686a29 100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@ -114,7 +114,6 @@ bool Monster_ValidTarget(entity this, entity targ, bool skipfacing) || (!IS_VEHICLE(targ) && (targ.flags & FL_NOTARGET)) || (!autocvar_g_monsters_typefrag && PHYS_INPUT_BUTTON_CHAT(targ)) || (SAME_TEAM(targ, this)) - || (STAT(FROZEN, targ)) || (targ.alpha != 0 && targ.alpha < 0.5) || (autocvar_g_monsters_lineofsight && !checkpvs(this.origin + this.view_ofs, targ)) // enemy cannot be seen || (MUTATOR_CALLHOOK(MonsterValidTarget, this, targ)) @@ -785,7 +784,7 @@ void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed) if(this.target2 && this.target2 != "" && this.goalentity.targetname != this.target2) this.goalentity = find(NULL, targetname, this.target2); - if(STAT(FROZEN, this)) + if(StatusEffects_active(STATUSEFFECT_Frozen, this)) { movelib_brake_simple(this, stpspeed); setanim(this, this.anim_idle, true, false, false); @@ -957,7 +956,6 @@ void Monster_Remove(entity this) if(this.(weaponentity)) delete(this.(weaponentity)); } - if(this.iceblock) { delete(this.iceblock); } WaypointSprite_Kill(this.sprite); delete(this); } @@ -1008,8 +1006,6 @@ void Monster_Reset(entity this) setorigin(this, this.pos1); this.angles = this.pos2; - Unfreeze(this, false); // remove any icy remains - SetResourceExplicit(this, RES_HEALTH, this.max_health); this.velocity = '0 0 0'; this.enemy = NULL; @@ -1043,9 +1039,6 @@ void Monster_Dead(entity this, entity attacker, float gibbed) this.nextthink = time; this.monster_lifetime = time + 5; - if(STAT(FROZEN, this)) - Unfreeze(this, false); // remove any icy remains - monster_dropitem(this, attacker); Monster_Sound(this, monstersound_death, 0, false, CH_VOICE); @@ -1095,9 +1088,6 @@ void Monster_Damage(entity this, entity inflictor, entity attacker, float damage if((this.spawnflags & MONSTERFLAG_INVINCIBLE) && deathtype != DEATH_KILL.m_id && !ITEM_DAMAGE_NEEDKILL(deathtype)) return; - if(STAT(FROZEN, this) && deathtype != DEATH_KILL.m_id && deathtype != DEATH_NADE_ICE_FREEZE.m_id) - return; - //if(time < this.pain_finished && deathtype != DEATH_KILL.m_id) //return; @@ -1246,7 +1236,7 @@ void Monster_Anim(entity this) deadbits = 0; } int animbits = deadbits; - if(STAT(FROZEN, this)) + if(StatusEffects_active(STATUSEFFECT_Frozen, this)) animbits |= ANIMSTATE_FROZEN; if(IS_DUCKED(this)) animbits |= ANIMSTATE_DUCK; // not that monsters can crouch currently... @@ -1263,43 +1253,6 @@ void Monster_Anim(entity this) */ } -void Monster_Frozen_Think(entity this) -{ - if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING) - { - STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1); - SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * this.max_health)); - if (this.iceblock) - this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1); - - if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE) && this.sprite) - WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH)); - - if(STAT(REVIVE_PROGRESS, this) >= 1) - Unfreeze(this, false); - } - else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING) - { - STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1); - SetResourceExplicit(this, RES_HEALTH, max(0, autocvar_g_nades_ice_health + (this.max_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this))); - - if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE) && this.sprite) - WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH)); - - if(GetResource(this, RES_HEALTH) < 1) - { - Unfreeze(this, false); - if(this.event_damage) - this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0'); - } - else if ( STAT(REVIVE_PROGRESS, this) <= 0 ) - Unfreeze(this, false); - } - // otherwise, no revival! - - this.enemy = NULL; // TODO: save enemy, and attack when revived? -} - void Monster_Enemy_Check(entity this) { if(this.enemy) @@ -1309,6 +1262,7 @@ void Monster_Enemy_Check(entity this) WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this); // cases where the enemy may have changed their state (don't need to check everything here) + // TODO: mutator hook if( (IS_DEAD(this.enemy) || GetResource(this.enemy, RES_HEALTH) < 1) || (STAT(FROZEN, this.enemy)) || (this.enemy.flags & FL_NOTARGET) @@ -1351,8 +1305,11 @@ void Monster_Think(entity this) return; } - if(STAT(FROZEN, this)) - Monster_Frozen_Think(this); + // TODO: mutator hook to control monster thinking + if(StatusEffects_active(STATUSEFFECT_Frozen, this)) + { + this.enemy = NULL; // TODO: save enemy, and attack when revived? + } else if(time >= this.last_enemycheck) { Monster_Enemy_Check(this); diff --git a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc index cf4d890c11..c75ac5d573 100644 --- a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc +++ b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc @@ -207,7 +207,6 @@ void buff_Touch(entity this, entity toucher) return; // incase mutator changed toucher if((this.team_forced && toucher.team != this.team_forced) - || (STAT(FROZEN, toucher)) || (toucher.vehicle) || (!this.buffdef) // TODO: error out or maybe reset type if this occurs? || (time < toucher.buff_shield) @@ -649,12 +648,6 @@ MUTATOR_HOOKFUNCTION(buffs, FilterItem) return false; } -MUTATOR_HOOKFUNCTION(buffs, Freeze) -{ - entity targ = M_ARGV(0, entity); - buff_RemoveAll(targ, STATUSEFFECT_REMOVE_NORMAL); -} - MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) { entity player = M_ARGV(0, entity); diff --git a/qcsrc/common/mutators/mutator/nades/nade/ice.qc b/qcsrc/common/mutators/mutator/nades/nade/ice.qc index 0daba58b19..2d679fad00 100644 --- a/qcsrc/common/mutators/mutator/nades/nade/ice.qc +++ b/qcsrc/common/mutators/mutator/nades/nade/ice.qc @@ -3,11 +3,10 @@ #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); + StatusEffects_apply(STATUSEFFECT_Frozen, frost_target, time + freezetime, 0); - Drop_Special_Items(frost_target); + //Drop_Special_Items(frost_target); } void nade_ice_think(entity this) @@ -62,8 +61,9 @@ void nade_ice_think(entity this) 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), + && (IS_PLAYER(it) || IS_MONSTER(it)) && !IS_DEAD(it) && GetResource(it, RES_HEALTH) > 0 && current_freeze_time > 0 + && (!it.revival_time || ((time - it.revival_time) >= 1.5)) + && !STAT(FROZEN, it) && !StatusEffects_active(STATUSEFFECT_Frozen, it), { switch (autocvar_g_nades_ice_teamcheck) { diff --git a/qcsrc/common/mutators/mutator/nades/nade/ice.qh b/qcsrc/common/mutators/mutator/nades/nade/ice.qh index ed64a8303e..9435c8d2a4 100644 --- a/qcsrc/common/mutators/mutator/nades/nade/ice.qh +++ b/qcsrc/common/mutators/mutator/nades/nade/ice.qh @@ -5,7 +5,6 @@ #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; diff --git a/qcsrc/common/mutators/mutator/nades/sv_nades.qc b/qcsrc/common/mutators/mutator/nades/sv_nades.qc index b97ea67e5d..0dc520c91f 100644 --- a/qcsrc/common/mutators/mutator/nades/sv_nades.qc +++ b/qcsrc/common/mutators/mutator/nades/sv_nades.qc @@ -708,15 +708,6 @@ MUTATOR_HOOKFUNCTION(nades, ForbidThrowCurrentWeapon, CBC_ORDER_LAST) } } -#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); @@ -781,64 +772,6 @@ MUTATOR_HOOKFUNCTION(nades, PlayerPreThink) 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) @@ -920,7 +853,7 @@ MUTATOR_HOOKFUNCTION(nades, Damage_Calculate) 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); + freezetag_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; diff --git a/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc index fd8a8bdc2d..d6739ab36f 100644 --- a/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc +++ b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc @@ -138,7 +138,7 @@ MUTATOR_HOOKFUNCTION(ok, PlayerPreThink) return; } entity player = M_ARGV(0, entity); - if (!IS_PLAYER(player) || IS_DEAD(player) || STAT(FROZEN, player)) + if (!IS_PLAYER(player) || IS_DEAD(player)) { return; } diff --git a/qcsrc/common/mutators/mutator/status_effects/cl_status_effects.qc b/qcsrc/common/mutators/mutator/status_effects/cl_status_effects.qc index 4c68c1bad6..d0e368edb1 100644 --- a/qcsrc/common/mutators/mutator/status_effects/cl_status_effects.qc +++ b/qcsrc/common/mutators/mutator/status_effects/cl_status_effects.qc @@ -2,6 +2,9 @@ METHOD(StatusEffect, m_active, bool(StatusEffect this, entity actor)) { + // TODO: allow per-entity status effects on the client side + actor = g_statuseffects; + if(!actor) return false; TC(StatusEffect, this); return (actor.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE); diff --git a/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.inc b/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.inc index 41349d1e0a..720c8bf736 100644 --- a/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.inc +++ b/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.inc @@ -1,5 +1,6 @@ // genmod.sh autogenerated file; do not modify #include +#include #include #include #include diff --git a/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.qh b/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.qh index ad12a0f297..a75df6058c 100644 --- a/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.qh +++ b/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.qh @@ -1,5 +1,6 @@ // genmod.sh autogenerated file; do not modify #include +#include #include #include #include diff --git a/qcsrc/common/mutators/mutator/status_effects/status_effect/frozen.qc b/qcsrc/common/mutators/mutator/status_effects/status_effect/frozen.qc new file mode 100644 index 0000000000..ef45a8b31f --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/status_effect/frozen.qc @@ -0,0 +1,78 @@ +#include "frozen.qh" + +#ifdef SVQC +.entity frozen_ice; + +void Frozen_ice_remove(entity this) +{ + if(this.frozen_ice) + delete(this.frozen_ice); + this.frozen_ice = NULL; +} + +void Frozen_ice_think(entity this) +{ + if(this.owner.frozen_ice != this || wasfreed(this.owner)) + { + delete(this); + return; + } + vector ice_org = this.owner.origin - '0 0 16'; + if(this.origin != ice_org) + setorigin(this, ice_org); + this.nextthink = time; +} + +void Frozen_ice_create(entity this) +{ + entity ice = new(ice); + // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player + ice.owner = this; + ice.scale = this.scale; + setthink(ice, Frozen_ice_think); + ice.nextthink = time; + ice.frame = floor(random() * 21); // ice model has 20 different looking frames + // TODO: unique (less pronounced) model + setmodel(ice, MDL_ICE); + ice.alpha = 0.5; + + this.frozen_ice = ice; + + Frozen_ice_think(ice); +} + +METHOD(Frozen, 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) + { + Frozen_ice_create(actor); + RemoveGrapplingHooks(actor); + // TODO: should hooks targeting this entity also be removed? + // TODO: special items as well? + } + SUPER(Frozen).m_apply(this, actor, eff_time, eff_flags); +} +METHOD(Frozen, m_remove, void(StatusEffect this, entity actor, int removal_type)) +{ + Frozen_ice_remove(actor); + SUPER(Frozen).m_remove(this, actor, removal_type); +} +METHOD(Frozen, m_tick, void(StatusEffect this, entity actor)) +{ + if(STAT(FROZEN, actor) || (actor.waterlevel && actor.watertype == CONTENT_LAVA)) + { + this.m_remove(this, actor, STATUSEFFECT_REMOVE_NORMAL); + return; + } + SUPER(Frozen).m_tick(this, actor); +} +#endif +#ifdef CSQC +METHOD(Frozen, m_tick, void(StatusEffect this, entity actor)) +{ + vector col = '0.25 0.90 1'; + float alpha_fade = 0.3; + drawfill('0 0 0', vec2(vid_conwidth, vid_conheight), col, autocvar_hud_colorflash_alpha * alpha_fade, DRAWFLAG_ADDITIVE); +} +#endif diff --git a/qcsrc/common/mutators/mutator/status_effects/status_effect/frozen.qh b/qcsrc/common/mutators/mutator/status_effects/status_effect/frozen.qh new file mode 100644 index 0000000000..be57a0a3e6 --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/status_effect/frozen.qh @@ -0,0 +1,11 @@ +#pragma once + +#include + +CLASS(Frozen, StatusEffect) + ATTRIB(Frozen, netname, string, "frozen"); + ATTRIB(Frozen, m_color, vector, '0 0.62 1'); + ATTRIB(Frozen, m_hidden, bool, true); + ATTRIB(Frozen, m_lifetime, float, 10); +ENDCLASS(Frozen) +REGISTER_STATUSEFFECT(Frozen, NEW(Frozen)); diff --git a/qcsrc/common/mutators/mutator/status_effects/status_effects.qh b/qcsrc/common/mutators/mutator/status_effects/status_effects.qh index 3f6d26a9e1..51f15ecbe4 100644 --- a/qcsrc/common/mutators/mutator/status_effects/status_effects.qh +++ b/qcsrc/common/mutators/mutator/status_effects/status_effects.qh @@ -121,7 +121,7 @@ void StatusEffects_Write(StatusEffect data, StatusEffect store) #undef G_MINOR #ifdef SVQC -bool StatusEffects_Send(StatusEffect this, Client to, int sf) +bool StatusEffects_Send(StatusEffect this, entity to, int sf) { TC(StatusEffect, this); WriteHeader(MSG_ENTITY, ENT_CLIENT_STATUSEFFECTS); diff --git a/qcsrc/common/mutators/mutator/status_effects/sv_status_effects.qc b/qcsrc/common/mutators/mutator/status_effects/sv_status_effects.qc index 97b1cec688..722a00d1ad 100644 --- a/qcsrc/common/mutators/mutator/status_effects/sv_status_effects.qc +++ b/qcsrc/common/mutators/mutator/status_effects/sv_status_effects.qc @@ -70,6 +70,13 @@ MUTATOR_HOOKFUNCTION(status_effects, PlayerDies) StatusEffects_removeall(frag_target, STATUSEFFECT_REMOVE_NORMAL); } +MUTATOR_HOOKFUNCTION(status_effects, MonsterDies) +{ + entity frag_target = M_ARGV(0, entity); + + StatusEffects_removeall(frag_target, STATUSEFFECT_REMOVE_NORMAL); +} + MUTATOR_HOOKFUNCTION(status_effects, ClientDisconnect) { entity player = M_ARGV(0, entity); diff --git a/qcsrc/common/mutators/mutator/walljump/walljump.qc b/qcsrc/common/mutators/mutator/walljump/walljump.qc index 519159d917..9707138c37 100644 --- a/qcsrc/common/mutators/mutator/walljump/walljump.qc +++ b/qcsrc/common/mutators/mutator/walljump/walljump.qc @@ -42,7 +42,7 @@ MUTATOR_HOOKFUNCTION(walljump, PlayerJump) if(!IS_ONGROUND(player)) if(player.move_movetype != MOVETYPE_NONE && player.move_movetype != MOVETYPE_FOLLOW && player.move_movetype != MOVETYPE_FLY && player.move_movetype != MOVETYPE_NOCLIP) if(!IS_JUMP_HELD(player)) - if(!STAT(FROZEN, player)) + if(!STAT(FROZEN, player) && !StatusEffects_active(STATUSEFFECT_Frozen, player)) if(!IS_DEAD(player)) { vector plane_normal = PlayerTouchWall(player); diff --git a/qcsrc/common/notifications/all.inc b/qcsrc/common/notifications/all.inc index 836fef2895..1e475b1689 100644 --- a/qcsrc/common/notifications/all.inc +++ b/qcsrc/common/notifications/all.inc @@ -284,7 +284,6 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input != MSG_INFO_NOTIF(DEATH_MURDER_NADE, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "nade_normal", _("^BG%s%s^K1 was blown up by ^BG%s^K1's Nade%s%s"), "") MSG_INFO_NOTIF(DEATH_MURDER_NADE_NAPALM, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "nade_napalm", _("^BG%s%s^K1 was burned to death by ^BG%s^K1's Napalm Nade%s%s"), _("^BG%s%s^K1 got too close to a napalm explosion%s%s")) MSG_INFO_NOTIF(DEATH_MURDER_NADE_ICE, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "nade_ice", _("^BG%s%s^K1 was blown up by ^BG%s^K1's Ice Nade%s%s"), "") - MSG_INFO_NOTIF(DEATH_MURDER_NADE_ICE_FREEZE, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "nade_ice", _("^BG%s%s^K1 was frozen to death by ^BG%s^K1's Ice Nade%s%s"), "") MSG_INFO_NOTIF(DEATH_MURDER_NADE_HEAL, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "nade_heal", _("^BG%s%s^K1 has not been healed by ^BG%s^K1's Healing Nade%s%s"), "") MSG_INFO_NOTIF(DEATH_MURDER_SHOOTING_STAR, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_shootingstar", _("^BG%s%s^K1 was shot into space by ^BG%s^K1%s%s"), "") MSG_INFO_NOTIF(DEATH_MURDER_SLIME, N_CONSOLE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_slime", _("^BG%s%s^K1 was slimed by ^BG%s^K1%s%s"), "") @@ -327,7 +326,6 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input != MSG_INFO_NOTIF(DEATH_SELF_NADE, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "nade_normal", _("^BG%s^K1 mastered the art of self-nading%s%s"), "") MSG_INFO_NOTIF(DEATH_SELF_NADE_NAPALM, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "nade_napalm", _("^BG%s^K1 was burned to death by their own Napalm Nade%s%s"), _("^BG%s^K1 decided to take a look at the results of their napalm explosion%s%s")) MSG_INFO_NOTIF(DEATH_SELF_NADE_ICE, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "nade_ice", _("^BG%s^K1 mastered the art of self-nading%s%s"), "") - MSG_INFO_NOTIF(DEATH_SELF_NADE_ICE_FREEZE, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "nade_ice", _("^BG%s^K1 was frozen to death by their own Ice Nade%s%s"), _("^BG%s^K1 felt a little chilly%s%s")) MSG_INFO_NOTIF(DEATH_SELF_NADE_HEAL, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "nade_heal", _("^BG%s^K1's Healing Nade didn't quite heal them%s%s"), "") MSG_INFO_NOTIF(DEATH_SELF_NOAMMO, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_outofammo", _("^BG%s^K1 died%s%s. What's the point of living without ammo?"), _("^BG%s^K1 ran out of ammo%s%s")) MSG_INFO_NOTIF(DEATH_SELF_ROT, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 rotted away%s%s"), "") @@ -660,7 +658,6 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input != MSG_CENTER_NOTIF(DEATH_SELF_MONSTER, N_ENABLE, 0, 0, "", CPID_Null, "0 0", BOLD(_("^K1You were killed by a monster!")), BOLD(_("^K1You need to watch out for monsters!"))) MSG_CENTER_NOTIF(DEATH_SELF_NADE, N_ENABLE, 0, 0, "", CPID_Null, "0 0", BOLD(_("^K1You forgot to put the pin back in!")), BOLD(_("^K1Tastes like chicken!"))) MSG_CENTER_NOTIF(DEATH_SELF_NADE_NAPALM, N_ENABLE, 0, 0, "", CPID_Null, "0 0", BOLD(_("^K1Hanging around a napalm explosion is bad!")), "") - MSG_CENTER_NOTIF(DEATH_SELF_NADE_ICE_FREEZE, N_ENABLE, 0, 0, "", CPID_Null, "0 0", BOLD(_("^K1You got a little bit too cold!")), BOLD(_("^K1You felt a little chilly!"))) MSG_CENTER_NOTIF(DEATH_SELF_NADE_HEAL, N_ENABLE, 0, 0, "", CPID_Null, "0 0", BOLD(_("^K1Your Healing Nade is a bit defective")), "") MSG_CENTER_NOTIF(DEATH_SELF_NOAMMO, N_ENABLE, 0, 0, "", CPID_Null, "0 0", BOLD(_("^K1You were killed for running out of ammo...")), BOLD(_("^K1You are respawning for running out of ammo..."))) MSG_CENTER_NOTIF(DEATH_SELF_ROT, N_ENABLE, 0, 0, "", CPID_Null, "0 0", BOLD(_("^K1You grew too old without taking your medicine")), BOLD(_("^K1You need to preserve your health"))) @@ -864,7 +861,6 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input != MSG_MULTI_NOTIF(DEATH_MURDER_NADE, N_ENABLE, NULL, INFO_DEATH_MURDER_NADE, NULL) MSG_MULTI_NOTIF(DEATH_MURDER_NADE_NAPALM, N_ENABLE, NULL, INFO_DEATH_MURDER_NADE_NAPALM, NULL) MSG_MULTI_NOTIF(DEATH_MURDER_NADE_ICE, N_ENABLE, NULL, INFO_DEATH_MURDER_NADE_ICE, NULL) - MSG_MULTI_NOTIF(DEATH_MURDER_NADE_ICE_FREEZE, N_ENABLE, NULL, INFO_DEATH_MURDER_NADE_ICE_FREEZE, NULL) MSG_MULTI_NOTIF(DEATH_MURDER_NADE_HEAL, N_ENABLE, NULL, INFO_DEATH_MURDER_NADE_HEAL, NULL) MSG_MULTI_NOTIF(DEATH_MURDER_SHOOTING_STAR, N_ENABLE, NULL, INFO_DEATH_MURDER_SHOOTING_STAR, NULL) MSG_MULTI_NOTIF(DEATH_MURDER_SLIME, N_ENABLE, NULL, INFO_DEATH_MURDER_SLIME, NULL) @@ -906,8 +902,7 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input != MSG_MULTI_NOTIF(DEATH_SELF_MON_ZOMBIE_MELEE, N_ENABLE, NULL, INFO_DEATH_SELF_MON_ZOMBIE_MELEE, CENTER_DEATH_SELF_MONSTER) MSG_MULTI_NOTIF(DEATH_SELF_NADE, N_ENABLE, NULL, INFO_DEATH_SELF_NADE, CENTER_DEATH_SELF_NADE) MSG_MULTI_NOTIF(DEATH_SELF_NADE_NAPALM, N_ENABLE, NULL, INFO_DEATH_SELF_NADE_NAPALM, CENTER_DEATH_SELF_NADE_NAPALM) - MSG_MULTI_NOTIF(DEATH_SELF_NADE_ICE, N_ENABLE, NULL, INFO_DEATH_SELF_NADE_ICE, CENTER_DEATH_SELF_NADE_ICE_FREEZE) - MSG_MULTI_NOTIF(DEATH_SELF_NADE_ICE_FREEZE, N_ENABLE, NULL, INFO_DEATH_SELF_NADE_ICE_FREEZE, CENTER_DEATH_SELF_NADE_ICE_FREEZE) + MSG_MULTI_NOTIF(DEATH_SELF_NADE_ICE, N_ENABLE, NULL, INFO_DEATH_SELF_NADE_ICE, CENTER_DEATH_SELF_NADE) MSG_MULTI_NOTIF(DEATH_SELF_NADE_HEAL, N_ENABLE, NULL, INFO_DEATH_SELF_NADE_HEAL, CENTER_DEATH_SELF_NADE_HEAL) MSG_MULTI_NOTIF(DEATH_SELF_NOAMMO, N_ENABLE, NULL, INFO_DEATH_SELF_NOAMMO, CENTER_DEATH_SELF_NOAMMO) MSG_MULTI_NOTIF(DEATH_SELF_ROT, N_ENABLE, NULL, INFO_DEATH_SELF_ROT, CENTER_DEATH_SELF_ROT) diff --git a/qcsrc/common/physics/player.qc b/qcsrc/common/physics/player.qc index b007afaf18..71279afd62 100644 --- a/qcsrc/common/physics/player.qc +++ b/qcsrc/common/physics/player.qc @@ -186,7 +186,7 @@ void PM_ClientMovement_UpdateStatus(entity this) //do_crouch = false; } else if (PHYS_INVEHICLE(this)) { do_crouch = false; - } else if (STAT(FROZEN, this) || IS_DEAD(this)) { + } else if (PHYS_FROZEN(this) || IS_DEAD(this)) { do_crouch = false; } diff --git a/qcsrc/common/physics/player.qh b/qcsrc/common/physics/player.qh index e2e9077679..c6a2112dd2 100644 --- a/qcsrc/common/physics/player.qh +++ b/qcsrc/common/physics/player.qh @@ -113,7 +113,7 @@ REPLICATE_INIT(bool, cvar_cl_movement_track_canjump); #define PHYS_FRICTION_ONLAND(s) STAT(MOVEVARS_FRICTION_ONLAND, s) #define PHYS_FRICTION_SLICK(s) STAT(MOVEVARS_FRICTION_SLICK, s) -#define PHYS_FROZEN(s) STAT(FROZEN, s) +#define PHYS_FROZEN(s) (STAT(FROZEN, s) || StatusEffects_active(STATUSEFFECT_Frozen, s)) #define PHYS_HIGHSPEED(s) STAT(MOVEVARS_HIGHSPEED, s) diff --git a/qcsrc/common/stats.qh b/qcsrc/common/stats.qh index befeffc176..7bbc4ca6a0 100644 --- a/qcsrc/common/stats.qh +++ b/qcsrc/common/stats.qh @@ -126,7 +126,7 @@ REGISTER_STAT(NADE_BONUS, FLOAT) REGISTER_STAT(NADE_BONUS_TYPE, INT) REGISTER_STAT(NADE_BONUS_SCORE, FLOAT) REGISTER_STAT(NADE_DARKNESS_TIME, FLOAT) -REGISTER_STAT(FROZEN, INT) +REGISTER_STAT(FROZEN, BOOL) REGISTER_STAT(REVIVE_PROGRESS, FLOAT) REGISTER_STAT(ROUNDLOST, INT) REGISTER_STAT(CAPTURE_PROGRESS, FLOAT) diff --git a/qcsrc/common/turrets/sv_turrets.qc b/qcsrc/common/turrets/sv_turrets.qc index 9a9a902c27..3545e734c8 100644 --- a/qcsrc/common/turrets/sv_turrets.qc +++ b/qcsrc/common/turrets/sv_turrets.qc @@ -716,8 +716,6 @@ float turret_validate_target(entity e_turret, entity e_target, float validate_fl // Cant touch this if (GetResource(e_target, RES_HEALTH) <= 0) return -6; - else if (STAT(FROZEN, e_target)) - return -6; // vehicle if(IS_VEHICLE(e_target)) diff --git a/qcsrc/common/vehicles/sv_vehicles.qc b/qcsrc/common/vehicles/sv_vehicles.qc index f18d867c60..c24bb677c9 100644 --- a/qcsrc/common/vehicles/sv_vehicles.qc +++ b/qcsrc/common/vehicles/sv_vehicles.qc @@ -941,10 +941,11 @@ void vehicles_enter(entity pl, entity veh) if((IS_BOT_CLIENT(pl) && !autocvar_g_vehicles_allow_bots)) return; + // TODO: mutator hook to prevent entering vehicles if((!IS_PLAYER(pl)) || (veh.phase >= time) || (pl.vehicle_enter_delay >= time) - || (STAT(FROZEN, pl)) + || (STAT(FROZEN, pl) || StatusEffects_active(STATUSEFFECT_Frozen, pl)) || (IS_DEAD(pl)) || (pl.vehicle) ) { return; } diff --git a/qcsrc/common/weapons/weapon/fireball.qc b/qcsrc/common/weapons/weapon/fireball.qc index 6e6cc8a689..c2a0a7c7bb 100644 --- a/qcsrc/common/weapons/weapon/fireball.qc +++ b/qcsrc/common/weapons/weapon/fireball.qc @@ -107,7 +107,7 @@ void W_Fireball_LaserPlay(entity this, float dt, float dist, float damage, float RandomSelection_Init(); for(e = WarpZone_FindRadius(this.origin, dist, true); e; e = e.chain) { - if(STAT(FROZEN, e)) continue; + if(STAT(FROZEN, e) || StatusEffects_active(STATUSEFFECT_Frozen, e)) continue; if(e == this.realowner) continue; if(IS_INDEPENDENT_PLAYER(e)) continue; if(e.takedamage != DAMAGE_AIM) continue; diff --git a/qcsrc/server/bot/default/aim.qc b/qcsrc/server/bot/default/aim.qc index 0a02e50d1d..985cffebb0 100644 --- a/qcsrc/server/bot/default/aim.qc +++ b/qcsrc/server/bot/default/aim.qc @@ -105,9 +105,6 @@ bool bot_shouldattack(entity this, entity targ) return false; } - if(STAT(FROZEN, targ)) - return false; - if(teamplay) { if(targ.team==0) diff --git a/qcsrc/server/bot/default/havocbot/havocbot.qc b/qcsrc/server/bot/default/havocbot/havocbot.qc index 5a9ef8f628..802ccc06bf 100644 --- a/qcsrc/server/bot/default/havocbot/havocbot.qc +++ b/qcsrc/server/bot/default/havocbot/havocbot.qc @@ -57,7 +57,7 @@ void havocbot_ai(entity this) } else { - if (!this.jumppadcount && !STAT(FROZEN, this) + if (!this.jumppadcount && !STAT(FROZEN, this) && !StatusEffects_active(STATUSEFFECT_Frozen, this) && !(this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP) && !IS_ONGROUND(this))) { // find a new goal @@ -66,7 +66,7 @@ void havocbot_ai(entity this) } // if we don't have a goal and we're under water look for a waypoint near the "shore" and push it - if(!(IS_DEAD(this) || STAT(FROZEN, this))) + if(!(IS_DEAD(this) || STAT(FROZEN, this) || StatusEffects_active(STATUSEFFECT_Frozen, this))) if(!this.goalcurrent) if(this.waterlevel == WATERLEVEL_SWIMMING || (this.aistatus & AI_STATUS_OUT_WATER)) { @@ -110,7 +110,7 @@ void havocbot_ai(entity this) return; } - if(IS_DEAD(this) || STAT(FROZEN, this)) + if(IS_DEAD(this) || STAT(FROZEN, this) || StatusEffects_active(STATUSEFFECT_Frozen, this)) { if (this.goalcurrent) navigation_clearroute(this); diff --git a/qcsrc/server/bot/default/navigation.qc b/qcsrc/server/bot/default/navigation.qc index 8fc33448b9..c34af9389c 100644 --- a/qcsrc/server/bot/default/navigation.qc +++ b/qcsrc/server/bot/default/navigation.qc @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc index b3a9a806cc..1f35e1503b 100644 --- a/qcsrc/server/client.qc +++ b/qcsrc/server/client.qc @@ -312,7 +312,6 @@ void PutObserverInServer(entity this, bool is_forced, bool use_spawnpoint) RemoveGrapplingHooks(this); Portal_ClearAll(this); - Unfreeze(this, false); SetSpectatee(this, NULL); if (this.alivetime) @@ -376,8 +375,6 @@ void PutObserverInServer(entity this, bool is_forced, bool use_spawnpoint) this.nextthink = 0; this.deadflag = DEAD_NO; UNSET_DUCKED(this); - STAT(REVIVE_PROGRESS, this) = 0; - this.revival_time = 0; this.draggable = drag_undraggable; player_powerups_remove_all(this, was_player); @@ -690,9 +687,6 @@ void PutPlayerInServer(entity this) this.punchangle = '0 0 0'; this.punchvector = '0 0 0'; - STAT(REVIVE_PROGRESS, this) = 0; - this.revival_time = 0; - STAT(AIR_FINISHED, this) = 0; this.waterlevel = WATERLEVEL_NONE; this.watertype = CONTENT_EMPTY; @@ -795,8 +789,6 @@ void PutPlayerInServer(entity this) } }); - Unfreeze(this, false); - MUTATOR_CALLHOOK(PlayerSpawn, this, spot); { string s = spot.target; @@ -1293,8 +1285,6 @@ void ClientDisconnect(entity this) Portal_ClearAll(this); - Unfreeze(this, false); - RemoveGrapplingHooks(this); strfree(this.shootfromfixedorigin); @@ -1697,7 +1687,6 @@ void player_regen(entity this) float rotstable, regenstable, rotframetime, regenframetime; if(!mutator_returnvalue) - if(!STAT(FROZEN, this)) { regenstable = autocvar_g_balance_armor_regenstable; rotstable = autocvar_g_balance_armor_rotstable; @@ -1813,8 +1802,6 @@ void SpectateCopy(entity this, entity spectatee) this.dmg_inflictor = spectatee.dmg_inflictor; this.v_angle = spectatee.v_angle; this.angles = spectatee.v_angle; - STAT(FROZEN, this) = STAT(FROZEN, spectatee); - STAT(REVIVE_PROGRESS, this) = STAT(REVIVE_PROGRESS, spectatee); this.viewloc = spectatee.viewloc; if(!PHYS_INPUT_BUTTON_USE(this) && STAT(CAMERA_SPECTATOR, this) != 2) this.fixangle = true; @@ -2563,7 +2550,7 @@ void PlayerUseKey(entity this) } else if(autocvar_g_vehicles_enter) { - if(!game_stopped && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this)) + if(!game_stopped && !STAT(FROZEN, this) && !StatusEffects_active(STATUSEFFECT_Frozen, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this)) { entity head, closest_target = NULL; head = WarpZone_FindRadius(this.origin, autocvar_g_vehicles_enter_radius, true); @@ -2683,6 +2670,7 @@ void PlayerPreThink (entity this) void DrownPlayer(entity this) { + // TODO: mutator hook to prevent drowning? if(IS_DEAD(this) || game_stopped || time < game_starttime || this.vehicle || STAT(FROZEN, this) || this.watertype != CONTENT_WATER) { @@ -2863,39 +2851,9 @@ void PlayerFrame (entity this) this.max_armorvalue = 0; } - // FreezeTag - if (IS_PLAYER(this) && time >= game_starttime) - { - if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING) - { - STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1); - SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health)); - if (this.iceblock) - this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1); - - if (STAT(REVIVE_PROGRESS, this) >= 1) - Unfreeze(this, false); - } - else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING) - { - STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1); - SetResourceExplicit(this, RES_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this))); - - if (GetResource(this, RES_HEALTH) < 1) - { - if (this.vehicle) - vehicles_exit(this.vehicle, VHEF_RELEASE); - if(this.event_damage) - this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0'); - } - else if (STAT(REVIVE_PROGRESS, this) <= 0) - Unfreeze(this, false); - } - } - // Vehicles if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !game_stopped && !this.vehicle) - if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this)) + if(IS_PLAYER(this) && !STAT(FROZEN, this) && !StatusEffects_active(STATUSEFFECT_Frozen, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this)) { FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it) && !IS_DEAD(it) && it.takedamage != DAMAGE_NO, { diff --git a/qcsrc/server/clientkill.qc b/qcsrc/server/clientkill.qc index 92bbfde4bb..3d04c13c08 100644 --- a/qcsrc/server/clientkill.qc +++ b/qcsrc/server/clientkill.qc @@ -236,7 +236,7 @@ void ClientKill_Silent(entity this, float _delay) // Called when a client types 'kill' in the console void ClientKill(entity this) { - if (game_stopped || this.player_blocked || STAT(FROZEN, this)) + if (game_stopped || this.player_blocked) return; ClientKill_TeamChange(this, 0); diff --git a/qcsrc/server/command/common.qc b/qcsrc/server/command/common.qc index a197312c51..bc2e655de8 100644 --- a/qcsrc/server/command/common.qc +++ b/qcsrc/server/command/common.qc @@ -369,7 +369,6 @@ void CommonCommand_editmob(int request, entity caller, int argc) if (!IS_PLAYER(caller)) { print_to(caller, "You must be playing to spawn a monster"); return; } if (MUTATOR_CALLHOOK(AllowMobSpawning, caller)) { print_to(caller, M_ARGV(1, string)); return; } if (caller.vehicle) { print_to(caller, "You can't spawn monsters while driving a vehicle"); return; } - if (STAT(FROZEN, caller)) { print_to(caller, "You can't spawn monsters while frozen"); return; } if (IS_DEAD(caller)) { print_to(caller, "You can't spawn monsters while dead"); return; } if (tmp_moncount >= autocvar_g_monsters_max) { print_to(caller, "The maximum monster count has been reached"); return; } if (tmp_moncount >= autocvar_g_monsters_max_perplayer) { print_to(caller, "You can't spawn any more monsters"); return; } diff --git a/qcsrc/server/command/vote.qc b/qcsrc/server/command/vote.qc index 7c008c10e9..e2df71b7ff 100644 --- a/qcsrc/server/command/vote.qc +++ b/qcsrc/server/command/vote.qc @@ -373,8 +373,6 @@ void reset_map(bool is_fake_round_start) accuracy_reset(it); // for spectators too because weapon accuracy is persistent if (!IS_PLAYER(it)) continue; - if (STAT(FROZEN, it)) - Unfreeze(it, false); player_powerups_remove_all(it, true); entity store = PS(it); if (store) diff --git a/qcsrc/server/damage.qc b/qcsrc/server/damage.qc index cc7d68a425..fac8bb731a 100644 --- a/qcsrc/server/damage.qc +++ b/qcsrc/server/damage.qc @@ -498,108 +498,6 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .en CS(targ).killcount = 0; } -void Ice_Think(entity this) -{ - if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this) - { - delete(this); - return; - } - vector ice_org = this.owner.origin - '0 0 16'; - if (this.origin != ice_org) - setorigin(this, ice_org); - this.nextthink = time; -} - -void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint) -{ - if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed - return; - - if(STAT(FROZEN, targ)) - return; - - float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health); - - STAT(FROZEN, targ) = frozen_type; - STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0); - SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1)); - targ.revive_speed = revivespeed; - if(targ.bot_attack) - IL_REMOVE(g_bot_targets, targ); - targ.bot_attack = false; - targ.freeze_time = time; - - entity ice = new(ice); - ice.owner = targ; - ice.scale = targ.scale; - // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player - setthink(ice, Ice_Think); - ice.nextthink = time; - ice.frame = floor(random() * 21); // ice model has 20 different looking frames - setmodel(ice, MDL_ICE); - ice.alpha = 1; - ice.colormod = Team_ColorRGB(targ.team); - ice.glowmod = ice.colormod; - targ.iceblock = ice; - targ.revival_time = 0; - - Ice_Think(ice); - - RemoveGrapplingHooks(targ); - - FOREACH_CLIENT(IS_PLAYER(it), - { - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(it.(weaponentity).hook.aiment == targ) - RemoveHook(it.(weaponentity).hook); - } - }); - - // add waypoint - if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint) - WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT); -} - -void Unfreeze(entity targ, bool reset_health) -{ - if(!STAT(FROZEN, targ)) - return; - - if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING) - SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health)); - - targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen; - - STAT(FROZEN, targ) = 0; - STAT(REVIVE_PROGRESS, targ) = 0; - targ.revival_time = time; - if(!targ.bot_attack) - IL_PUSH(g_bot_targets, targ); - targ.bot_attack = true; - - WaypointSprite_Kill(targ.waypointsprite_attached); - - FOREACH_CLIENT(IS_PLAYER(it), - { - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(it.(weaponentity).hook.aiment == targ) - RemoveHook(it.(weaponentity).hook); - } - }); - - // remove the ice block - if(targ.iceblock) - delete(targ.iceblock); - targ.iceblock = NULL; - - MUTATOR_CALLHOOK(Unfreeze, targ); -} - void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) { float complainteamdamage = 0; @@ -737,55 +635,6 @@ void Damage(entity targ, entity inflictor, entity attacker, float damage, int de } } - if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype) - && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id) - { - if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage) - { - Unfreeze(targ, false); - SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health); - Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname); - Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF); - } - - damage = 0; - force *= autocvar_g_frozen_force; - } - - if(IS_PLAYER(targ) && STAT(FROZEN, targ) - && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger) - { - Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1); - - entity spot = SelectSpawnPoint(targ, false); - if(spot) - { - damage = 0; - targ.deadflag = DEAD_NO; - - targ.angles = spot.angles; - - targ.effects = 0; - targ.effects |= EF_TELEPORT_BIT; - - targ.angles_z = 0; // never spawn tilted even if the spot says to - targ.fixangle = true; // turn this way immediately - targ.velocity = '0 0 0'; - targ.avelocity = '0 0 0'; - targ.punchangle = '0 0 0'; - targ.punchvector = '0 0 0'; - targ.oldvelocity = targ.velocity; - - targ.spawnorigin = spot.origin; - setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24)); - // don't reset back to last position, even if new position is stuck in solid - targ.oldorigin = targ.origin; - - Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1); - } - } - if (targ == attacker) damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself @@ -802,6 +651,7 @@ void Damage(entity targ, entity inflictor, entity attacker, float damage, int de else victim = targ; + // TODO: allow the mutator hook to tell if the hit sound should be team or not if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker)) { if (DIFF_TEAM(victim, attacker)) @@ -1134,6 +984,7 @@ float RadiusDamage(entity inflictor, entity attacker, float coredamage, float ed bool Heal(entity targ, entity inflictor, float amount, float limit) { + // TODO: mutator hook to control healing if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ)) return false; @@ -1279,7 +1130,7 @@ void Fire_ApplyDamage(entity e) } e.fire_hitsound = true; - if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e)) + if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e) && !StatusEffects_active(STATUSEFFECT_Frozen, e)) { IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e, { diff --git a/qcsrc/server/damage.qh b/qcsrc/server/damage.qh index 93805b16af..1b3c59dbd5 100644 --- a/qcsrc/server/damage.qh +++ b/qcsrc/server/damage.qh @@ -25,10 +25,6 @@ float autocvar_g_balance_selfdamagepercent; float autocvar_g_friendlyfire; float autocvar_g_friendlyfire_virtual; float autocvar_g_friendlyfire_virtual_force; -float autocvar_g_frozen_revive_falldamage; -int autocvar_g_frozen_revive_falldamage_health; -bool autocvar_g_frozen_damage_trigger; -float autocvar_g_frozen_force; .void(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) event_damage; @@ -103,24 +99,6 @@ float Obituary_WeaponDeath( void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity); -// Frozen status effect -//const int FROZEN_NOT = 0; -const int FROZEN_NORMAL = 1; -const int FROZEN_TEMP_REVIVING = 2; -const int FROZEN_TEMP_DYING = 3; - -.float revival_time; // time at which player was last revived -.float revive_speed; // NOTE: multiplier (anything above 1 is instaheal) -.float freeze_time; -.entity iceblock; -.entity frozen_by; // for ice fields - -void Ice_Think(entity this); - -void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint); - -void Unfreeze(entity targ, bool reset_health); - // WEAPONTODO #define DMG_NOWEP (weaponentities[0]) diff --git a/qcsrc/server/hook.qc b/qcsrc/server/hook.qc index 26753d95ec..749afb1c85 100644 --- a/qcsrc/server/hook.qc +++ b/qcsrc/server/hook.qc @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -167,6 +168,7 @@ void GrapplingHookThink(entity this) // bit to control the rope bool frozen_pulling = (autocvar_g_grappling_hook_tarzan >= 2 && autocvar_g_balance_grapplehook_pull_frozen); + bool target_isfrozen = (STAT(FROZEN, this.aiment) || StatusEffects_active(STATUSEFFECT_Frozen, this.aiment)); vector dir = this.origin - myorg; dist = vlen(dir); @@ -215,7 +217,7 @@ void GrapplingHookThink(entity this) { entity aim_ent = ((IS_VEHICLE(this.aiment) && this.aiment.owner) ? this.aiment.owner : this.aiment); v = v - dv * 0.5; - if((frozen_pulling && STAT(FROZEN, this.aiment)) || !frozen_pulling) + if((frozen_pulling && target_isfrozen) || !frozen_pulling) { this.aiment.velocity = this.aiment.velocity - dv * 0.5; UNSET_ONGROUND(this.aiment); @@ -236,7 +238,7 @@ void GrapplingHookThink(entity this) if(!frozen_pulling && !(this.aiment.flags & FL_PROJECTILE)) pull_entity.velocity = WarpZone_RefSys_TransformVelocity(this, pull_entity, v * velocity_multiplier); - if(frozen_pulling && autocvar_g_balance_grapplehook_pull_frozen == 2 && !STAT(FROZEN, this.aiment)) + if(frozen_pulling && autocvar_g_balance_grapplehook_pull_frozen == 2 && !target_isfrozen) { RemoveHook(this); return; diff --git a/qcsrc/server/items/items.qc b/qcsrc/server/items/items.qc index 0e9836fd00..b778bdd97e 100644 --- a/qcsrc/server/items/items.qc +++ b/qcsrc/server/items/items.qc @@ -701,7 +701,6 @@ void Item_Touch(entity this, entity toucher) } if(!(toucher.flags & FL_PICKUPITEMS) - || STAT(FROZEN, toucher) || IS_DEAD(toucher) || (this.solid != SOLID_TRIGGER) || (this.owner == toucher) diff --git a/qcsrc/server/main.qc b/qcsrc/server/main.qc index b0433b9ea5..0cdc16fb1e 100644 --- a/qcsrc/server/main.qc +++ b/qcsrc/server/main.qc @@ -85,6 +85,7 @@ void CreatureFrame_hotliquids(entity this) } else { + // TODO: do we even need this hack? frozen players still die in lava! if (STAT(FROZEN, this)) { if (this.watertype == CONTENT_LAVA) diff --git a/qcsrc/server/mutators/events.qh b/qcsrc/server/mutators/events.qh index 19f84a3a81..b8f4c3addd 100644 --- a/qcsrc/server/mutators/events.qh +++ b/qcsrc/server/mutators/events.qh @@ -53,6 +53,14 @@ MUTATOR_HOOKABLE(PlayerSpawn, EV_PlayerSpawn); /**/ MUTATOR_HOOKABLE(PlayerWeaponSelect, EV_PlayerWeaponSelect); +/** called when setting the player's animation state */ +#define EV_PlayerAnim(i, o) \ + /** player */ i(entity, MUTATOR_ARGV_0_entity) \ + /** anim bits */ i(int, MUTATOR_ARGV_1_int) \ + /** */ o(int, MUTATOR_ARGV_1_int) \ + /**/ +MUTATOR_HOOKABLE(PlayerAnim, EV_PlayerAnim); + /** called in reset_map */ #define EV_reset_map_global(i, o) \ /**/ @@ -1242,24 +1250,6 @@ enum { MUT_VOTEPARSE_UNACCEPTABLE // return 0 (vote parameter counted as unacceptable, warns caller) }; -/** - * Called when freezing an entity (monster or player), return true to force showing a waypoint - */ -#define EV_Freeze(i, o) \ - /** targ */ i(entity, MUTATOR_ARGV_0_entity) \ - /** revive speed */ i(float, MUTATOR_ARGV_1_float) \ - /** frozen type */ i(int, MUTATOR_ARGV_2_int) \ - /**/ -MUTATOR_HOOKABLE(Freeze, EV_Freeze); - -/** - * Called when an entity (monster or player) is defrosted - */ -#define EV_Unfreeze(i, o) \ - /** targ */ i(entity, MUTATOR_ARGV_0_entity) \ - /**/ -MUTATOR_HOOKABLE(Unfreeze, EV_Unfreeze); - /** * Called when a player is trying to join, argument is the number of players allowed to join the match */ diff --git a/qcsrc/server/player.qc b/qcsrc/server/player.qc index 060f68541e..e2793e92a1 100644 --- a/qcsrc/server/player.qc +++ b/qcsrc/server/player.qc @@ -166,12 +166,16 @@ void player_anim(entity this) deadbits = 0; } int animbits = deadbits; - if(STAT(FROZEN, this)) + if(StatusEffects_active(STATUSEFFECT_Frozen, this)) animbits |= ANIMSTATE_FROZEN; if(this.move_movetype == MOVETYPE_FOLLOW) animbits |= ANIMSTATE_FOLLOW; if(IS_DUCKED(this)) animbits |= ANIMSTATE_DUCK; + + MUTATOR_CALLHOOK(PlayerAnim, this, animbits); + animbits = M_ARGV(1, int); + animdecide_setstate(this, animbits, false); animdecide_setimplicitstate(this, IS_ONGROUND(this)); } @@ -245,12 +249,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, } } - if (STAT(FROZEN, this)) - { - if (!ITEM_DAMAGE_NEEDKILL(deathtype)) - damage = 0; - } - else if (StatusEffects_active(STATUSEFFECT_SpawnShield, this) && autocvar_g_spawnshield_blockdamage < 1) + if (StatusEffects_active(STATUSEFFECT_SpawnShield, this) && autocvar_g_spawnshield_blockdamage < 1) damage *= 1 - bound(0, autocvar_g_spawnshield_blockdamage, 1); if(deathtype & HITTYPE_SOUND) // sound based attacks cause bleeding from the ears @@ -350,7 +349,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, if(take) this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen); - if (time > this.pain_finished && !STAT(FROZEN, this)) // Don't switch pain sequences like crazy + if (time > this.pain_finished && !STAT(FROZEN, this) && !StatusEffects_active(STATUSEFFECT_Frozen, this)) // Don't switch pain sequences like crazy { this.pain_finished = time + 0.5; //Supajoe @@ -433,7 +432,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, if ((dh || da) && !forbid_logging_damage) { float realdmg = damage - excess; - if ((this != attacker || deathtype == DEATH_KILL.m_id) && realdmg && !STAT(FROZEN, this) + if ((this != attacker || deathtype == DEATH_KILL.m_id) && realdmg && (!(round_handler_IsActive() && !round_handler_IsRoundStarted()) && time >= game_starttime)) { // accumulate damage, it will be logged later in this frame @@ -516,7 +515,6 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, ClientKill_Now_TeamChange(this); // can turn player into spectator // player could have been miraculously resuscitated ;) - // e.g. players in freezetag get frozen, they don't really die if(GetResource(this, RES_HEALTH) >= 1 || !(IS_PLAYER(this) || this.classname == "body")) return; @@ -525,8 +523,6 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, // when we get here, player actually dies - Unfreeze(this, false); // remove any icy remains - // clear waypoints WaypointSprite_PlayerDead(this); // throw a weapon diff --git a/qcsrc/server/utils.qh b/qcsrc/server/utils.qh index fee006ab21..89feab8156 100644 --- a/qcsrc/server/utils.qh +++ b/qcsrc/server/utils.qh @@ -22,7 +22,7 @@ const string STR_OBSERVER = "observer"; #define IS_VEHICLE(v) (v.vehicle_flags & VHF_ISVEHICLE) #define IS_TURRET(v) (v.turret_flags & TUR_FLAG_ISTURRET) -#define IS_MOVABLE(v) ((IS_PLAYER(v) || IS_MONSTER(v)) && !STAT(FROZEN, v)) +#define IS_MOVABLE(v) ((IS_PLAYER(v) || IS_MONSTER(v)) && !STAT(FROZEN, v) && !StatusEffects_active(STATUSEFFECT_Frozen, v)) #define IS_DEAD(s) ((s).deadflag != DEAD_NO) #define IS_INVISIBLE(v) (v.alpha <= 0.25 || StatusEffects_active(STATUSEFFECT_Invisibility, v) || MUTATOR_IS_ENABLED(cloaked)) diff --git a/qcsrc/server/weapons/accuracy.qc b/qcsrc/server/weapons/accuracy.qc index 7916a76e02..3573fe813b 100644 --- a/qcsrc/server/weapons/accuracy.qc +++ b/qcsrc/server/weapons/accuracy.qc @@ -115,10 +115,9 @@ bool accuracy_isgooddamage(entity attacker, entity targ) if (warmup_stage || game_stopped) return false; - // damage to dead/frozen players is good only if it happens in the frame they get killed / frozen + // damage to dead players is good only if it happens in the frame they get killed // so that stats for weapons that shoot multiple projectiles per shot are properly counted if (IS_DEAD(targ) && time > targ.death_time) return false; - if (STAT(FROZEN, targ) && time > targ.freeze_time) return false; if (SAME_TEAM(attacker, targ)) return false; if (mutator_check == MUT_ACCADD_INVALID) return true; diff --git a/qcsrc/server/weapons/weaponsystem.qc b/qcsrc/server/weapons/weaponsystem.qc index dccec6c480..148b1733fc 100644 --- a/qcsrc/server/weapons/weaponsystem.qc +++ b/qcsrc/server/weapons/weaponsystem.qc @@ -428,7 +428,7 @@ bool weaponLocked(entity player) if (time < game_starttime && !sv_ready_restart_after_countdown) return true; if (player.player_blocked) return true; if (game_stopped) return true; - if (STAT(FROZEN, player)) return true; + if (StatusEffects_active(STATUSEFFECT_Frozen, player)) return true; if (MUTATOR_CALLHOOK(LockWeapon, player)) return true; return false; } -- 2.39.5