/**/
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';
}
// 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))
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;
+}
// 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)))
{
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();
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_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;
{
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, 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);
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))
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();
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);
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);
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);
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);
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(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);
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),
+ && 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)
{
}
}
-#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
+ 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
--- /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);
//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)
// 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 CENTER_OR_VIEWOFS(ent) (ent.origin + (IS_PLAYER(ent) ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5)))
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;
}