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
// 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"
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)"
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)"
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"
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"
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"
/**/
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 */
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))
{
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));
// 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';
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, "")
}
// 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)
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;
+}
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;
}
{
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;
// 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;
}
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;
// 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;
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();
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);
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;
{
// 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)
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);
{
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
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), {
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;
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
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);
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;
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)
// 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
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)
{
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)
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();
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))
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);
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);
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));
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);
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);
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;
//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);
}
if(!IS_PLAYER(toucher)) { return; }
- if(STAT(FROZEN, toucher)) { return; }
if(IS_DEAD(toucher)) { return; }
if ( SAME_TEAM(this,toucher) )
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;
}
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);
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;
|| (!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))
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);
if(this.(weaponentity))
delete(this.(weaponentity));
}
- if(this.iceblock) { delete(this.iceblock); }
WaypointSprite_Kill(this.sprite);
delete(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;
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);
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;
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...
*/
}
-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)
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)
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);
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)
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);
#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)
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)
{
#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;
}
}
-#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);
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)
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;
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;
}
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);
// genmod.sh autogenerated file; do not modify
#include <common/mutators/mutator/status_effects/status_effect/burning.qc>
+#include <common/mutators/mutator/status_effects/status_effect/frozen.qc>
#include <common/mutators/mutator/status_effects/status_effect/spawnshield.qc>
#include <common/mutators/mutator/status_effects/status_effect/stunned.qc>
#include <common/mutators/mutator/status_effects/status_effect/superweapons.qc>
// genmod.sh autogenerated file; do not modify
#include <common/mutators/mutator/status_effects/status_effect/burning.qh>
+#include <common/mutators/mutator/status_effects/status_effect/frozen.qh>
#include <common/mutators/mutator/status_effects/status_effect/spawnshield.qh>
#include <common/mutators/mutator/status_effects/status_effect/stunned.qh>
#include <common/mutators/mutator/status_effects/status_effect/superweapons.qh>
--- /dev/null
+#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
--- /dev/null
+#pragma once
+
+#include <common/mutators/mutator/status_effects/all.qh>
+
+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));
#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);
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);
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);
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"), "")
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"), "")
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")))
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)
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)
//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;
}
#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)
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)
// 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))
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; }
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;
return false;
}
- if(STAT(FROZEN, targ))
- return false;
-
if(teamplay)
{
if(targ.team==0)
}
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
}
// 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))
{
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);
#include <common/mapobjects/func/ladder.qh>
#include <common/mapobjects/trigger/hurt.qh>
#include <common/mapobjects/trigger/jumppads.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/net_linked.qh>
#include <common/stats.qh>
#include <common/weapons/_all.qh>
RemoveGrapplingHooks(this);
Portal_ClearAll(this);
- Unfreeze(this, false);
SetSpectatee(this, NULL);
if (this.alivetime)
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);
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;
}
});
- Unfreeze(this, false);
-
MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
{
string s = spot.target;
Portal_ClearAll(this);
- Unfreeze(this, false);
-
RemoveGrapplingHooks(this);
strfree(this.shootfromfixedorigin);
float rotstable, regenstable, rotframetime, regenframetime;
if(!mutator_returnvalue)
- if(!STAT(FROZEN, this))
{
regenstable = autocvar_g_balance_armor_regenstable;
rotstable = autocvar_g_balance_armor_rotstable;
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;
}
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);
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)
{
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,
{
// 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);
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; }
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)
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;
}
}
- 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
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))
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;
}
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,
{
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;
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])
#include <common/constants.qh>
#include <common/effects/all.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/net_linked.qh>
#include <common/physics/player.qh>
#include <common/state.qh>
// 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);
{
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);
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;
}
if(!(toucher.flags & FL_PICKUPITEMS)
- || STAT(FROZEN, toucher)
|| IS_DEAD(toucher)
|| (this.solid != SOLID_TRIGGER)
|| (this.owner == toucher)
}
else
{
+ // TODO: do we even need this hack? frozen players still die in lava!
if (STAT(FROZEN, this))
{
if (this.watertype == CONTENT_LAVA)
/**/
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) \
/**/
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
*/
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));
}
}
}
- 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
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
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
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;
// when we get here, player actually dies
- Unfreeze(this, false); // remove any icy remains
-
// clear waypoints
WaypointSprite_PlayerDead(this);
// throw a weapon
#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))
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;
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;
}