From: Mario Date: Wed, 12 Feb 2025 08:11:29 +0000 (+1000) Subject: Implement a Frozen status effect for ice nades, migrate Freeze Tag frozen elimination... X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=37e06527b8654cfd9b35c4549ce6ece69592583c;p=xonotic%2Fxonotic-data.pk3dir.git Implement a Frozen status effect for ice nades, migrate Freeze Tag frozen elimination state back into the Freeze Tag code --- diff --git a/qcsrc/client/mutators/events.qh b/qcsrc/client/mutators/events.qh index 6f6f34399..0b5a9a6aa 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 6fc7d063a..238fd4c87 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/gamemodes/gamemode/ctf/sv_ctf.qc b/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc index 858b3a862..d1040754c 100644 --- a/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc +++ b/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc @@ -1140,6 +1140,7 @@ 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)) diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/cl_freezetag.qc b/qcsrc/common/gamemodes/gamemode/freezetag/cl_freezetag.qc index df4931a37..8a3eeeb01 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 a9f0143a8..b7db43d0f 100644 --- a/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc +++ b/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc @@ -196,23 +196,111 @@ 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) = FROZEN_NORMAL; + 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 && 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; + + 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))) @@ -333,7 +421,7 @@ void ft_RemovePlayer(entity this) { if (STAT(FROZEN, this) != FROZEN_NORMAL) 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); @@ -364,7 +475,7 @@ MUTATOR_HOOKFUNCTION(ft, PlayerDies) if(round_handler_CountdownRunning()) { if (STAT(FROZEN, frag_target) == FROZEN_NORMAL) - Unfreeze(frag_target, true); + freezetag_Unfreeze(frag_target, true); freezetag_count_alive_players(); frag_target.respawn_time = time; frag_target.respawn_flags |= RESPAWN_FORCE; @@ -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,19 +609,43 @@ 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, PlayerDamage_SplitHealthArmor) +{ + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(6, float); + + if(STAT(FROZEN, frag_target) && !ITEM_DAMAGE_NEEDKILL(frag_deathtype)) + M_ARGV(4, float) = 0; } 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); @@ -525,12 +677,96 @@ 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 #undef IN_REVIVING_RANGE #endif +void freezetag_PlayerFrame(entity this) +{ + 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) + freezetag_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) + freezetag_Unfreeze(this, false); + } + } +} + #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)) @@ -644,7 +880,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(); @@ -702,9 +938,36 @@ MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) WaypointSprite_UpdateHealth(player_wp, STAT(REVIVE_PROGRESS, player)); } + freezetag_PlayerFrame(player); + 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 +983,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); diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh b/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh index 049f03714..49d32d494 100644 --- a/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh +++ b/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh @@ -48,3 +48,22 @@ 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 state (elimination) +// TODO: use a simple boolean for frozen state +//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 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 bba3baa3c..bab20b65b 100644 --- a/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qc +++ b/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qc @@ -116,10 +116,10 @@ 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(toucher.ballcarried) { return; } 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 c8a82b0c1..5c05d4b30 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 d3d3a6a9d..744ae3619 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 5838c6242..5d8425789 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 62aff020d..e59ead16c 100644 --- a/qcsrc/common/gamemodes/gamemode/tka/sv_tka.qc +++ b/qcsrc/common/gamemodes/gamemode/tka/sv_tka.qc @@ -96,7 +96,6 @@ void tka_TouchEvent(entity this, entity toucher) // runs any time that the ball if(toucher.ballcarried) { return; } 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 4600fe2e7..7524aa06d 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 5ce983e18..0e82b85de 100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@ -133,7 +133,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)) @@ -804,7 +803,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); @@ -976,7 +975,6 @@ void Monster_Remove(entity this) if(this.(weaponentity)) delete(this.(weaponentity)); } - if(this.iceblock) { delete(this.iceblock); } WaypointSprite_Kill(this.sprite); delete(this); } @@ -1027,8 +1025,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; @@ -1062,9 +1058,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); @@ -1114,9 +1107,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; @@ -1265,7 +1255,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... @@ -1282,43 +1272,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) @@ -1328,6 +1281,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) @@ -1370,8 +1324,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 cf4d890c1..c75ac5d57 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 24feb75be..f681643c1 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), + && it.iscreature && !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/sv_nades.qc b/qcsrc/common/mutators/mutator/nades/sv_nades.qc index a14787861..f1d90d495 100644 --- a/qcsrc/common/mutators/mutator/nades/sv_nades.qc +++ b/qcsrc/common/mutators/mutator/nades/sv_nades.qc @@ -686,15 +686,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); @@ -761,64 +752,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) @@ -900,7 +833,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 619c393e4..84a5f055b 100644 --- a/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc +++ b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc @@ -129,7 +129,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 4c68c1bad..d0e368edb 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 41349d1e0..720c8bf73 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 ad12a0f29..a75df6058 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 000000000..76e02d0ea --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/status_effect/frozen.qc @@ -0,0 +1,75 @@ +#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 + setmodel(ice, MDL_ICE); + ice.alpha = 0.5; + + this.frozen_ice = 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 000000000..be57a0a3e --- /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 3f6d26a9e..51f15ecbe 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 97b1cec68..722a00d1a 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 519159d91..9707138c3 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/physics/player.qc b/qcsrc/common/physics/player.qc index b007afaf1..71279afd6 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 e2e907767..c6a2112dd 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/turrets/sv_turrets.qc b/qcsrc/common/turrets/sv_turrets.qc index 9a9a902c2..3545e734c 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 f18d867c6..c24bb677c 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 6e6cc8a68..c2a0a7c7b 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 0a02e50d1..985cffebb 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 5a9ef8f62..802ccc06b 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 8fc33448b..c34af9389 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 9c2aaf892..93dcc3e36 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; @@ -1292,8 +1284,6 @@ void ClientDisconnect(entity this) Portal_ClearAll(this); - Unfreeze(this, false); - RemoveGrapplingHooks(this); strfree(this.shootfromfixedorigin); @@ -1696,7 +1686,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; @@ -1812,8 +1801,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; @@ -2562,7 +2549,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); @@ -2682,6 +2669,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) { @@ -2862,39 +2850,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 92bbfde4b..3d04c13c0 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 a197312c5..bc2e655de 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 7c008c10e..e2df71b7f 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 b67afb712..7b7a95c61 100644 --- a/qcsrc/server/damage.qc +++ b/qcsrc/server/damage.qc @@ -503,108 +503,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; @@ -742,55 +640,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 @@ -807,6 +656,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)) @@ -1139,6 +989,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; @@ -1284,7 +1135,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 bd9e10f97..63222e8eb 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; @@ -105,24 +101,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 26753d95e..749afb1c8 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 6b9b4c41e..16216410c 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 b0433b9ea..0cdc16fb1 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 19f84a3a8..b8f4c3add 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 060f68541..e2793e92a 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 ae0c284fe..94aa70f63 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 CENTER_OR_VIEWOFS(ent) (ent.origin + (IS_PLAYER(ent) ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5))) diff --git a/qcsrc/server/weapons/accuracy.qc b/qcsrc/server/weapons/accuracy.qc index 7916a76e0..3573fe813 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 dccec6c48..148b1733f 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; }