drawsubpic(pos + eX * mySize.x - eX * min(mySize.x * 0.5, mySize.y), eX * min(mySize.x * 0.5, mySize.y) + eY * mySize.y, pic, '0.75 0 0', '0.25 1 0', color, theAlpha, drawflag);
}
-void DrawNumIcon_expanding(vector myPos, vector mySize, float theTime, string icon, bool vertical, int icon_right_align, vector color, float theAlpha, float fadelerp)
+void DrawNumIcon_expanding(vector myPos, vector mySize, float theTime, string icon, bool vertical, bool isInfinite, int icon_right_align, vector color, float theAlpha, float fadelerp)
{
TC(bool, vertical); TC(int, icon_right_align);
vector newPos = '0 0 0', newSize = '0 0 0';
vector picpos, numpos;
+ string text = isInfinite ? "\xE2\x88\x9E" : ftos(theTime); // Use infinity symbol (U+221E)
if (vertical)
{
// reduce only y to draw numbers with different number of digits with the same y size
numpos.y += newSize.y * ((1 - 0.7) / 2);
newSize.y *= 0.7;
- drawstring_aspect(numpos, ftos(theTime), newSize, color, panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL);
+ drawstring_aspect(numpos, text, newSize, color, panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL);
return;
}
// NOTE: newSize_x is always equal to 3 * mySize_y so we can use
// '2 1 0' * newSize_y instead of eX * (2/3) * newSize_x + eY * newSize_y
- drawstring_aspect_expanding(numpos, ftos(theTime), '2 1 0' * newSize.y, color, panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL, fadelerp);
+ drawstring_aspect_expanding(numpos, text, '2 1 0' * newSize.y, color, panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL, fadelerp);
drawpic_aspect_skin_expanding(picpos, icon, '1 1 0' * newSize.y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL, fadelerp);
}
-void DrawNumIcon(vector myPos, vector mySize, float theTime, string icon, bool vertical, int icon_right_align, vector color, float theAlpha)
+void DrawNumIcon(vector myPos, vector mySize, float theTime, string icon, bool vertical, bool isInfinite, int icon_right_align, vector color, float theAlpha)
{
TC(bool, vertical); TC(int, icon_right_align);
- DrawNumIcon_expanding(myPos, mySize, theTime, icon, vertical, icon_right_align, color, theAlpha, 0);
+ DrawNumIcon_expanding(myPos, mySize, theTime, icon, vertical, isInfinite, icon_right_align, color, theAlpha, 0);
}
/*
float HUD_GetRowCount(int item_count, vector size, float item_aspect);
vector HUD_Get_Num_Color(float hp, float maxvalue, bool blink);
-void DrawNumIcon(vector myPos, vector mySize, float theTime, string icon, bool vertical, bool icon_right_align, vector color, float theAlpha);
-void DrawNumIcon_expanding(vector myPos, vector mySize, float theTime, string icon, bool vertical, int icon_right_align, vector color, float theAlpha, float fadelerp);
+void DrawNumIcon(vector myPos, vector mySize, float theTime, string icon, bool vertical, bool isInfinite, bool icon_right_align, vector color, float theAlpha);
+void DrawNumIcon_expanding(vector myPos, vector mySize, float theTime, string icon, bool vertical, bool isInfinite, int icon_right_align, vector color, float theAlpha, float fadelerp);
void HUD_Panel_DrawHighlight(vector pos, vector mySize, vector color, float theAlpha, int drawflag);
vector HUD_GetTableSize_BestItemAR(int item_count, vector psize, float item_aspect);
drawpic_aspect_skin(pos + eX * mySize.x - eX * 0.5 * mySize.y, "health", '0.5 0.5 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
}
if(autocvar_hud_panel_healtharmor_text)
- DrawNumIcon(pos, mySize, hp, biggercount, 0, iconalign, HUD_Get_Num_Color(hp, maxtotal, true), 1);
+ DrawNumIcon(pos, mySize, hp, biggercount, false, false, iconalign, HUD_Get_Num_Color(hp, maxtotal, true), 1);
if(fuel)
HUD_Panel_DrawProgressBar(pos, vec2(mySize.x, 0.2 * mySize.y), "progressbar", fuel/100, 0, (baralign == 1 || baralign == 3), autocvar_hud_progressbar_fuel_color, panel_fg_alpha * 0.8, DRAWFLAG_NORMAL);
HUD_Panel_DrawProgressBar(pos + health_offset, mySize, autocvar_hud_panel_healtharmor_progressbar_health, p_health/maxhealth, is_vertical, health_baralign, autocvar_hud_progressbar_health_color, autocvar_hud_progressbar_alpha * panel_fg_alpha * pain_health_alpha, DRAWFLAG_NORMAL);
}
if(autocvar_hud_panel_healtharmor_text)
- DrawNumIcon(pos + health_offset, mySize, health, "health", is_vertical, health_iconalign, HUD_Get_Num_Color(health, maxhealth, true), 1);
+ DrawNumIcon(pos + health_offset, mySize, health, "health", is_vertical, false, health_iconalign, HUD_Get_Num_Color(health, maxhealth, true), 1);
}
//if(armor)
}
if(!autocvar_hud_panel_healtharmor_progressbar || p_armor)
if(autocvar_hud_panel_healtharmor_text)
- DrawNumIcon(pos + armor_offset, mySize, armor, "armor", is_vertical, armor_iconalign, HUD_Get_Num_Color(armor, maxarmor, true), 1);
+ DrawNumIcon(pos + armor_offset, mySize, armor, "armor", is_vertical, false, armor_iconalign, HUD_Get_Num_Color(armor, maxarmor, true), 1);
}
vector cell_size = mySize;
.vector colormod; // Color
.float count; // Time left
.float lifetime; // Maximum time
+.float cnt; // Infinite timer
entity powerupItems;
int powerupItemsCount;
powerupItemsCount = 0;
}
-void addPowerupItem(string name, string icon, vector color, float currentTime, float lifeTime)
+void addPowerupItem(string name, string icon, vector color, float currentTime, float lifeTime, bool isInfinite)
{
if(!powerupItems)
powerupItems = spawn();
item.colormod = color;
item.count = currentTime;
item.lifetime = lifeTime;
+ item.cnt = isInfinite;
++powerupItemsCount;
}
void HUD_Powerups()
{
- int allItems = STAT(ITEMS);
- int allBuffs = STAT(BUFFS);
- float strengthTime, shieldTime, superTime;
-
// Initialize items
if(!autocvar__hud_configure)
{
return;
if(STAT(HEALTH) <= 0 && autocvar_hud_panel_powerups_hide_ondeath)
return;
- //if(!(allItems & (ITEM_Strength.m_itemid | ITEM_Shield.m_itemid | IT_SUPERWEAPON)) && !allBuffs) return;
-
- strengthTime = bound(0, STAT(STRENGTH_FINISHED) - time, 99);
- shieldTime = bound(0, STAT(INVINCIBLE_FINISHED) - time, 99);
- superTime = bound(0, STAT(SUPERWEAPONS_FINISHED) - time, 99);
-
- if(allItems & IT_UNLIMITED_SUPERWEAPONS)
- superTime = 99;
-
- // Prevent stuff to show up on mismatch that will be fixed next frame
- if(!(allItems & IT_SUPERWEAPON))
- superTime = 0;
- }
- else
- {
- strengthTime = 15;
- shieldTime = 27;
- superTime = 13;
- allBuffs = 0;
}
// Add items to linked list
resetPowerupItems();
- if(strengthTime)
- addPowerupItem("Strength", "strength", autocvar_hud_progressbar_strength_color, strengthTime, 30);
- if(shieldTime)
- addPowerupItem("Shield", "shield", autocvar_hud_progressbar_shield_color, shieldTime, 30);
- if(superTime && !(allItems & IT_UNLIMITED_SUPERWEAPONS))
- addPowerupItem("Superweapons", "superweapons", autocvar_hud_progressbar_superweapons_color, superTime, 30);
-
MUTATOR_CALLHOOK(HUD_Powerups_add);
if(!powerupItemsCount)
fullSeconds = ceil(item.count);
textColor = '0.6 0.6 0.6' + (item.colormod * 0.4);
- if(item.count > 1)
- DrawNumIcon(itemPos, itemSize, fullSeconds, item.netname, isVertical, align, textColor, panel_fg_alpha);
- if(item.count <= 5)
- DrawNumIcon_expanding(itemPos, itemSize, fullSeconds, item.netname, isVertical, align, textColor, panel_fg_alpha, bound(0, (fullSeconds - item.count) / 0.5, 1));
+ if(item.cnt)
+ DrawNumIcon(itemPos, itemSize, fullSeconds, item.netname, isVertical, true, align, textColor, panel_fg_alpha);
+ else
+ {
+ if(item.count > 1)
+ DrawNumIcon(itemPos, itemSize, fullSeconds, item.netname, isVertical, false, align, textColor, panel_fg_alpha);
+ if(item.count <= 5)
+ DrawNumIcon_expanding(itemPos, itemSize, fullSeconds, item.netname, isVertical, false, align, textColor, panel_fg_alpha, bound(0, (fullSeconds - item.count) / 0.5, 1));
+ }
}
// Determine next section
vector autocvar_hud_progressbar_strength_color;
vector autocvar_hud_progressbar_superweapons_color;
-void addPowerupItem(string name, string icon, vector color, float currentTime, float lifeTime);
+void addPowerupItem(string name, string icon, vector color, float currentTime, float lifeTime, bool isInfinite);
#include <common/mapobjects/trigger/viewloc.qh>
#include <common/minigames/cl_minigames.qh>
#include <common/minigames/cl_minigames_hud.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/mutators/mutator/waypoints/all.qh>
#include <common/net_linked.qh>
#include <common/net_notice.qh>
}
// edge detection postprocess handling done second (used by hud_powerup)
- float sharpen_intensity = 0, strength_finished = STAT(STRENGTH_FINISHED), invincible_finished = STAT(INVINCIBLE_FINISHED);
+ float sharpen_intensity = 0, strength_finished = StatusEffects_gettime(STATUSEFFECT_Strength, g_statuseffects), invincible_finished = StatusEffects_gettime(STATUSEFFECT_Shield, g_statuseffects);
if (strength_finished - time > 0) { sharpen_intensity += (strength_finished - time); }
if (invincible_finished - time > 0) { sharpen_intensity += (invincible_finished - time); }
#ifdef SVQC
.float strength_finished; // NOTE: this field is used only by map entities, it does not directly apply the strength stat
.float invincible_finished; // ditto
+.float buffs_finished; // ditts
#define spawnfunc_body(item) \
if (!Item_IsDefinitionAllowed(item)) \
#include <common/mutators/mutator/sandbox/_mod.inc>
#include <common/mutators/mutator/spawn_near_teammate/_mod.inc>
#include <common/mutators/mutator/stale_move_negation/_mod.inc>
+#include <common/mutators/mutator/status_effects/_mod.inc>
#include <common/mutators/mutator/superspec/_mod.inc>
#include <common/mutators/mutator/touchexplode/_mod.inc>
#include <common/mutators/mutator/vampire/_mod.inc>
#include <common/mutators/mutator/sandbox/_mod.qh>
#include <common/mutators/mutator/spawn_near_teammate/_mod.qh>
#include <common/mutators/mutator/stale_move_negation/_mod.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/mutators/mutator/superspec/_mod.qh>
#include <common/mutators/mutator/touchexplode/_mod.qh>
#include <common/mutators/mutator/vampire/_mod.qh>
REGISTER_BUFF(AMMO) {
this.m_name = _("Ammo");
this.netname = "ammo";
+ this.m_icon = "buff_ammo";
this.m_skin = 3;
this.m_color = '0.76 1 0.1';
}
REGISTER_BUFF(RESISTANCE) {
this.m_name = _("Resistance");
this.netname = "resistance";
+ this.m_icon = "buff_resistance";
this.m_skin = 0;
this.m_color = '0.36 1 0.07';
}
REGISTER_BUFF(SPEED) {
this.m_name = _("Speed");
this.netname = "speed";
+ this.m_icon = "buff_speed";
this.m_skin = 9;
this.m_color = '0.1 1 0.84';
}
REGISTER_BUFF(MEDIC) {
this.m_name = _("Medic");
this.netname = "medic";
+ this.m_icon = "buff_medic";
this.m_skin = 1;
this.m_color = '1 0.12 0';
}
REGISTER_BUFF(BASH) {
this.m_name = _("Bash");
this.netname = "bash";
+ this.m_icon = "buff_bash";
this.m_skin = 5;
this.m_color = '1 0.39 0';
}
REGISTER_BUFF(VAMPIRE) {
this.m_name = _("Vampire");
this.netname = "vampire";
+ this.m_icon = "buff_vampire";
this.m_skin = 2;
this.m_color = '1 0 0.24';
}
REGISTER_BUFF(DISABILITY) {
this.m_name = _("Disability");
this.netname = "disability";
+ this.m_icon = "buff_disability";
this.m_skin = 7;
this.m_color = '0.94 0.3 1';
}
REGISTER_BUFF(VENGEANCE) {
this.m_name = _("Vengeance");
this.netname = "vengeance";
+ this.m_icon = "buff_vengeance";
this.m_skin = 15;
this.m_color = '1 0.23 0.61';
}
REGISTER_BUFF(JUMP) {
this.m_name = _("Jump");
this.netname = "jump";
+ this.m_icon = "buff_jump";
this.m_skin = 10;
this.m_color = '0.24 0.78 1';
}
REGISTER_BUFF(INVISIBLE) {
this.m_name = _("Invisible");
this.netname = "invisible";
+ this.m_icon = "buff_invisible";
this.m_skin = 12;
this.m_color = '0.5 0.5 1';
}
REGISTER_BUFF(INFERNO) {
this.m_name = _("Inferno");
this.netname = "inferno";
+ this.m_icon = "buff_inferno";
this.m_skin = 16;
this.m_color = '1 0.62 0';
}
REGISTER_BUFF(SWAPPER) {
this.m_name = _("Swapper");
this.netname = "swapper";
+ this.m_icon = "buff_swapper";
this.m_skin = 17;
this.m_color = '0.63 0.36 1';
}
REGISTER_BUFF(MAGNET) {
this.m_name = _("Magnet");
this.netname = "magnet";
+ this.m_icon = "buff_magnet";
this.m_skin = 18;
this.m_color = '1 0.95 0.18';
}
REGISTER_BUFF(LUCK) {
this.m_name = _("Luck");
this.netname = "luck";
+ this.m_icon = "buff_luck";
this.m_skin = 19;
this.m_color = '1 0.23 0.44';
}
REGISTER_BUFF(FLIGHT) {
this.m_name = _("Flight");
this.netname = "flight";
+ this.m_icon = "buff_flight";
this.m_skin = 11;
this.m_color = '0.23 0.44 1';
}
string BUFF_NAME(int i)
{
- Buff b = REGISTRY_GET(Buffs, i);
+ Buff b = REGISTRY_GET(StatusEffect, i);
return strcat(rgb_to_hexcolor(b.m_color), b.m_name);
}
-
-entity buff_FirstFromFlags(int _buffs)
-{
- if (flags)
- {
- FOREACH(Buffs, it.m_itemid & _buffs, { return it; });
- }
- return BUFF_Null;
-}
REGISTER_RADARICON(Buff, 1);
#endif
-REGISTRY(Buffs, BITS(5))
-REGISTER_REGISTRY(Buffs)
-REGISTRY_CHECK(Buffs)
-
#define REGISTER_BUFF(id) \
- REGISTER(Buffs, BUFF_##id, m_id, NEW(Buff))
+ REGISTER(StatusEffect, BUFF_##id, m_id, NEW(Buff))
-#include <common/items/item/pickup.qh>
-CLASS(Buff, Pickup)
+#include <common/mutators/mutator/status_effects/_mod.qh>
+CLASS(Buff, StatusEffects)
/** bit index */
ATTRIB(Buff, m_itemid, int, 0);
ATTRIB(Buff, netname, string, "buff");
+ ATTRIB(Buff, m_icon, string, "buff");
ATTRIB(Buff, m_color, vector, '1 1 1');
ATTRIB(Buff, m_name, string, "Buff");
ATTRIB(Buff, m_skin, int, 0);
+ ATTRIB(Buff, m_lifetime, float, 60);
ATTRIB(Buff, m_sprite, string, "");
METHOD(Buff, display, void(entity this, void(string name, string icon) returns)) {
- returns(this.m_name, sprintf("/gfx/hud/%s/buff_%s", cvar_string("menu_skin"), this.netname));
+ returns(this.m_name, sprintf("/gfx/hud/%s/%s", cvar_string("menu_skin"), this.m_icon));
}
#ifdef SVQC
METHOD(Buff, m_time, float(Buff this))
ENDCLASS(Buff)
STATIC_INIT(REGISTER_BUFFS) {
- FOREACH(Buffs, true, {
+ FOREACH(StatusEffect, it.instanceOfBuff, {
it.m_itemid = BIT(it.m_id - 1);
it.m_sprite = strzone(strcat("buff-", it.netname));
});
}
#ifdef SVQC
+ .entity buffdef;
void buff_Init(entity ent);
void buff_Init_Compat(entity ent, entity replacement);
#define BUFF_SPAWNFUNC(e, b, t) spawnfunc(item_buff_##e) { \
- STAT(BUFFS, this) = b.m_itemid; \
+ this.buffdef = b; \
this.team = t; \
buff_Init(this); \
}
#endif
string Buff_UndeprecateName(string buffname);
-entity buff_FirstFromFlags(int _buffs);
-REGISTER_BUFF(Null);
-BUFF_SPAWNFUNCS(random, BUFF_Null)
+BUFF_SPAWNFUNCS(random, NULL)
-REGISTRY_DEFINE_GET(Buffs, BUFF_Null)
#include "all.inc"
#include "cl_buffs.qh"
REGISTER_MUTATOR(cl_buffs, true);
-MUTATOR_HOOKFUNCTION(cl_buffs, HUD_Powerups_add)
-{
- int allBuffs = STAT(BUFFS);
- if (allBuffs)
- FOREACH(Buffs, it.m_itemid & allBuffs, {
- addPowerupItem(it.m_name, strcat("buff_", it.netname), it.m_color, bound(0, STAT(BUFF_TIME) - time, 99), 60);
- });
-}
MUTATOR_HOOKFUNCTION(cl_buffs, WP_Format)
{
entity this = M_ARGV(0, entity);
string s = M_ARGV(1, string);
if (s == WP_Buff.netname || s == RADARICON_Buff.netname)
{
- Buff b = REGISTRY_GET(Buffs, this.wp_extra);
+ Buff b = REGISTRY_GET(StatusEffect, this.wp_extra);
M_ARGV(2, vector) = b.m_color;
M_ARGV(3, string) = b.m_name;
- M_ARGV(4, string) = strcat("buff_", b.netname);
+ M_ARGV(4, string) = b.m_icon;
return true;
}
}
// buff item
bool buff_Waypoint_visible_for_player(entity this, entity player, entity view)
{
- if(!this.owner.buff_active && !this.owner.buff_activetime)
+ if(!this.owner.buff_active && !this.owner.buff_activetime || !this.owner.buffdef)
return false;
- if (STAT(BUFFS, view))
+ entity heldbuff = buff_FirstFromFlags(view); // TODO: cache this information so it isn't performing a loop every frame
+ if (heldbuff)
{
- return CS_CVAR(view).cvar_cl_buffs_autoreplace == false || STAT(BUFFS, view) != STAT(BUFFS, this.owner);
+ return CS_CVAR(view).cvar_cl_buffs_autoreplace == false || heldbuff != this.owner.buffdef;
}
return WaypointSprite_visible_for_player(this, player, view);
{
if(autocvar_g_buffs_waypoint_distance <= 0) return;
- entity buff = buff_FirstFromFlags(STAT(BUFFS, e));
+ entity buff = e.buffdef;
entity wp = WaypointSprite_Spawn(WP_Buff, 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs.z, NULL, e.team, e, buff_waypoint, true, RADARICON_Buff);
wp.wp_extra = buff.m_id;
WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_Buff, e.glowmod);
if((this.team && DIFF_TEAM(toucher, this))
|| (STAT(FROZEN, toucher))
|| (toucher.vehicle)
+ || (!this.buffdef) // TODO: error out or maybe reset type if this occurs?
|| (time < PS(toucher).buff_shield)
)
{
return;
}
- if (STAT(BUFFS, toucher))
+ entity heldbuff = buff_FirstFromFlags(toucher);
+ entity thebuff = this.buffdef;
+
+ if (heldbuff)
{
- if (CS_CVAR(toucher).cvar_cl_buffs_autoreplace && STAT(BUFFS, toucher) != STAT(BUFFS, this))
+ if (CS_CVAR(toucher).cvar_cl_buffs_autoreplace && heldbuff != thebuff)
{
// TODO: lost-gained notification for this case
- int buffid = buff_FirstFromFlags(STAT(BUFFS, toucher)).m_id;
+ int buffid = heldbuff.m_id;
Send_Notification(NOTIF_ONE, toucher, MSG_INFO, INFO_ITEM_BUFF_LOST, toucher.netname, buffid);
if(!IS_INDEPENDENT_PLAYER(toucher))
Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_INFO, INFO_ITEM_BUFF_LOST, toucher.netname, buffid);
- STAT(BUFFS, toucher) = 0;
//sound(toucher, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
}
else { return; } // do nothing
this.owner = toucher;
this.buff_active = false;
this.lifetime = 0;
- entity thebuff = buff_FirstFromFlags(STAT(BUFFS, this));
Send_Notification(NOTIF_ONE, toucher, MSG_MULTI, ITEM_BUFF_GOT, thebuff.m_id);
if(!IS_INDEPENDENT_PLAYER(toucher))
Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_INFO, INFO_ITEM_BUFF, toucher.netname, thebuff.m_id);
Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(this), '0 0 0', 1);
sound(toucher, CH_TRIGGER, SND_SHIELD_RESPAWN, VOL_BASE, ATTN_NORM);
- STAT(BUFFS, toucher) |= (STAT(BUFFS, this));
- STAT(LAST_PICKUP, toucher) = time;
- float bufftime = ((this.count) ? this.count : thebuff.m_time(thebuff));
+ float oldtime = StatusEffects_gettime(thebuff, toucher);
+ float bufftime = ((this.buffs_finished) ? this.buffs_finished : thebuff.m_time(thebuff));
+
+ buff_RemoveAll(toucher, STATUSEFFECT_REMOVE_NORMAL); // remove previous buffs so that a new one may be added
if(bufftime)
- STAT(BUFF_TIME, toucher) = min(time + bufftime, max(STAT(BUFF_TIME, toucher), time) + bufftime);
+ StatusEffects_apply(thebuff, toucher, min(time + bufftime, max(oldtime, time) + bufftime), 0);
+ else
+ StatusEffects_apply(thebuff, toucher, time + 999, 0); // HACK: zero timer means "infinite"!
+
+ STAT(LAST_PICKUP, toucher) = time;
}
float buff_Available(entity buff)
{
- if (buff == BUFF_Null)
+ if (!buff)
return false;
if (buff == BUFF_AMMO && ((start_items & IT_UNLIMITED_AMMO) || cvar("g_melee_only")))
return false;
void buff_NewType(entity ent)
{
RandomSelection_Init();
- FOREACH(Buffs, buff_Available(it),
+ FOREACH(StatusEffect, it.instanceOfBuff && buff_Available(it),
{
// if it's already been chosen, give it a lower priority
float myseencount = (it.buff_seencount > 0) ? it.buff_seencount : 1; // no division by zero please!
RandomSelection_AddEnt(it, max(0.2, 1 / myseencount), 1);
});
entity newbuff = RandomSelection_chosen_ent;
+ if(!newbuff)
+ return;
newbuff.buff_seencount += 1; // lower chances of seeing this buff again soon
- STAT(BUFFS, ent) = newbuff.m_itemid;
+ ent.buffdef = newbuff;
+}
+
+void buff_RemoveAll(entity actor, int removal_type)
+{
+ if(!actor.statuseffects)
+ return;
+ FOREACH(StatusEffect, it.instanceOfBuff,
+ {
+ it.m_remove(it, actor, removal_type);
+ });
+}
+
+entity buff_FirstFromFlags(entity actor)
+{
+ if(!actor.statuseffects)
+ return NULL;
+ FOREACH(StatusEffect, it.instanceOfBuff && it.m_active(it, actor), { return it; });
+ return NULL;
}
void buff_Think(entity this)
if(this.buff_waypoint && autocvar_g_buffs_waypoint_distance <= 0)
WaypointSprite_Kill(this.buff_waypoint);
- if(STAT(BUFFS, this) != this.oldbuffs)
+ if(this.buffdef != this.oldbuffs)
{
- entity buff = buff_FirstFromFlags(STAT(BUFFS, this));
+ entity buff = this.buffdef;
this.color = buff.m_color;
this.glowmod = buff_GlowColor(buff);
this.skin = buff.m_skin;
WaypointSprite_UpdateBuildFinished(this.buff_waypoint, time + this.buff_activetime - frametime);
}
- this.oldbuffs = STAT(BUFFS, this);
+ this.oldbuffs = this.buffdef;
}
if(!game_stopped)
}
if(!this.buff_active && !this.buff_activetime)
- if(!this.owner || STAT(FROZEN, this.owner) || IS_DEAD(this.owner) || !this.owner.iscreature || this.owner.vehicle || !(STAT(BUFFS, this.owner) & STAT(BUFFS, this)) || this.pickup_anyway > 0 || (this.pickup_anyway >= 0 && autocvar_g_buffs_pickup_anyway))
+ if(!this.owner || STAT(FROZEN, this.owner) || IS_DEAD(this.owner) || !this.owner.iscreature || this.owner.vehicle
+ || this.pickup_anyway > 0 || (this.pickup_anyway >= 0 && autocvar_g_buffs_pickup_anyway) || this.buffdef != buff_FirstFromFlags(this.owner))
{
buff_SetCooldown(this, autocvar_g_buffs_cooldown_respawn + frametime);
this.owner = NULL;
bool buff_Customize(entity this, entity client)
{
entity player = WaypointSprite_getviewentity(client);
- if(!this.buff_active || (this.team && DIFF_TEAM(player, this)))
+ if((!this.buff_active || !this.buffdef) || (this.team && DIFF_TEAM(player, this)))
{
this.alpha = 0.3;
if(this.effects & EF_FULLBRIGHT) { this.effects &= ~(EF_FULLBRIGHT); }
if(!teamplay && this.team) { this.team = 0; }
- entity buff = buff_FirstFromFlags(STAT(BUFFS, this));
+ entity buff = this.buffdef;
- if(!STAT(BUFFS, this) || !buff_Available(buff))
+ if(!buff || !buff_Available(buff))
buff_NewType(this);
this.classname = "item_buff";
IL_PUSH(g_items, this);
setthink(this, buff_Think);
settouch(this, buff_Touch);
+ setmodel(this, MDL_BUFF);
+ setsize(this, BUFF_MIN, BUFF_MAX);
this.reset = buff_Reset;
this.nextthink = time + 0.1;
this.gravity = 1;
this.pflags = PFLAGS_FULLDYNAMIC;
this.dtor = buff_Delete;
+ if(!this.buffs_finished)
+ this.buffs_finished = this.count; // legacy support
+
if(this.spawnflags & 1)
this.noalign = true;
if(this.noalign)
set_movetype(this, MOVETYPE_NONE); // reset by random location
- setmodel(this, MDL_BUFF);
- setsize(this, BUFF_MIN, BUFF_MAX);
-
if(cvar("g_buffs_random_location") || (this.spawnflags & 64))
buff_Respawn(this);
}
else if (ent.spawnflags & 4)
ent.team = NUM_TEAM_2;
- STAT(BUFFS, ent) = replacement.m_itemid;
+ ent.buffdef = replacement;
buff_Init(ent);
}
if(frag_deathtype == DEATH_BUFF.m_id) { return; }
- if(STAT(BUFFS, frag_target) & BUFF_RESISTANCE.m_itemid)
+ if(StatusEffects_active(BUFF_RESISTANCE, frag_target))
{
float reduced = frag_damage * autocvar_g_buffs_resistance_blockpercent;
frag_damage = bound(0, frag_damage - reduced, frag_damage);
}
- if(STAT(BUFFS, frag_target) & BUFF_SPEED.m_itemid)
+ if(StatusEffects_active(BUFF_SPEED, frag_target))
if(frag_target != frag_attacker)
frag_damage *= autocvar_g_buffs_speed_damage_take;
- if(STAT(BUFFS, frag_target) & BUFF_MEDIC.m_itemid)
+ if(StatusEffects_active(BUFF_MEDIC, frag_target))
if((GetResource(frag_target, RES_HEALTH) - frag_damage) <= 0)
if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
if(frag_attacker)
if(random() <= autocvar_g_buffs_medic_survive_chance)
frag_damage = max(5, GetResource(frag_target, RES_HEALTH) - autocvar_g_buffs_medic_survive_health);
- if(STAT(BUFFS, frag_target) & BUFF_JUMP.m_itemid)
+ if(StatusEffects_active(BUFF_JUMP, frag_target))
if(frag_deathtype == DEATH_FALL.m_id)
frag_damage = 0;
- if(STAT(BUFFS, frag_target) & BUFF_VENGEANCE.m_itemid)
+ if(StatusEffects_active(BUFF_VENGEANCE, frag_target))
if(frag_attacker)
if(frag_attacker != frag_target)
if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
dmgent.nextthink = time + 0.1;
}
- if(STAT(BUFFS, frag_target) & BUFF_BASH.m_itemid)
+ if(StatusEffects_active(BUFF_BASH, frag_target))
if(frag_attacker != frag_target)
frag_force = '0 0 0';
- if(STAT(BUFFS, frag_attacker) & BUFF_BASH.m_itemid)
+ if(StatusEffects_active(BUFF_BASH, frag_attacker))
if(frag_force)
{
if(frag_attacker == frag_target)
frag_force *= autocvar_g_buffs_bash_force;
}
- if(STAT(BUFFS, frag_attacker) & BUFF_DISABILITY.m_itemid)
+ if(StatusEffects_active(BUFF_DISABILITY, frag_attacker))
if(frag_target != frag_attacker)
frag_target.buff_disability_time = time + autocvar_g_buffs_disability_slowtime;
- if(STAT(BUFFS, frag_target) & BUFF_INFERNO.m_itemid)
+ if(StatusEffects_active(BUFF_INFERNO, frag_target))
{
if(frag_deathtype == DEATH_FIRE.m_id)
frag_damage = 0;
frag_damage *= 0.5; // TODO: cvarize?
}
- if(STAT(BUFFS, frag_attacker) & BUFF_LUCK.m_itemid)
+ if(StatusEffects_active(BUFF_LUCK, frag_attacker))
if(frag_attacker != frag_target)
if(autocvar_g_buffs_luck_damagemultiplier > 0)
if(random() <= autocvar_g_buffs_luck_chance)
frag_damage *= autocvar_g_buffs_luck_damagemultiplier;
- if(STAT(BUFFS, frag_attacker) & BUFF_INFERNO.m_itemid)
+ if(StatusEffects_active(BUFF_INFERNO, frag_attacker))
if(frag_target != frag_attacker) {
float btime = buff_Inferno_CalculateTime(
frag_damage,
{
entity frag_attacker = M_ARGV(1, entity);
entity frag_target = M_ARGV(2, entity);
- if(!(STAT(BUFFS, frag_attacker) & BUFF_VAMPIRE.m_itemid))
+ if(!StatusEffects_active(BUFF_VAMPIRE, frag_attacker))
return;
float health_take = bound(0, M_ARGV(4, float), GetResource(frag_target, RES_HEALTH));
entity player = M_ARGV(0, entity);
buffs_BuffModel_Remove(player);
- player.oldbuffs = 0;
+ player.oldbuffs = NULL;
// reset timers here to prevent them continuing after re-spawn
player.buff_disability_time = 0;
player.buff_disability_effect_time = 0;
entity player = M_ARGV(0, entity);
// these automatically reset, no need to worry
- if(STAT(BUFFS, player) & BUFF_SPEED.m_itemid)
+ if(StatusEffects_active(BUFF_SPEED, player))
STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_buffs_speed_speed;
if(time < player.buff_disability_time)
entity player = M_ARGV(0, entity);
// these automatically reset, no need to worry
- if(STAT(BUFFS, player) & BUFF_JUMP.m_itemid)
+ if(StatusEffects_active(BUFF_JUMP, player))
STAT(MOVEVARS_JUMPVELOCITY, player) = autocvar_g_buffs_jump_height;
}
{
entity frag_target = M_ARGV(2, entity);
- if(STAT(BUFFS, frag_target))
+ entity heldbuff = buff_FirstFromFlags(frag_target);
+ if(heldbuff)
{
- int buffid = buff_FirstFromFlags(STAT(BUFFS, frag_target)).m_id;
+ int buffid = heldbuff.m_id;
if(!IS_INDEPENDENT_PLAYER(frag_target))
Send_Notification(NOTIF_ALL_EXCEPT, frag_target, MSG_INFO, INFO_ITEM_BUFF_LOST, frag_target.netname, buffid);
- STAT(BUFFS, frag_target) = 0;
- STAT(BUFF_TIME, frag_target) = 0;
buffs_BuffModel_Remove(frag_target);
}
entity player = M_ARGV(0, entity);
- if(STAT(BUFFS, player))
+ entity heldbuff = buff_FirstFromFlags(player);
+ if(heldbuff)
{
- int buffid = buff_FirstFromFlags(STAT(BUFFS, player)).m_id;
+ int buffid = heldbuff.m_id;
Send_Notification(NOTIF_ONE, player, MSG_MULTI, ITEM_BUFF_DROP, buffid);
if(!IS_INDEPENDENT_PLAYER(player))
Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_INFO, INFO_ITEM_BUFF_LOST, player.netname, buffid);
- STAT(BUFFS, player) = 0;
- STAT(BUFF_TIME, player) = 0;
+ buff_RemoveAll(player, STATUSEFFECT_REMOVE_NORMAL);
PS(player).buff_shield = time + max(0, autocvar_g_buffs_pickup_delay);
sound(player, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
return true;
if(MUTATOR_RETURNVALUE || game_stopped) return;
entity player = M_ARGV(0, entity);
- if(STAT(BUFFS, player) & BUFF_SWAPPER.m_itemid)
+ if(StatusEffects_active(BUFF_SWAPPER, player))
{
float best_distance = autocvar_g_buffs_swapper_range;
entity closest = NULL;
sound(closest, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM);
// TODO: add a counter to handle how many times one can teleport, and a delay to prevent spam
- STAT(BUFFS, player) = 0;
+ buff_RemoveAll(player, STATUSEFFECT_REMOVE_NORMAL);
return true;
}
}
// if you have the invisibility powerup, sprites ALWAYS are restricted to your team
// but only apply this to real players, not to spectators
- if((wp.owner.flags & FL_CLIENT) && (STAT(BUFFS, wp.owner) & BUFF_INVISIBLE.m_itemid) && (e == player))
+ if((wp.owner.flags & FL_CLIENT) && (e == player) && StatusEffects_active(BUFF_INVISIBLE, wp.owner))
if(DIFF_TEAM(wp.owner, e))
return true;
}
{
entity player = M_ARGV(1, entity);
- if(STAT(BUFFS, player) & BUFF_SPEED.m_itemid)
+ if(StatusEffects_active(BUFF_SPEED, player))
M_ARGV(0, float) *= autocvar_g_buffs_speed_rate;
if(time < player.buff_disability_time)
{
entity player = M_ARGV(1, entity);
- if(STAT(BUFFS, player) & BUFF_SPEED.m_itemid)
+ if(StatusEffects_active(BUFF_SPEED, player))
M_ARGV(0, float) *= autocvar_g_buffs_speed_weaponspeed;
if(time < player.buff_disability_time)
{
entity player = M_ARGV(0, entity);
- if(game_stopped || IS_DEAD(player) || frametime || !IS_PLAYER(player)) return;
+ if(game_stopped || IS_DEAD(player) || !IS_PLAYER(player)) return;
- if(STAT(BUFFS, player) & BUFF_FLIGHT.m_itemid)
+ if(StatusEffects_active(BUFF_FLIGHT, player))
{
if(!PHYS_INPUT_BUTTON_CROUCH(player))
player.buff_flight_crouchheld = false;
// 2: notify carrier as well
int buff_lost = 0;
- if(STAT(BUFF_TIME, player) && STAT(BUFFS, player))
- if(time >= STAT(BUFF_TIME, player))
- {
- STAT(BUFF_TIME, player) = 0;
+ entity heldbuff = buff_FirstFromFlags(player);
+ float bufftime = StatusEffects_gettime(heldbuff, player);
+ if(heldbuff && bufftime && time >= bufftime)
buff_lost = 2;
- }
if(STAT(FROZEN, player)) { buff_lost = 1; }
- if(buff_lost)
+ if(buff_lost && heldbuff)
{
- if(STAT(BUFFS, player))
+ int buffid = heldbuff.m_id;
+ if(buff_lost == 2)
{
- int buffid = buff_FirstFromFlags(STAT(BUFFS, player)).m_id;
- if(buff_lost == 2)
- {
- Send_Notification(NOTIF_ONE, player, MSG_MULTI, ITEM_BUFF_DROP, buffid); // TODO: special timeout message?
- sound(player, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
- }
- else if(!IS_INDEPENDENT_PLAYER(player))
- Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_INFO, INFO_ITEM_BUFF_LOST, player.netname, buffid);
- STAT(BUFFS, player) = 0;
- PS(player).buff_shield = time + max(0, autocvar_g_buffs_pickup_delay); // always put in a delay, even if small
+ Send_Notification(NOTIF_ONE, player, MSG_MULTI, ITEM_BUFF_DROP, buffid); // TODO: special timeout message?
+ sound(player, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
}
+ else if(!IS_INDEPENDENT_PLAYER(player))
+ Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_INFO, INFO_ITEM_BUFF_LOST, player.netname, buffid);
+ buff_RemoveAll(player, STATUSEFFECT_REMOVE_TIMEOUT); // TODO: remove only the currently active buff?
+ heldbuff = NULL;
+ PS(player).buff_shield = time + max(0, autocvar_g_buffs_pickup_delay); // always put in a delay, even if small
}
- if(STAT(BUFFS, player) & BUFF_MAGNET.m_itemid)
+ if(StatusEffects_active(BUFF_MAGNET, player))
{
vector pickup_size;
IL_EACH(g_items, it.itemdef,
{
- if(STAT(BUFFS, it))
+ if(it.buffdef)
pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_buff;
else
pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_item;
});
}
- if(STAT(BUFFS, player) & BUFF_AMMO.m_itemid)
+ if(StatusEffects_active(BUFF_AMMO, player))
{
for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
{
}
}
- if((STAT(BUFFS, player) & BUFF_INVISIBLE.m_itemid) && (player.oldbuffs & BUFF_INVISIBLE.m_itemid))
+ if(!player.vehicle && StatusEffects_active(BUFF_INVISIBLE, player) && player.oldbuffs == BUFF_INVISIBLE)
player.alpha = ((autocvar_g_buffs_invisible_alpha) ? autocvar_g_buffs_invisible_alpha : -1); // powerups reset alpha, so we must enforce this (TODO)
-#define BUFF_ONADD(b) if ( (STAT(BUFFS, player) & (b).m_itemid) && !(player.oldbuffs & (b).m_itemid))
-#define BUFF_ONREM(b) if (!(STAT(BUFFS, player) & (b).m_itemid) && (player.oldbuffs & (b).m_itemid))
+#define BUFF_ONADD(b) if ( (heldbuff == (b)) && (player.oldbuffs != (b)))
+#define BUFF_ONREM(b) if ( (heldbuff != (b)) && (player.oldbuffs == (b)))
- if(STAT(BUFFS, player) != player.oldbuffs)
+ if(heldbuff != player.oldbuffs)
{
- entity buff = buff_FirstFromFlags(STAT(BUFFS, player));
- float bufftime = buff != BUFF_Null ? buff.m_time(buff) : 0;
- if(STAT(BUFF_TIME, player) <= time) // if the player still has a buff countdown, don't reset it!
- STAT(BUFF_TIME, player) = (bufftime) ? time + bufftime : 0;
+ bufftime = heldbuff ? heldbuff.m_time(heldbuff) : 0;
+ if(StatusEffects_gettime(heldbuff, player) <= time) // if the player still has a buff countdown, don't reset it!
+ {
+ player.statuseffects.statuseffect_time[heldbuff.m_id] = (bufftime) ? time + bufftime : 0;
+ StatusEffects_update(player);
+ }
BUFF_ONADD(BUFF_AMMO)
{
player.buff_ammo_prev_infitems = (player.items & IT_UNLIMITED_AMMO);
player.items |= IT_UNLIMITED_AMMO;
- if(STAT(BUFFS, player) & BUFF_AMMO.m_itemid)
+ if(StatusEffects_active(BUFF_AMMO, player))
{
for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
{
else
player.items &= ~IT_UNLIMITED_AMMO;
- if(STAT(BUFFS, player) & BUFF_AMMO.m_itemid)
+ if(StatusEffects_active(BUFF_AMMO, player))
{
for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
{
BUFF_ONADD(BUFF_INVISIBLE)
{
- if(time < STAT(STRENGTH_FINISHED, player) && MUTATOR_IS_ENABLED(mutator_instagib))
+ if(StatusEffects_active(STATUSEFFECT_Strength, player) && MUTATOR_IS_ENABLED(mutator_instagib))
player.buff_invisible_prev_alpha = default_player_alpha; // we don't want to save the powerup's alpha, as player may lose the powerup while holding the buff
else
player.buff_invisible_prev_alpha = player.alpha;
- player.alpha = autocvar_g_buffs_invisible_alpha;
+ if(!player.vehicle)
+ player.alpha = autocvar_g_buffs_invisible_alpha;
}
BUFF_ONREM(BUFF_INVISIBLE)
{
- if(time < STAT(STRENGTH_FINISHED, player) && MUTATOR_IS_ENABLED(mutator_instagib))
- player.alpha = autocvar_g_instagib_invis_alpha;
- else
- player.alpha = player.buff_invisible_prev_alpha;
+ if(!player.vehicle)
+ {
+ if(StatusEffects_active(STATUSEFFECT_Strength, player) && MUTATOR_IS_ENABLED(mutator_instagib))
+ player.alpha = autocvar_g_instagib_invis_alpha;
+ else
+ player.alpha = player.buff_invisible_prev_alpha;
+ }
}
BUFF_ONADD(BUFF_FLIGHT)
BUFF_ONREM(BUFF_FLIGHT)
player.gravity = ((player.trigger_gravity_check) ? player.trigger_gravity_check.enemy.gravity : player.buff_flight_oldgravity);
- player.oldbuffs = STAT(BUFFS, player);
- if(STAT(BUFFS, player))
+ player.oldbuffs = heldbuff;
+ if(heldbuff)
{
if(!player.buff_model)
buffs_BuffModel_Spawn(player);
- player.buff_model.color = buff.m_color;
- player.buff_model.glowmod = buff_GlowColor(player.buff_model);
- player.buff_model.skin = buff.m_skin;
+ player.buff_model.color = heldbuff.m_color;
+ player.buff_model.glowmod = buff_GlowColor(heldbuff);
+ player.buff_model.skin = heldbuff.m_skin;
player.effects |= EF_NOSHADOW;
}
#undef BUFF_ONREM
}
-MUTATOR_HOOKFUNCTION(buffs, SpectateCopy)
-{
- entity spectatee = M_ARGV(0, entity);
- entity client = M_ARGV(1, entity);
-
- STAT(BUFFS, client) = STAT(BUFFS, spectatee);
- STAT(BUFF_TIME, client) = STAT(BUFF_TIME, spectatee);
-}
-
MUTATOR_HOOKFUNCTION(buffs, PlayerRegen)
{
entity player = M_ARGV(0, entity);
- if(STAT(BUFFS, player) & BUFF_MEDIC.m_itemid)
+ if(StatusEffects_active(BUFF_MEDIC, player))
{
M_ARGV(2, float) = autocvar_g_buffs_medic_rot; // rot_mod
M_ARGV(4, float) = M_ARGV(1, float) = autocvar_g_buffs_medic_max; // limit_mod = max_mod
M_ARGV(2, float) = autocvar_g_buffs_medic_regen; // regen_mod
}
- if(STAT(BUFFS, player) & BUFF_SPEED.m_itemid)
+ if(StatusEffects_active(BUFF_SPEED, player))
M_ARGV(2, float) = autocvar_g_buffs_speed_regen; // regen_mod
}
.float buff_activetime;
.float buff_activetime_updated;
.entity buff_waypoint;
-.int oldbuffs; // for updating effects
+.entity oldbuffs; // for updating effects
.float buff_shield; // delay for players to keep them from spamming buff pickups
.entity buff_model; // controls effects (TODO: make csqc)
.float cvar_cl_buffs_autoreplace;
float buff_Available(entity buff);
+
+void buff_RemoveAll(entity actor, int removal_type);
+
+entity buff_FirstFromFlags(entity actor);
#include <server/client.qh>
#include <common/items/_mod.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
#include "../random_items/sv_random_items.qh"
bool autocvar_g_instagib_damagedbycontents = true;
{
entity player = M_ARGV(0, entity);
- if (!(player.effects & EF_FULLBRIGHT))
- player.effects |= EF_FULLBRIGHT;
+ player.effects |= EF_FULLBRIGHT;
if (player.items & ITEM_Invisibility.m_itemid)
{
- play_countdown(player, STAT(STRENGTH_FINISHED, player), SND_POWEROFF);
- if (time > STAT(STRENGTH_FINISHED, player))
+ play_countdown(player, StatusEffects_gettime(STATUSEFFECT_Strength, player), SND_POWEROFF);
+ if (time > StatusEffects_gettime(STATUSEFFECT_Strength, player))
{
if(!player.vehicle) // already reset upon exit
{
}
else
{
- if (time < STAT(STRENGTH_FINISHED, player))
+ if (time < StatusEffects_gettime(STATUSEFFECT_Strength, player))
{
if(!player.vehicle) // incase the player is given powerups while inside a vehicle
{
if (player.items & ITEM_Speed.m_itemid)
{
- play_countdown(player, STAT(INVINCIBLE_FINISHED, player), SND_POWEROFF);
- if (time > STAT(INVINCIBLE_FINISHED, player))
+ play_countdown(player, StatusEffects_gettime(STATUSEFFECT_Shield, player), SND_POWEROFF);
+ if (time > StatusEffects_gettime(STATUSEFFECT_Shield, player))
{
player.items &= ~ITEM_Speed.m_itemid;
Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERDOWN_SPEED);
}
else
{
- if (time < STAT(INVINCIBLE_FINISHED, player))
+ if (time < StatusEffects_gettime(STATUSEFFECT_Shield, player))
{
player.items |= ITEM_Speed.m_itemid;
Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_SPEED, player.netname);
if(d < dist)
{
e.fireball_impactvec = p;
- RandomSelection_AddEnt(e, 1 / (1 + d), !Fire_IsBurning(e));
+ RandomSelection_AddEnt(e, 1 / (1 + d), !StatusEffects_active(STATUSEFFECT_Burning, e));
}
}
if(RandomSelection_chosen_ent)
--- /dev/null
+// generated file; do not modify
+#include <common/mutators/mutator/status_effects/all.qc>
+#include <common/mutators/mutator/status_effects/status_effects.qc>
+#ifdef CSQC
+ #include <common/mutators/mutator/status_effects/cl_status_effects.qc>
+#endif
+#ifdef SVQC
+ #include <common/mutators/mutator/status_effects/sv_status_effects.qc>
+#endif
+
+#include <common/mutators/mutator/status_effects/status_effect/_mod.inc>
--- /dev/null
+// generated file; do not modify
+#include <common/mutators/mutator/status_effects/all.qh>
+#include <common/mutators/mutator/status_effects/status_effects.qh>
+#ifdef CSQC
+ #include <common/mutators/mutator/status_effects/cl_status_effects.qh>
+#endif
+#ifdef SVQC
+ #include <common/mutators/mutator/status_effects/sv_status_effects.qh>
+#endif
+
+#include <common/mutators/mutator/status_effects/status_effect/_mod.qh>
--- /dev/null
+#include "all.qh"
--- /dev/null
+#pragma once
+
+#ifdef GAMEQC
+ #include <common/sounds/all.qh>
+#endif
+
+REGISTRY(StatusEffect, 32)
+REGISTER_REGISTRY(StatusEffect)
+#define REGISTER_STATUSEFFECT(id, inst) REGISTER(StatusEffect, STATUSEFFECT, id, m_id, inst)
+
+REGISTRY_SORT(StatusEffect)
+REGISTRY_CHECK(StatusEffect)
+
+REGISTRY_DEFINE_GET(StatusEffect, NULL)
+STATIC_INIT(StatusEffect) { FOREACH(StatusEffect, true, it.m_id = i); }
+
+enum
+{
+ STATUSEFFECT_FLAG_ACTIVE = BIT(0),
+ STATUSEFFECT_FLAG_PERSISTENT = BIT(1) ///< Effect is currently being granted passively.
+};
+
+enum
+{
+ STATUSEFFECT_REMOVE_NORMAL, ///< Effect is being removed by a function, calls regular removal mechanics.
+ STATUSEFFECT_REMOVE_TIMEOUT,
+ STATUSEFFECT_REMOVE_CLEAR ///< Effect is being forcibly removed without calling any additional mechanics.
+};
+
+CLASS(StatusEffects, Object)
+ ATTRIB(StatusEffects, m_id, int, 0);
+ ATTRIB(StatusEffects, m_name, string);
+ ATTRIB(StatusEffects, m_icon, string);
+ ATTRIB(StatusEffects, m_color, vector, '1 1 1');
+ /** Whether the effect is displayed in the HUD */
+ ATTRIB(StatusEffects, m_hidden, bool, false);
+ /** Lifetime scale for HUD progress bars */
+ ATTRIB(StatusEffects, m_lifetime, float, 30);
+#ifdef GAMEQC
+ ATTRIB(StatusEffects, m_sound, Sound, SND_Null);
+ ATTRIB(StatusEffects, m_sound_rm, Sound, SND_Null);
+ METHOD(StatusEffects, m_tick, void(StatusEffects this, entity actor));
+ METHOD(StatusEffects, m_active, bool(StatusEffects this, entity actor));
+ /** Stores times of status effects, the id being the index */
+ ATTRIBARRAY(StatusEffects, statuseffect_time, float, REGISTRY_MAX(StatusEffect));
+ ATTRIBARRAY(StatusEffects, statuseffect_flags, int, REGISTRY_MAX(StatusEffect));
+#endif
+#ifdef SVQC
+ METHOD(StatusEffects, m_apply, void(StatusEffects this, entity actor, float eff_time, int eff_flags));
+ METHOD(StatusEffects, m_remove, void(StatusEffects this, entity actor, int removal_type));
+ /** Sets the persistent flag and updates client side if returning true */
+ METHOD(StatusEffects, m_persistent, bool(StatusEffects this, entity actor)) { return false; };
+#endif
+ METHOD(StatusEffects, display, void(StatusEffects this, void(string name, string icon) returns))
+ {
+ TC(StatusEffects, this);
+ returns(this.m_name, this.m_icon ? sprintf("/gfx/hud/%s/%s", cvar_string("menu_skin"), this.m_icon) : string_null);
+ }
+ENDCLASS(StatusEffects)
--- /dev/null
+#include "cl_status_effects.qh"
+
+METHOD(StatusEffects, m_active, bool(StatusEffects this, entity actor))
+{
+ if(!actor) return false;
+ TC(StatusEffects, this);
+ return (actor.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE);
+}
+
+METHOD(StatusEffects, m_tick, void(StatusEffects this, entity actor))
+{
+ if(this.m_hidden || autocvar__hud_configure)
+ return;
+
+ float currentTime = bound(0, actor.statuseffect_time[this.m_id] - time, 99);
+ addPowerupItem(this.m_name, this.m_icon, this.m_color, currentTime, this.m_lifetime, (actor.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_PERSISTENT));
+}
+
+MUTATOR_HOOKFUNCTION(status_effects, HUD_Powerups_add)
+{
+ if(!g_statuseffects && !autocvar__hud_configure) return;
+
+ // NOTE: the passed entity may be null here if we're in configure mode
+ StatusEffects_tick(g_statuseffects);
+}
--- /dev/null
+#pragma once
--- /dev/null
+// generated file; do not modify
+#include <common/mutators/mutator/status_effects/status_effect/burning.qc>
+#include <common/mutators/mutator/status_effects/status_effect/powerups.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/mutators/mutator/status_effects/status_effect/burning.qh>
+#include <common/mutators/mutator/status_effects/status_effect/powerups.qh>
--- /dev/null
+#include "burning.qh"
+
+#ifdef SVQC
+METHOD(Burning, m_remove, void(StatusEffects this, entity actor, int removal_type))
+{
+ actor.effects &= ~EF_FLAME;
+ SUPER(Burning).m_remove(this, actor, removal_type);
+}
+METHOD(Burning, m_persistent, bool(StatusEffects this, entity actor))
+{
+ return (autocvar_g_balance_contents_playerdamage_lava_burn && actor.waterlevel && actor.watertype == CONTENT_LAVA);
+}
+METHOD(Burning, m_tick, void(StatusEffects this, entity actor))
+{
+ if(STAT(FROZEN, actor) || (actor.waterlevel && actor.watertype != CONTENT_LAVA))
+ {
+ this.m_remove(this, actor, STATUSEFFECT_REMOVE_NORMAL);
+ return;
+ }
+ Fire_ApplyDamage(actor);
+ actor.effects |= EF_FLAME;
+ SUPER(Burning).m_tick(this, actor);
+}
+#endif
--- /dev/null
+#pragma once
+
+#include <common/mutators/mutator/status_effects/all.qh>
+
+#ifdef SVQC
+void Fire_ApplyDamage(entity e);
+#endif
+#ifdef GAMEQC
+SOUND(Burning_Remove, "desertfactory/steam_burst");
+#endif
+CLASS(Burning, StatusEffects)
+ ATTRIB(Burning, netname, string, "burning");
+ ATTRIB(Burning, m_name, string, _("Burning"));
+ ATTRIB(Burning, m_icon, string, "buff_inferno");
+ ATTRIB(Burning, m_color, vector, '1 0.62 0');
+ ATTRIB(Burning, m_hidden, bool, true);
+ ATTRIB(Burning, m_lifetime, float, 10);
+#ifdef GAMEQC
+ ATTRIB(Burning, m_sound_rm, Sound, SND_Burning_Remove);
+#endif
+ENDCLASS(Burning)
+REGISTER_STATUSEFFECT(Burning, NEW(Burning));
--- /dev/null
+#include "powerups.qh"
+
+#ifdef CSQC
+METHOD(Strength, m_active, bool(StatusEffects this, entity actor))
+{
+ if(autocvar__hud_configure)
+ return true;
+ return SUPER(Strength).m_active(this, actor);
+}
+METHOD(Strength, m_tick, void(StatusEffects this, entity actor))
+{
+ if(this.m_hidden)
+ return;
+
+ float currentTime = (autocvar__hud_configure) ? 15 : bound(0, actor.statuseffect_time[this.m_id] - time, 99);
+ addPowerupItem(this.m_name, this.m_icon, autocvar_hud_progressbar_strength_color, currentTime, this.m_lifetime, (actor.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_PERSISTENT));
+}
+
+METHOD(Superweapons, m_active, bool(StatusEffects this, entity actor))
+{
+ if(autocvar__hud_configure)
+ return true;
+ return SUPER(Superweapons).m_active(this, actor);
+}
+METHOD(Superweapons, m_tick, void(StatusEffects this, entity actor))
+{
+ if(this.m_hidden)
+ return;
+
+ int allItems = STAT(ITEMS);
+
+ // Prevent stuff to show up on mismatch that will be fixed next frame
+ if(!(allItems & IT_SUPERWEAPON) && !autocvar__hud_configure)
+ return;
+
+ if(allItems & IT_UNLIMITED_SUPERWEAPONS)
+ return;
+
+ float currentTime = (autocvar__hud_configure) ? 13 : bound(0, actor.statuseffect_time[this.m_id] - time, 99);
+ addPowerupItem(this.m_name, this.m_icon, autocvar_hud_progressbar_superweapons_color, currentTime, this.m_lifetime, (actor.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_PERSISTENT));
+}
+
+METHOD(Shield, m_active, bool(StatusEffects this, entity actor))
+{
+ if(autocvar__hud_configure)
+ return true;
+ return SUPER(Shield).m_active(this, actor);
+}
+METHOD(Shield, m_tick, void(StatusEffects this, entity actor))
+{
+ if(this.m_hidden)
+ return;
+
+ float currentTime = (autocvar__hud_configure) ? 27 : bound(0, actor.statuseffect_time[this.m_id] - time, 99);
+ addPowerupItem(this.m_name, this.m_icon, autocvar_hud_progressbar_shield_color, currentTime, this.m_lifetime, (actor.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_PERSISTENT));
+}
+#endif
--- /dev/null
+#pragma once
+
+#include <common/mutators/mutator/status_effects/all.qh>
+
+CLASS(Powerups, StatusEffects)
+#ifdef GAMEQC
+ ATTRIB(Powerups, m_sound_rm, Sound, SND_POWEROFF);
+#endif
+ENDCLASS(Powerups)
+
+CLASS(Strength, Powerups)
+ ATTRIB(Strength, netname, string, "strength");
+ ATTRIB(Strength, m_name, string, _("Strength"));
+ ATTRIB(Strength, m_icon, string, "strength");
+ENDCLASS(Strength)
+REGISTER_STATUSEFFECT(Strength, NEW(Strength));
+
+CLASS(Shield, Powerups)
+ ATTRIB(Shield, netname, string, "shield");
+ ATTRIB(Shield, m_name, string, _("Shield"));
+ ATTRIB(Shield, m_icon, string, "shield");
+ENDCLASS(Shield)
+REGISTER_STATUSEFFECT(Shield, NEW(Shield));
+
+CLASS(Superweapons, StatusEffects)
+ ATTRIB(Superweapons, netname, string, "superweapons");
+ ATTRIB(Superweapons, m_name, string, _("Superweapons"));
+ ATTRIB(Superweapons, m_icon, string, "superweapons");
+#ifdef GAMEQC
+ ATTRIB(Superweapons, m_sound_rm, Sound, SND_POWEROFF);
+#endif
+ENDCLASS(Superweapons)
+REGISTER_STATUSEFFECT(Superweapons, NEW(Superweapons));
--- /dev/null
+#include "status_effects.qh"
+
+#ifdef GAMEQC
+bool StatusEffects_active(StatusEffects this, entity actor)
+{
+ return this.m_active(this, actor);
+}
+
+void StatusEffects_tick(entity actor)
+{
+ FOREACH(StatusEffect, it.m_active(it, actor),
+ {
+ it.m_tick(it, actor);
+ });
+}
+
+float StatusEffects_gettime(StatusEffects this, entity actor)
+{
+ entity store = actor;
+#ifdef SVQC
+ store = actor.statuseffects;
+#endif
+ if(!store) return 0;
+ return store.statuseffect_time[this.m_id];
+}
+#endif
+#ifdef SVQC
+void StatusEffects_apply(StatusEffects this, entity actor, float eff_time, int eff_flags)
+{
+ this.m_apply(this, actor, eff_time, eff_flags);
+}
+
+void StatusEffects_copy(StatusEffects this, entity store, float time_offset)
+{
+ if(!this || !store)
+ return;
+ FOREACH(StatusEffect, true,
+ {
+ if(time_offset)
+ store.statuseffect_time[it.m_id] = time + this.statuseffect_time[it.m_id] - time_offset;
+ else
+ store.statuseffect_time[it.m_id] = this.statuseffect_time[it.m_id];
+ store.statuseffect_flags[it.m_id] = this.statuseffect_flags[it.m_id];
+ });
+}
+
+void StatusEffects_remove(StatusEffects this, entity actor, int removal_type)
+{
+ this.m_remove(this, actor, removal_type);
+}
+
+void StatusEffects_removeall(entity actor, int removal_type)
+{
+ if(!actor.statuseffects)
+ return;
+ FOREACH(StatusEffect, true,
+ {
+ it.m_remove(it, actor, removal_type);
+ });
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef GAMEQC
+#include <common/mutators/base.qh>
+
+REGISTER_MUTATOR(status_effects, true);
+#endif
+
+#include "all.qh"
+
+#ifdef GAMEQC
+/** Entity statuseffects */
+.StatusEffects statuseffects;
+/** Player statuseffects storage (holds previous state) */
+.StatusEffects statuseffects_store;
+
+REGISTER_NET_LINKED(ENT_CLIENT_STATUSEFFECTS)
+
+const int StatusEffects_groups_minor = 8; // must be a multiple of 8 (one byte) to optimize bandwidth usage
+const int StatusEffects_groups_major = 4; // must be >= ceil(REGISTRY_COUNT(StatusEffect) / StatusEffects_groups_minor)
+#endif
+
+// no need to perform these checks on both server and client
+#ifdef CSQC
+STATIC_INIT(StatusEffects)
+{
+ if (StatusEffects_groups_minor / 8 != floor(StatusEffects_groups_minor / 8))
+ error("StatusEffects_groups_minor is not a multiple of 8.");
+ int min_major_value = ceil(REGISTRY_COUNT(StatusEffect) / StatusEffects_groups_minor);
+ if (StatusEffects_groups_major < min_major_value)
+ error(sprintf("StatusEffects_groups_major can not be < %d.", min_major_value));
+}
+#endif
+
+#ifdef SVQC
+#define G_MAJOR(id) (floor((id) / StatusEffects_groups_minor))
+#define G_MINOR(id) ((id) % StatusEffects_groups_minor)
+#endif
+
+#ifdef CSQC
+StatusEffects g_statuseffects;
+void StatusEffects_entremove(entity this)
+{
+ if(g_statuseffects == this)
+ g_statuseffects = NULL;
+}
+
+NET_HANDLE(ENT_CLIENT_STATUSEFFECTS, bool isnew)
+{
+ make_pure(this);
+ g_statuseffects = this;
+ this.entremove = StatusEffects_entremove;
+ const int majorBits = Readbits(StatusEffects_groups_major);
+ for (int i = 0; i < StatusEffects_groups_major; ++i) {
+ if (!(majorBits & BIT(i))) {
+ continue;
+ }
+ const int minorBits = Readbits(StatusEffects_groups_minor);
+ for (int j = 0; j < StatusEffects_groups_minor; ++j) {
+ if (!(minorBits & BIT(j))) {
+ continue;
+ }
+ const StatusEffects it = REGISTRY_GET(StatusEffect, StatusEffects_groups_minor * i + j);
+ this.statuseffect_time[it.m_id] = ReadFloat();
+ this.statuseffect_flags[it.m_id] = ReadByte();
+ }
+ }
+ return true;
+}
+#endif
+
+#ifdef SVQC
+int SEFminorBitsArr[StatusEffects_groups_major];
+void StatusEffects_Write(StatusEffects data, StatusEffects store)
+{
+ if (!data) {
+ WriteShort(MSG_ENTITY, 0);
+ return;
+ }
+ TC(StatusEffects, data);
+
+ for (int i = 0; i < StatusEffects_groups_major; ++i)
+ SEFminorBitsArr[i] = 0;
+
+ int majorBits = 0;
+ FOREACH(StatusEffect, true, {
+ .float fld = statuseffect_time[it.m_id];
+ .int flg = statuseffect_flags[it.m_id];
+ const bool changed = (store.(fld) != data.(fld) || store.(flg) != data.(flg));
+ store.(fld) = data.(fld);
+ store.(flg) = data.(flg);
+ if (changed) {
+ int maj = G_MAJOR(it.m_id);
+ majorBits = BITSET(majorBits, BIT(maj), true);
+ SEFminorBitsArr[maj] = BITSET(SEFminorBitsArr[maj], BIT(G_MINOR(it.m_id)), true);
+ }
+ });
+
+ Writebits(MSG_ENTITY, majorBits, StatusEffects_groups_major);
+ for (int i = 0; i < StatusEffects_groups_major; ++i)
+ {
+ if (!(majorBits & BIT(i)))
+ continue;
+
+ const int minorBits = SEFminorBitsArr[i];
+ Writebits(MSG_ENTITY, minorBits, StatusEffects_groups_minor);
+ for (int j = 0; j < StatusEffects_groups_minor; ++j)
+ {
+ if (!(minorBits & BIT(j)))
+ continue;
+
+ const entity it = REGISTRY_GET(StatusEffect, StatusEffects_groups_minor * i + j);
+ WriteFloat(MSG_ENTITY, data.statuseffect_time[it.m_id]);
+ WriteByte(MSG_ENTITY, data.statuseffect_flags[it.m_id]);
+ }
+ }
+}
+#endif
+
+#undef G_MAJOR
+#undef G_MINOR
+
+#ifdef SVQC
+bool StatusEffects_Send(StatusEffects this, Client to, int sf)
+{
+ TC(StatusEffects, this);
+ WriteHeader(MSG_ENTITY, ENT_CLIENT_STATUSEFFECTS);
+ StatusEffects_Write(this, to.statuseffects_store);
+ return true;
+}
+
+bool StatusEffects_customize(entity this, entity client)
+{
+ // sends to spectators too!
+ return (client.statuseffects == this);
+}
+
+void StatusEffects_new(entity this)
+{
+ StatusEffects eff = NEW(StatusEffects);
+ this.statuseffects = eff;
+ eff.owner = this;
+ if(this.statuseffects_store)
+ {
+ setcefc(eff, StatusEffects_customize);
+ Net_LinkEntity(eff, false, 0, StatusEffects_Send);
+ }
+}
+void StatusEffects_delete(entity e) { delete(e.statuseffects); e.statuseffects = NULL; }
+// may be called on non-player entities, should be harmless!
+void StatusEffects_update(entity e) { e.statuseffects.SendFlags = 0xFFFFFF; }
+
+// this clears the storage entity instead of the statuseffects object, useful for map resets and such
+void StatusEffects_clearall(entity store)
+{
+ if(!store)
+ return; // safety net
+ // NOTE: you will need to perform StatusEffects_update after this to update the storage entity
+ // (unless store is the storage entity)
+ FOREACH(StatusEffect, true, {
+ store.statuseffect_time[it.m_id] = 0;
+ store.statuseffect_flags[it.m_id] = 0;
+ });
+}
+
+void StatusEffectsStorage_attach(entity e) { e.statuseffects_store = NEW(StatusEffects); e.statuseffects_store.drawonlytoclient = e; }
+void StatusEffectsStorage_delete(entity e) { delete(e.statuseffects_store); e.statuseffects_store = NULL; }
+
+// called when an entity is deleted with delete() / remove()
+// or when a player disconnects
+void ONREMOVE(entity this)
+{
+ // remove statuseffects object attached to 'this'
+ if(this.statuseffects && this.statuseffects.owner == this)
+ StatusEffects_delete(this);
+}
+#endif
+
+#ifdef GAMEQC
+bool StatusEffects_active(StatusEffects this, entity actor);
+
+// runs every SV_StartFrame on the server
+// called by HUD_Powerups_add on the client
+void StatusEffects_tick(entity actor);
+
+// accesses the status effect timer, returns 0 if the entity has no statuseffects object
+// pass g_statuseffects as the actor on client side
+// pass the entity with a .statuseffects on server side
+float StatusEffects_gettime(StatusEffects this, entity actor);
+#endif
+#ifdef SVQC
+// call when applying the effect to an entity
+void StatusEffects_apply(StatusEffects this, entity actor, float eff_time, int eff_flags);
+
+// copies all the status effect fields to the specified storage entity
+// does not perform an update
+void StatusEffects_copy(StatusEffects this, entity store, float time_offset);
+
+// call when removing the effect
+void StatusEffects_remove(StatusEffects this, entity actor, int removal_type);
+
+void StatusEffects_removeall(entity actor, int removal_type);
+#endif
--- /dev/null
+#include "sv_status_effects.qh"
+
+METHOD(StatusEffects, m_active, bool(StatusEffects this, entity actor))
+{
+ TC(StatusEffects, this);
+ if(!actor.statuseffects)
+ return false; // safety net
+ return (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE);
+}
+
+METHOD(StatusEffects, m_tick, void(StatusEffects this, entity actor))
+{
+ StatusEffects data = actor.statuseffects;
+ .int flg = statuseffect_flags[this.m_id];
+ int oldflag = data.(flg);
+ data.(flg) = BITSET(data.(flg), STATUSEFFECT_FLAG_PERSISTENT, this.m_persistent(this, actor));
+ if(oldflag != data.(flg))
+ StatusEffects_update(actor);
+
+ if(data.(flg) & STATUSEFFECT_FLAG_PERSISTENT)
+ return;
+ if(time > actor.statuseffects.statuseffect_time[this.m_id])
+ {
+ this.m_remove(this, actor, STATUSEFFECT_REMOVE_TIMEOUT);
+ return;
+ }
+}
+
+METHOD(StatusEffects, m_apply, void(StatusEffects this, entity actor, float eff_time, float eff_flags))
+{
+ if(!actor.statuseffects)
+ StatusEffects_new(actor);
+
+ eff_flags |= STATUSEFFECT_FLAG_ACTIVE; // automatically enable active flag if applied (TODO?)
+ actor.statuseffects.statuseffect_time[this.m_id] = eff_time; // TODO: add onto the existing time rather than replacing it?
+ actor.statuseffects.statuseffect_flags[this.m_id] = eff_flags;
+ StatusEffects_update(actor);
+}
+
+METHOD(StatusEffects, m_remove, void(StatusEffects this, entity actor, int removal_type))
+{
+ if(!actor.statuseffects)
+ return;
+ if(removal_type == STATUSEFFECT_REMOVE_NORMAL && this.m_active(this, actor))
+ sound(actor, CH_TRIGGER, this.m_sound_rm, VOL_BASE, ATTEN_NORM);
+ actor.statuseffects.statuseffect_time[this.m_id] = 0;
+ actor.statuseffects.statuseffect_flags[this.m_id] = 0;
+ StatusEffects_update(actor);
+}
+
+MUTATOR_HOOKFUNCTION(status_effects, SV_StartFrame)
+{
+ // TODO: explicitly only loop through entities with a valid statuseffects object
+ IL_EACH(g_damagedbycontents, it.damagedbycontents,
+ {
+ if (it.move_movetype == MOVETYPE_NOCLIP || !it.statuseffects) continue;
+ StatusEffects_tick(it);
+ });
+}
+
+MUTATOR_HOOKFUNCTION(status_effects, PlayerDies)
+{
+ entity frag_target = M_ARGV(2, entity);
+
+ StatusEffects_removeall(frag_target, STATUSEFFECT_REMOVE_NORMAL);
+}
+
+MUTATOR_HOOKFUNCTION(status_effects, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ // no need to network updates, as there is no statuseffects object attached
+ StatusEffects_clearall(player.statuseffects_store);
+
+ // don't delete spectatee's effects!
+ if(player.statuseffects && player.statuseffects.owner == player)
+ StatusEffects_delete(player);
+}
+
+MUTATOR_HOOKFUNCTION(status_effects, reset_map_global)
+{
+ FOREACH_CLIENT(IS_PLAYER(it) && it.statuseffects,
+ {
+ StatusEffects_clearall(it.statuseffects);
+ StatusEffects_update(it);
+ });
+ return false;
+}
+
+MUTATOR_HOOKFUNCTION(status_effects, SpectateCopy)
+{
+ entity spectatee = M_ARGV(0, entity);
+ entity client = M_ARGV(1, entity);
+
+ client.statuseffects = spectatee.statuseffects;
+}
+
+MUTATOR_HOOKFUNCTION(status_effects, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.statuseffects && player.statuseffects.owner == player)
+ {
+ StatusEffects_clearall(player.statuseffects);
+ StatusEffects_update(player);
+ }
+ else
+ {
+ StatusEffects_clearall(player.statuseffects_store);
+ player.statuseffects = NULL;
+ }
+
+ // TODO: special hook for when effects are initialized?
+ if(STAT(WEAPONS, player) & WEPSET_SUPERWEAPONS)
+ StatusEffects_apply(STATUSEFFECT_Superweapons, player, time + autocvar_g_balance_superweapons_time, 0);
+}
--- /dev/null
+#pragma once
if(cmd_name == "followpowerup")
{
- FOREACH_CLIENT(IS_PLAYER(it) && (STAT(STRENGTH_FINISHED, it) > time || STAT(INVINCIBLE_FINISHED, it) > time), { return superspec_Spectate(player, it); });
+ FOREACH_CLIENT(IS_PLAYER(it) && (StatusEffects_active(STATUSEFFECT_Strength, it) || StatusEffects_active(STATUSEFFECT_Shield, it)), { return superspec_Spectate(player, it); });
superspec_msg("", "", player, "No active powerup\n", 1);
return true;
if(cmd_name == "followstrength")
{
- FOREACH_CLIENT(IS_PLAYER(it) && STAT(STRENGTH_FINISHED, it) > time, { return superspec_Spectate(player, it); });
+ FOREACH_CLIENT(IS_PLAYER(it) && StatusEffects_active(STATUSEFFECT_Strength, it), { return superspec_Spectate(player, it); });
superspec_msg("", "", player, "No active Strength\n", 1);
return true;
if(cmd_name == "followshield")
{
- FOREACH_CLIENT(IS_PLAYER(it) && STAT(INVINCIBLE_FINISHED, it) > time, { return superspec_Spectate(player, it); });
+ FOREACH_CLIENT(IS_PLAYER(it) && StatusEffects_active(STATUSEFFECT_Shield, it), { return superspec_Spectate(player, it); });
superspec_msg("", "", player, "No active Shield\n", 1);
return true;
#define PHYS_VIEWHEIGHT(s) STAT(VIEWHEIGHT, s)
#define PHYS_HEALTH(s) STAT(HEALTH, s)
-#define BUFFS_STAT(s) STAT(BUFFS, s)
-
#define PHYS_ACCELERATE(s) STAT(MOVEVARS_ACCELERATE, s)
#define PHYS_AIRACCELERATE(s) STAT(MOVEVARS_AIRACCELERATE, s)
#define PHYS_AIRACCEL_QW(s) STAT(MOVEVARS_AIRACCEL_QW, s)
#include "state.qh"
+#include <common/mutators/mutator/status_effects/_mod.qh>
#include <server/command/getreplies.qh>
void Inventory_new(PlayerState this);
anticheat_init(this);
W_HitPlotOpen(this);
InventoryStorage_attach(this);
+ StatusEffectsStorage_attach(this);
}
void bot_clientdisconnect(entity this);
ClientData_Detach(this);
entcs_detach(this);
InventoryStorage_delete(this);
+ StatusEffectsStorage_delete(this);
delete(CS(this));
this._cs = NULL;
REGISTER_STAT(WEAPONRATEFACTOR, float, W_WeaponRateFactor(this))
REGISTER_STAT(GAME_STOPPED, int, game_stopped)
REGISTER_STAT(GAMESTARTTIME, float, game_starttime)
-REGISTER_STAT(STRENGTH_FINISHED, float)
-REGISTER_STAT(INVINCIBLE_FINISHED, float)
/** arc heat in [0,1] */
REGISTER_STAT(PRESSED_KEYS, int)
/** this stat could later contain some other bits of info, like, more server-side particle config */
REGISTER_STAT(HIT_TIME, float)
REGISTER_STAT(DAMAGE_DEALT_TOTAL, int)
REGISTER_STAT(TYPEHIT_TIME, float)
-REGISTER_STAT(SUPERWEAPONS_FINISHED, float)
REGISTER_STAT(AIR_FINISHED, float)
REGISTER_STAT(VEHICLESTAT_HEALTH, int)
REGISTER_STAT(VEHICLESTAT_SHIELD, int)
REGISTER_STAT(ROUNDSTARTTIME, float, round_starttime)
REGISTER_STAT(MONSTERS_TOTAL, int)
REGISTER_STAT(MONSTERS_KILLED, int)
-REGISTER_STAT(BUFFS, int)
REGISTER_STAT(NADE_BONUS, float)
REGISTER_STAT(NADE_BONUS_TYPE, int)
REGISTER_STAT(NADE_BONUS_SCORE, float)
REGISTER_STAT(FROZEN, int)
REGISTER_STAT(REVIVE_PROGRESS, float)
REGISTER_STAT(ROUNDLOST, int)
-REGISTER_STAT(BUFF_TIME, float)
REGISTER_STAT(CTF_FLAGSTATUS, int)
REGISTER_STAT(CAPTURE_PROGRESS, float)
REGISTER_STAT(ENTRAP_ORB, float)
});
float desirabledamage;
desirabledamage = enemydamage;
- if(time > STAT(INVINCIBLE_FINISHED, actor) && time > actor.spawnshieldtime)
+ if(StatusEffects_active(STATUSEFFECT_Shield, actor) && time > actor.spawnshieldtime)
desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
if(teamplay && actor.team)
desirabledamage = desirabledamage - teamdamage;
if(d < dist)
{
e.fireball_impactvec = p;
- RandomSelection_AddEnt(e, 1 / (1 + d), !Fire_IsBurning(e));
+ RandomSelection_AddEnt(e, 1 / (1 + d), !StatusEffects_active(STATUSEFFECT_Burning, e));
}
}
if(RandomSelection_chosen_ent)
float desirabledamage;
desirabledamage = enemydamage;
- if(time > STAT(INVINCIBLE_FINISHED, actor) && time > actor.spawnshieldtime)
+ if(StatusEffects_active(STATUSEFFECT_Shield, actor) && time > actor.spawnshieldtime)
desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
if(teamplay && actor.team)
desirabledamage = desirabledamage - teamdamage;
#include <common/mapobjects/teleporters.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/physics/player.qh>
#include <common/state.qh>
if (skill > 6 && !(IS_ONGROUND(this)))
{
#define ROCKETJUMP_DAMAGE() WEP_CVAR(devastator, damage) * 0.8 \
- * ((STAT(STRENGTH_FINISHED, this) > time) ? autocvar_g_balance_powerup_strength_selfdamage : 1) \
- * ((STAT(INVINCIBLE_FINISHED, this) > time) ? autocvar_g_balance_powerup_invincible_takedamage : 1)
+ * ((StatusEffects_active(STATUSEFFECT_Strength, this)) ? autocvar_g_balance_powerup_strength_selfdamage : 1) \
+ * ((StatusEffects_active(STATUSEFFECT_Shield, this)) ? autocvar_g_balance_powerup_invincible_takedamage : 1)
// save some CPU cycles by checking trigger_hurt after checking
// that something can be done to evade it (cheaper checks)
#include "roles.qh"
#include <common/stats.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/weapons/_all.qh>
#include <server/bot/default/bot.qh>
#include <server/bot/default/cvars.qh>
t = bound(0, 1 + t, 3);
if (skill > 3)
{
- if (time < STAT(STRENGTH_FINISHED, this) - 1) t += 0.5;
- if (time < STAT(STRENGTH_FINISHED, it) - 1) t -= 0.5;
- if (time < STAT(INVINCIBLE_FINISHED, this) - 1) t += 0.2;
- if (time < STAT(INVINCIBLE_FINISHED, it) - 1) t -= 0.4;
+ if (time < StatusEffects_gettime(STATUSEFFECT_Strength, this) - 1) t += 0.5;
+ if (time < StatusEffects_gettime(STATUSEFFECT_Strength, it) - 1) t -= 0.5;
+ if (time < StatusEffects_gettime(STATUSEFFECT_Shield, this) - 1) t += 0.2;
+ if (time < StatusEffects_gettime(STATUSEFFECT_Shield, it) - 1) t -= 0.4;
}
t += max(0, 8 - skill) * 0.05; // less skilled bots attack more mindlessly
ratingscale *= t;
#include <common/mapobjects/teleporters.qh>
#include <common/mapobjects/triggers.qh>
#include <common/monsters/_mod.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/physics/player.qh>
#include <common/stats.qh>
#include <common/util.qh>
SetResource(this.personal, RES_HEALTH, max(1, GetResource(this, RES_HEALTH)));
SetResource(this.personal, RES_ARMOR, GetResource(this, RES_ARMOR));
STAT(WEAPONS, this.personal) = STAT(WEAPONS, this);
- STAT(BUFFS, this.personal) = STAT(BUFFS, this);
- STAT(BUFF_TIME, this.personal) = STAT(BUFF_TIME, this);
+ StatusEffects_copy(this.statuseffects, this.personal, 0);
this.personal.items = this.items;
this.personal.pauserotarmor_finished = this.pauserotarmor_finished;
this.personal.pauserothealth_finished = this.pauserothealth_finished;
this.personal.pauserotfuel_finished = this.pauserotfuel_finished;
this.personal.pauseregen_finished = this.pauseregen_finished;
- STAT(STRENGTH_FINISHED, this.personal) = STAT(STRENGTH_FINISHED, this);
- STAT(INVINCIBLE_FINISHED, this.personal) = STAT(INVINCIBLE_FINISHED, this);
this.personal.teleport_time = time;
break; // this part itself doesn't cheat, so let's not count this
case CHIMPULSE_CLONE_MOVING.impulse:
SetResource(this, RES_HEALTH, GetResource(this.personal, RES_HEALTH));
SetResource(this, RES_ARMOR, GetResource(this.personal, RES_ARMOR));
STAT(WEAPONS, this) = STAT(WEAPONS, this.personal);
- STAT(BUFFS, this) = STAT(BUFFS, this.personal);
- STAT(BUFF_TIME, this) = STAT(BUFF_TIME, this.personal);
this.items = this.personal.items;
this.pauserotarmor_finished = time + this.personal.pauserotarmor_finished - this.personal.teleport_time;
this.pauserothealth_finished = time + this.personal.pauserothealth_finished - this.personal.teleport_time;
this.pauserotfuel_finished = time + this.personal.pauserotfuel_finished - this.personal.teleport_time;
this.pauseregen_finished = time + this.personal.pauseregen_finished - this.personal.teleport_time;
- STAT(STRENGTH_FINISHED, this) = time + STAT(STRENGTH_FINISHED, this.personal) - this.personal.teleport_time;
- STAT(INVINCIBLE_FINISHED, this) = time + STAT(INVINCIBLE_FINISHED, this.personal) - this.personal.teleport_time;
+ StatusEffects_copy(this.personal, this.statuseffects, this.personal.teleport_time);
+ StatusEffects_update(this);
if(!autocvar_g_allow_checkpoints)
DID_CHEAT();
#include <common/mutators/mutator/instagib/sv_instagib.qh>
#include <common/mutators/mutator/nades/nades.qh>
#include <common/mutators/mutator/overkill/oknex.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/mutators/mutator/waypoints/all.qh>
#include <common/net_linked.qh>
#include <common/net_notice.qh>
this.scale = 0;
this.fade_time = 0;
this.pain_finished = 0;
- STAT(STRENGTH_FINISHED, this) = 0;
- STAT(INVINCIBLE_FINISHED, this) = 0;
- STAT(SUPERWEAPONS_FINISHED, this) = 0;
STAT(AIR_FINISHED, this) = 0;
//this.dphitcontentsmask = 0;
this.dphitcontentsmask = DPCONTENTS_SOLID;
this.punchangle = '0 0 0';
this.punchvector = '0 0 0';
this.oldvelocity = this.velocity;
- this.fire_endtime = -1;
this.event_damage = func_null;
this.event_heal = func_null;
PS(this).dual_weapons = '0 0 0';
- STAT(SUPERWEAPONS_FINISHED, this) = (STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS) ? time + autocvar_g_balance_superweapons_time : 0;
-
this.items = start_items;
this.spawnshieldtime = time + autocvar_g_spawnshieldtime;
this.punchangle = '0 0 0';
this.punchvector = '0 0 0';
- STAT(STRENGTH_FINISHED, this) = 0;
- STAT(INVINCIBLE_FINISHED, this) = 0;
- this.fire_endtime = -1;
STAT(REVIVE_PROGRESS, this) = 0;
this.revival_time = 0;
- // TODO: we can't set these in the PlayerSpawn hook since the target code is called before it!
- STAT(BUFFS, this) = 0;
- STAT(BUFF_TIME, this) = 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;
if(g_assault || g_race) // TODO: make targeting work in assault & race without this hack
spot.target = s;
}
- Unfreeze(this, false);
-
- MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
-
if (autocvar_spawn_debug)
{
sprint(this, strcat("spawnpoint origin: ", vtos(spot.origin), "\n"));
else
this.modelflags &= ~MF_ROCKET;
- this.effects &= ~(EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_FLAME | EF_NODEPTHTEST);
+ this.effects &= ~(EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_NODEPTHTEST);
if (IS_DEAD(this))
player_powerups_remove_all(this);
// add a way to see what the items were BEFORE all of these checks for the mutator hook
int items_prev = this.items;
- Fire_ApplyDamage(this);
- Fire_ApplyEffect(this);
-
if (!MUTATOR_IS_ENABLED(mutator_instagib))
{
if (this.items & ITEM_Strength.m_itemid)
{
- play_countdown(this, STAT(STRENGTH_FINISHED, this), SND_POWEROFF);
+ play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Strength, this), SND_POWEROFF);
this.effects = this.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT);
- if (time > STAT(STRENGTH_FINISHED, this))
+ if (time > StatusEffects_gettime(STATUSEFFECT_Strength, this))
{
this.items = this.items - (this.items & ITEM_Strength.m_itemid);
//Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_STRENGTH, this.netname);
}
else
{
- if (time < STAT(STRENGTH_FINISHED, this))
+ if (time < StatusEffects_gettime(STATUSEFFECT_Strength, this))
{
this.items = this.items | ITEM_Strength.m_itemid;
if(!g_cts)
}
if (this.items & ITEM_Shield.m_itemid)
{
- play_countdown(this, STAT(INVINCIBLE_FINISHED, this), SND_POWEROFF);
+ play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Shield, this), SND_POWEROFF);
this.effects = this.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT);
- if (time > STAT(INVINCIBLE_FINISHED, this))
+ if (time > StatusEffects_gettime(STATUSEFFECT_Shield, this))
{
this.items = this.items - (this.items & ITEM_Shield.m_itemid);
//Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_SHIELD, this.netname);
}
else
{
- if (time < STAT(INVINCIBLE_FINISHED, this))
+ if (time < StatusEffects_gettime(STATUSEFFECT_Shield, this))
{
this.items = this.items | ITEM_Shield.m_itemid;
if(!g_cts)
{
if (!(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS))
{
- STAT(SUPERWEAPONS_FINISHED, this) = 0;
+ StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_NORMAL);
this.items = this.items - (this.items & IT_SUPERWEAPON);
//Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_LOST, this.netname);
Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_LOST);
}
else
{
- play_countdown(this, STAT(SUPERWEAPONS_FINISHED, this), SND_POWEROFF);
- if (time > STAT(SUPERWEAPONS_FINISHED, this))
+ play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Superweapons, this), SND_POWEROFF);
+ if (time > StatusEffects_gettime(STATUSEFFECT_Superweapons, this))
{
this.items = this.items - (this.items & IT_SUPERWEAPON);
STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
}
else if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)
{
- if (time < STAT(SUPERWEAPONS_FINISHED, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS))
+ if (time < StatusEffects_gettime(STATUSEFFECT_Superweapons, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS))
{
this.items = this.items | IT_SUPERWEAPON;
if(!(this.items & IT_UNLIMITED_SUPERWEAPONS))
}
else
{
- STAT(SUPERWEAPONS_FINISHED, this) = 0;
+ if(StatusEffects_active(STATUSEFFECT_Superweapons, this))
+ StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_TIMEOUT);
STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
}
}
- else
+ else if(StatusEffects_active(STATUSEFFECT_Superweapons, this)) // cheaper to check than to update each frame!
{
- STAT(SUPERWEAPONS_FINISHED, this) = 0;
+ StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_CLEAR);
}
}
this.items = spectatee.items;
STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee);
STAT(HIT_TIME, this) = STAT(HIT_TIME, spectatee);
- STAT(STRENGTH_FINISHED, this) = STAT(STRENGTH_FINISHED, spectatee);
- STAT(INVINCIBLE_FINISHED, this) = STAT(INVINCIBLE_FINISHED, spectatee);
- STAT(SUPERWEAPONS_FINISHED, this) = STAT(SUPERWEAPONS_FINISHED, spectatee);
STAT(AIR_FINISHED, this) = STAT(AIR_FINISHED, spectatee);
STAT(PRESSED_KEYS, this) = STAT(PRESSED_KEYS, spectatee);
STAT(WEAPONS, this) = STAT(WEAPONS, spectatee);
#include <common/mapobjects/trigger/counter.qh>
#include <common/mapobjects/triggers.qh>
#include <common/mutators/mutator/buffs/buffs.qh>
+#include <common/mutators/mutator/buffs/sv_buffs.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/notifications/all.qh>
#include <common/stats.qh>
#include <common/weapons/_all.qh>
if (!(this.spawnflags & 8))
{
- STAT(STRENGTH_FINISHED, actor) = 0;
- STAT(INVINCIBLE_FINISHED, actor) = 0;
- if(STAT(BUFFS, actor)) // TODO: make a dropbuffs function to handle this
+ StatusEffects_remove(STATUSEFFECT_Strength, actor, STATUSEFFECT_REMOVE_NORMAL);
+ StatusEffects_remove(STATUSEFFECT_Shield, actor, STATUSEFFECT_REMOVE_NORMAL);
+ entity heldbuff = buff_FirstFromFlags(actor);
+ if(heldbuff) // TODO: make a dropbuffs function to handle this
{
- int buffid = buff_FirstFromFlags(STAT(BUFFS, actor)).m_id;
+ int buffid = heldbuff.m_id;
Send_Notification(NOTIF_ONE, actor, MSG_MULTI, ITEM_BUFF_DROP, buffid);
sound(actor, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
if(!IS_INDEPENDENT_PLAYER(actor))
Send_Notification(NOTIF_ALL_EXCEPT, actor, MSG_INFO, INFO_ITEM_BUFF_LOST, actor.netname, buffid);
- STAT(BUFFS, actor) = 0;
- STAT(BUFF_TIME, actor) = 0;
+ buff_RemoveAll(actor, STATUSEFFECT_REMOVE_NORMAL);
}
}
else if (it.classname == "item_health_mega")
SetResourceExplicit(this, RES_HEALTH, 200);
else if (it.classname == "item_buff") {
- entity buff = buff_FirstFromFlags(STAT(BUFFS, it));
+ entity buff = it.buffdef;
this.netname = cons(this.netname, buff.netname);
- STAT(BUFF_TIME, this) = it.count;
+ this.buffs_finished = it.count;
}
//remove(it); // removing ents in init functions causes havoc, workaround:
#include <common/mapobjects/defs.qh>
#include <common/mapobjects/triggers.qh>
#include <common/mutators/mutator/buffs/buffs.qh>
+#include <common/mutators/mutator/buffs/sv_buffs.qh>
#include <common/mutators/mutator/instagib/sv_instagib.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/mutators/mutator/waypoints/waypointsprites.qh>
#include <common/notifications/all.qh>
#include <common/physics/movetypes/movetypes.qh>
if(w != 0 || slot == 0)
s = strcat(s, ftos(w));
}
- if(time < STAT(STRENGTH_FINISHED, player))
+ if(StatusEffects_active(STATUSEFFECT_Strength, player))
s = strcat(s, "S");
- if(time < STAT(INVINCIBLE_FINISHED, player))
+ if(StatusEffects_active(STATUSEFFECT_Shield, player))
s = strcat(s, "I");
if(PHYS_INPUT_BUTTON_CHAT(player))
s = strcat(s, "T");
int f3 = 0;
if(deathtype == DEATH_BUFF.m_id)
- f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id;
+ f3 = buff_FirstFromFlags(attacker).m_id;
if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker))
Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
return healed;
}
-float Fire_IsBurning(entity e)
-{
- return (time < e.fire_endtime);
-}
-
float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
{
float dps;
if(IS_DEAD(e))
return -1;
}
- else
- {
- if(!e.fire_burner)
- {
- // print("adding a fire burner to ", e.classname, "\n");
- e.fire_burner = new(fireburner);
- setthink(e.fire_burner, fireburner_think);
- e.fire_burner.nextthink = time;
- e.fire_burner.owner = e;
- }
- }
t = max(t, 0.1);
dps = d / t;
- if(Fire_IsBurning(e))
+ if(StatusEffects_active(STATUSEFFECT_Burning, e))
{
- mintime = e.fire_endtime - time;
+ float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
+
+ mintime = fireendtime - time;
maxtime = max(mintime, t);
mindps = e.fire_damagepersec;
// b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
e.fire_damagepersec = totaldamage / totaltime;
- e.fire_endtime = time + totaltime;
+ StatusEffects_apply(STATUSEFFECT_Burning, e, time + totaltime, 0);
if(totaldamage > 1.2 * mindamage)
{
e.fire_deathtype = dt;
else
{
e.fire_damagepersec = dps;
- e.fire_endtime = time + t;
+ StatusEffects_apply(STATUSEFFECT_Burning, e, time + t, 0);
e.fire_deathtype = dt;
e.fire_owner = o;
e.fire_hitsound = false;
float t, d, hi, ty;
entity o;
- // water, slime and ice stop fire
- if (STAT(FROZEN, e) || (e.waterlevel && (e.watertype != CONTENT_LAVA)))
- e.fire_endtime = 0;
-
- if (!Fire_IsBurning(e))
- return;
-
for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
if(IS_NOT_A_CLIENT(o))
o = e.fire_owner;
- t = min(frametime, e.fire_endtime - time);
+ float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
+ t = min(frametime, fireendtime - time);
d = e.fire_damagepersec * t;
hi = e.fire_owner.damage_dealt;
if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
{
- t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
+ t = autocvar_g_balance_firetransfer_time * (fireendtime - time);
d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
}
});
}
}
-
-void Fire_ApplyEffect(entity e)
-{
- if(Fire_IsBurning(e))
- e.effects |= EF_FLAME;
- else
- e.effects &= ~EF_FLAME;
-}
-
-void fireburner_think(entity this)
-{
- // for players, this is done in the regular loop
- if(wasfreed(this.owner))
- {
- delete(this);
- return;
- }
- Fire_ApplyEffect(this.owner);
- if(!Fire_IsBurning(this.owner))
- {
- this.owner.fire_burner = NULL;
- delete(this);
- return;
- }
- Fire_ApplyDamage(this.owner);
- this.nextthink = time;
-}
bool Heal(entity targ, entity inflictor, float amount, float limit);
.float fire_damagepersec;
-.float fire_endtime;
.float fire_deathtype;
.entity fire_owner;
.float fire_hitsound;
.entity fire_burner;
-void fireburner_think(entity this);
-
-float Fire_IsBurning(entity e);
-
float Fire_AddDamage(entity e, entity o, float d, float t, float dt);
void Fire_ApplyDamage(entity e);
-void Fire_ApplyEffect(entity e);
-
IntrusiveList g_damagedbycontents;
STATIC_INIT(g_damagedbycontents) { g_damagedbycontents = IL_NEW(); }
#include <common/monsters/_mod.qh>
#include <common/mutators/mutator/buffs/buffs.qh>
#include <common/mutators/mutator/buffs/sv_buffs.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/notifications/all.qh>
#include <common/util.qh>
#include <common/weapons/_all.qh>
if (item.strength_finished)
{
pickedup = true;
- STAT(STRENGTH_FINISHED, player) = max(STAT(STRENGTH_FINISHED, player), time) + item.strength_finished;
+ StatusEffects_apply(STATUSEFFECT_Strength, player, max(StatusEffects_gettime(STATUSEFFECT_Strength, player), time) + item.strength_finished, 0);
}
if (item.invincible_finished)
{
pickedup = true;
- STAT(INVINCIBLE_FINISHED, player) = max(STAT(INVINCIBLE_FINISHED, player), time) + item.invincible_finished;
+ StatusEffects_apply(STATUSEFFECT_Shield, player, max(StatusEffects_gettime(STATUSEFFECT_Shield, player), time) + item.invincible_finished, 0);
}
if (item.superweapons_finished)
{
pickedup = true;
- STAT(SUPERWEAPONS_FINISHED, player) = max(STAT(SUPERWEAPONS_FINISHED, player), time) + item.superweapons_finished;
+ StatusEffects_apply(STATUSEFFECT_Superweapons, player, max(StatusEffects_gettime(STATUSEFFECT_Superweapons, player), time) + item.superweapons_finished, 0);
}
// always eat teamed entities
else if(argv(j) == "fuel_regen") this.items |= ITEM_JetpackRegen.m_itemid;
else
{
- FOREACH(Buffs, it != BUFF_Null,
+ FOREACH(StatusEffect, it.instanceOfBuff,
{
string s = Buff_UndeprecateName(argv(j));
if(s == it.netname)
{
- STAT(BUFFS, this) |= (it.m_itemid);
- if(!STAT(BUFF_TIME, this))
- STAT(BUFF_TIME, this) = it.m_time(it);
+ this.buffdef = it;
+ if(!this.buffs_finished)
+ this.buffs_finished = it.m_time(it);
break;
}
});
res = GetResource(this, RES_FUEL); if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "fuel");
res = GetResource(this, RES_HEALTH); if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "health");
res = GetResource(this, RES_ARMOR); if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "armor");
- // HACK: buffs share a single timer, so we need to include enabled buffs AFTER disabled ones to avoid loss
- FOREACH(Buffs, it != BUFF_Null && !(STAT(BUFFS, this) & it.m_itemid), str = sprintf("%s %s%d %s", str, valueprefix, max(0, STAT(BUFF_TIME, this)), it.netname));
- FOREACH(Buffs, it != BUFF_Null && (STAT(BUFFS, this) & it.m_itemid), str = sprintf("%s %s%d %s", str, valueprefix, max(0, STAT(BUFF_TIME, this)), it.netname));
+ FOREACH(StatusEffect, it.instanceOfBuff, str = sprintf("%s %s%d %s", str, valueprefix, this.buffs_finished * boolean(this.buffdef == it), it.netname));
FOREACH(Weapons, it != WEP_Null, str = sprintf("%s %s%d %s", str, itemprefix, !!(STAT(WEAPONS, this) & (it.m_wepset)), it.netname));
}
this.netname = strzone(str);
bool GiveBuff(entity e, Buff thebuff, int op, int val)
{
- bool had_buff = (STAT(BUFFS, e) & thebuff.m_itemid);
- float new_buff_time = ((had_buff) ? STAT(BUFF_TIME, e) : 0);
+ bool had_buff = StatusEffects_active(thebuff, e);
+ float new_buff_time = ((had_buff) ? StatusEffects_gettime(thebuff, e) : 0);
switch (op)
{
case OP_SET:
}
if(new_buff_time <= 0)
{
- if(had_buff)
- STAT(BUFF_TIME, e) = new_buff_time;
- STAT(BUFFS, e) &= ~thebuff.m_itemid;
+ StatusEffects_remove(thebuff, e, STATUSEFFECT_REMOVE_TIMEOUT);
}
else
{
- STAT(BUFF_TIME, e) = new_buff_time;
- STAT(BUFFS, e) = thebuff.m_itemid; // NOTE: replaces any existing buffs on the player!
+ buff_RemoveAll(e, STATUSEFFECT_REMOVE_CLEAR); // clear old buffs on the player first!
+ StatusEffects_apply(thebuff, e, new_buff_time, 0);
}
- bool have_buff = (STAT(BUFFS, e) & thebuff.m_itemid);
+ bool have_buff = StatusEffects_active(thebuff, e);
return (had_buff != have_buff);
}
return SetResourceExplicit(e, res_type, new_val);
}
+bool GiveStatusEffect(entity e, StatusEffects this, int op, float val)
+{
+ bool had_eff = StatusEffects_active(this, e);
+ float new_eff_time = ((had_eff) ? StatusEffects_gettime(this, e) : 0);
+ switch (op)
+ {
+ case OP_SET:
+ new_eff_time = val;
+ break;
+ case OP_MIN:
+ new_eff_time = max(new_eff_time, val);
+ break;
+ case OP_MAX:
+ new_eff_time = min(new_eff_time, val);
+ break;
+ case OP_PLUS:
+ new_eff_time += val;
+ break;
+ case OP_MINUS:
+ new_eff_time -= val;
+ break;
+ }
+ if(new_eff_time <= 0)
+ StatusEffects_remove(this, e, STATUSEFFECT_REMOVE_TIMEOUT);
+ else
+ StatusEffects_apply(this, e, new_eff_time, 0);
+ bool have_eff = StatusEffects_active(this, e);
+ return (had_eff != have_eff);
+}
float GiveItems(entity e, float beginarg, float endarg)
{
}
}
- STAT(STRENGTH_FINISHED, e) = max(0, STAT(STRENGTH_FINISHED, e) - time);
- STAT(INVINCIBLE_FINISHED, e) = max(0, STAT(INVINCIBLE_FINISHED, e) - time);
- STAT(SUPERWEAPONS_FINISHED, e) = max(0, STAT(SUPERWEAPONS_FINISHED, e) - time);
- STAT(BUFF_TIME, e) = max(0, STAT(BUFF_TIME, e) - time);
+ if(e.statuseffects)
+ {
+ FOREACH(StatusEffect, true,
+ {
+ e.statuseffects.statuseffect_time[it.m_id] = max(0, e.statuseffects.statuseffect_time[it.m_id] - time);
+ });
+ }
PREGIVE(e, items);
PREGIVE_WEAPONS(e);
- PREGIVE(e, stat_STRENGTH_FINISHED);
- PREGIVE(e, stat_INVINCIBLE_FINISHED);
- PREGIVE(e, stat_SUPERWEAPONS_FINISHED);
+ PREGIVE_STATUSEFFECT(e, STATUSEFFECT_Strength);
+ PREGIVE_STATUSEFFECT(e, STATUSEFFECT_Shield);
+ //PREGIVE_STATUSEFFECT(e, STATUSEFFECT_Superweapons);
PREGIVE_RESOURCE(e, RES_BULLETS);
PREGIVE_RESOURCE(e, RES_CELLS);
PREGIVE_RESOURCE(e, RES_PLASMA);
continue;
case "ALL":
got += GiveBit(e, items, ITEM_JetpackRegen.m_itemid, op, val);
- got += GiveValue(e, stat_STRENGTH_FINISHED, op, val);
- got += GiveValue(e, stat_INVINCIBLE_FINISHED, op, val);
- got += GiveValue(e, stat_SUPERWEAPONS_FINISHED, op, val);
+ got += GiveStatusEffect(e, STATUSEFFECT_Strength, op, val);
+ got += GiveStatusEffect(e, STATUSEFFECT_Shield, op, val);
+ got += GiveStatusEffect(e, STATUSEFFECT_Superweapons, op, val);
got += GiveBit(e, items, IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS, op, val);
case "all":
got += GiveBit(e, items, ITEM_Jetpack.m_itemid, op, val);
case "allweapons":
FOREACH(Weapons, it != WEP_Null && !(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK)), got += GiveWeapon(e, it.m_id, op, val));
//case "allbuffs": // all buffs makes a player god, do not want!
- //FOREACH(Buffs, it != BUFF_Null, got += GiveBuff(e, it.m_itemid, op, val));
+ //FOREACH(StatusEffect, it.instanceOfBuff, got += GiveBuff(e, it, op, val));
case "allammo":
got += GiveResourceValue(e, RES_CELLS, op, val);
got += GiveResourceValue(e, RES_PLASMA, op, val);
got += GiveBit(e, items, ITEM_JetpackRegen.m_itemid, op, val);
break;
case "strength":
- got += GiveValue(e, stat_STRENGTH_FINISHED, op, val);
+ got += GiveStatusEffect(e, STATUSEFFECT_Strength, op, val);
break;
case "invincible":
- got += GiveValue(e, stat_INVINCIBLE_FINISHED, op, val);
+ got += GiveStatusEffect(e, STATUSEFFECT_Shield, op, val);
break;
case "superweapons":
- got += GiveValue(e, stat_SUPERWEAPONS_FINISHED, op, val);
+ got += GiveStatusEffect(e, STATUSEFFECT_Superweapons, op, val);
break;
case "cells":
got += GiveResourceValue(e, RES_CELLS, op, val);
got += GiveResourceValue(e, RES_FUEL, op, val);
break;
default:
- FOREACH(Buffs, it != BUFF_Null && buff_Available(it) && Buff_UndeprecateName(cmd) == it.netname,
+ FOREACH(StatusEffect, it.instanceOfBuff && buff_Available(it) && Buff_UndeprecateName(cmd) == it.netname,
{
got += GiveBuff(e, it, op, val);
break;
if(STAT(WEAPONS, e) & (it.m_wepset))
it.wr_init(it);
});
- POSTGIVE_VALUE(e, stat_STRENGTH_FINISHED, 1, SND_POWERUP, SND_POWEROFF);
- POSTGIVE_VALUE(e, stat_INVINCIBLE_FINISHED, 1, SND_Shield, SND_POWEROFF);
- //POSTGIVE_VALUE(e, stat_SUPERWEAPONS_FINISHED, 1, SND_Null, SND_Null);
+ POSTGIVE_STATUSEFFECT(e, STATUSEFFECT_Strength, 1, SND_POWERUP, SND_POWEROFF);
+ POSTGIVE_STATUSEFFECT(e, STATUSEFFECT_Shield, 1, SND_POWERUP, SND_POWEROFF);
POSTGIVE_RESOURCE(e, RES_BULLETS, 0, SND_ITEMPICKUP, SND_Null);
POSTGIVE_RESOURCE(e, RES_CELLS, 0, SND_ITEMPICKUP, SND_Null);
POSTGIVE_RESOURCE(e, RES_PLASMA, 0, SND_ITEMPICKUP, SND_Null);
POSTGIVE_RES_ROT(e, RES_ARMOR, 1, pauserotarmor_finished, autocvar_g_balance_pause_armor_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_ARMOR25, SND_Null);
POSTGIVE_RES_ROT(e, RES_HEALTH, 1, pauserothealth_finished, autocvar_g_balance_pause_health_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_MEGAHEALTH, SND_Null);
- if(STAT(SUPERWEAPONS_FINISHED, e) <= 0)
+ if(!StatusEffects_active(STATUSEFFECT_Superweapons, e))
+ {
if(!g_weaponarena && (STAT(WEAPONS, e) & WEPSET_SUPERWEAPONS))
- STAT(SUPERWEAPONS_FINISHED, e) = autocvar_g_balance_superweapons_time;
+ StatusEffects_apply(STATUSEFFECT_Superweapons, e, autocvar_g_balance_superweapons_time, 0);
+ }
- if(STAT(STRENGTH_FINISHED, e) <= 0)
- STAT(STRENGTH_FINISHED, e) = 0;
- else
- STAT(STRENGTH_FINISHED, e) += time;
- if(STAT(INVINCIBLE_FINISHED, e) <= 0)
- STAT(INVINCIBLE_FINISHED, e) = 0;
- else
- STAT(INVINCIBLE_FINISHED, e) += time;
- if(STAT(SUPERWEAPONS_FINISHED, e) <= 0)
- STAT(SUPERWEAPONS_FINISHED, e) = 0;
- else
- STAT(SUPERWEAPONS_FINISHED, e) += time;
- if(STAT(BUFF_TIME, e) <= 0)
- STAT(BUFF_TIME, e) = 0;
- else
- STAT(BUFF_TIME, e) += time;
+ if(e.statuseffects)
+ {
+ FOREACH(StatusEffect, true,
+ {
+ if(e.statuseffects.statuseffect_time[it.m_id] <= 0)
+ e.statuseffects.statuseffect_time[it.m_id] = 0;
+ else
+ e.statuseffects.statuseffect_time[it.m_id] += time;
+ });
+
+ StatusEffects_update(e);
+ }
for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
{
#define PREGIVE_WEAPONS(e) WepSet save_weapons; save_weapons = STAT(WEAPONS, e)
#define PREGIVE(e,f) float save_##f; save_##f = (e).f
+#define PREGIVE_STATUSEFFECT(e,f) float save_##f = StatusEffects_gettime((f), (e))
#define PREGIVE_RESOURCE(e,f) float save_##f = GetResource((e), (f))
#define POSTGIVE_WEAPON(e,b,snd_incr,snd_decr) GiveSound((e), !!(save_weapons & WepSet_FromWeapon(b)), !!(STAT(WEAPONS, e) & WepSet_FromWeapon(b)), 0, snd_incr, snd_decr)
#define POSTGIVE_BIT(e,f,b,snd_incr,snd_decr) GiveSound((e), save_##f & (b), (e).f & (b), 0, snd_incr, snd_decr)
+#define POSTGIVE_STATUSEFFECT(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, StatusEffects_gettime((f), (e)), t, snd_incr, snd_decr)
#define POSTGIVE_RESOURCE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, GetResource((e), (f)), t, snd_incr, snd_decr)
#define POSTGIVE_RES_ROT(e,f,t,rotfield,rottime,regenfield,regentime,snd_incr,snd_decr) GiveRot((e),save_##f,GetResource((e),(f)),rotfield,rottime,regenfield,regentime);GiveSound((e),save_##f,GetResource((e),(f)),t,snd_incr,snd_decr)
#define POSTGIVE_VALUE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr)
// don't play teleportation sounds
this.teleportable = TELEPORT_SIMPLE;
- STAT(STRENGTH_FINISHED, this) = 0;
- STAT(INVINCIBLE_FINISHED, this) = 0;
- STAT(SUPERWEAPONS_FINISHED, this) = 0;
STAT(AIR_FINISHED, this) = 0;
STAT(MOVEVARS_SPECIALCOMMAND, this) = false; // sweet release
#include <common/items/item.qh>
#include <common/mapinfo.qh>
#include <common/mapobjects/subs.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/notifications/all.qh>
#include <common/state.qh>
#include <common/util.qh>
});
if(superweapons <= 1)
{
- wep.superweapons_finished = STAT(SUPERWEAPONS_FINISHED, own);
- STAT(SUPERWEAPONS_FINISHED, own) = 0;
+ wep.superweapons_finished = StatusEffects_gettime(STATUSEFFECT_Superweapons, own);
+ StatusEffects_remove(STATUSEFFECT_Superweapons, own, STATUSEFFECT_REMOVE_CLEAR);
}
else
{
- float timeleft = STAT(SUPERWEAPONS_FINISHED, own) - time;
+ float timeleft = StatusEffects_gettime(STATUSEFFECT_Superweapons, own) - time;
float weptimeleft = timeleft / superweapons;
wep.superweapons_finished = time + weptimeleft;
- STAT(SUPERWEAPONS_FINISHED, own) -= weptimeleft;
+ if(own.statuseffects)
+ {
+ // TODO: this doesn't explicitly enable the effect, use apply here?
+ own.statuseffects.statuseffect_time[STATUSEFFECT_Superweapons.m_id] -= weptimeleft;
+ StatusEffects_update(own);
+ }
}
}
}