From: Mario Date: Sun, 23 May 2021 06:41:48 +0000 (+1000) Subject: Experimental status effects system: general backend for buffs and debuffs networked... X-Git-Tag: xonotic-v0.8.5~408 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=c58baab5bd2dc4a77eb7d8e97824621d887c8e75;p=xonotic%2Fxonotic-data.pk3dir.git Experimental status effects system: general backend for buffs and debuffs networked to the client --- diff --git a/qcsrc/client/hud/hud.qc b/qcsrc/client/hud/hud.qc index 9593e1936..cfb819efa 100644 --- a/qcsrc/client/hud/hud.qc +++ b/qcsrc/client/hud/hud.qc @@ -395,11 +395,12 @@ void HUD_Panel_DrawHighlight(vector pos, vector mySize, vector color, float theA 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) { @@ -437,7 +438,7 @@ void DrawNumIcon_expanding(vector myPos, vector mySize, float theTime, string ic // 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; } @@ -471,14 +472,14 @@ void DrawNumIcon_expanding(vector myPos, vector mySize, float theTime, string ic // 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); } /* diff --git a/qcsrc/client/hud/hud.qh b/qcsrc/client/hud/hud.qh index 14e1af0a0..07d31bd64 100644 --- a/qcsrc/client/hud/hud.qh +++ b/qcsrc/client/hud/hud.qh @@ -78,8 +78,8 @@ void HUD_Radar_Hide_Maximized(); 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); diff --git a/qcsrc/client/hud/panel/healtharmor.qc b/qcsrc/client/hud/panel/healtharmor.qc index 7e6e33339..bc5b68b0a 100644 --- a/qcsrc/client/hud/panel/healtharmor.qc +++ b/qcsrc/client/hud/panel/healtharmor.qc @@ -139,7 +139,7 @@ void HUD_HealthArmor() 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); @@ -236,7 +236,7 @@ void HUD_HealthArmor() 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) @@ -283,7 +283,7 @@ void HUD_HealthArmor() } 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; diff --git a/qcsrc/client/hud/panel/powerups.qc b/qcsrc/client/hud/panel/powerups.qc index 9a4ab1df3..70d41ec47 100644 --- a/qcsrc/client/hud/panel/powerups.qc +++ b/qcsrc/client/hud/panel/powerups.qc @@ -21,6 +21,7 @@ void HUD_Powerups_Export(int fh) .vector colormod; // Color .float count; // Time left .float lifetime; // Maximum time +.float cnt; // Infinite timer entity powerupItems; int powerupItemsCount; @@ -34,7 +35,7 @@ void resetPowerupItems() 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(); @@ -49,6 +50,7 @@ void addPowerupItem(string name, string icon, vector color, float currentTime, f item.colormod = color; item.count = currentTime; item.lifetime = lifeTime; + item.cnt = isInfinite; ++powerupItemsCount; } @@ -72,10 +74,6 @@ int getPowerupItemAlign(int align, int column, int row, int columns, int rows, b void HUD_Powerups() { - int allItems = STAT(ITEMS); - int allBuffs = STAT(BUFFS); - float strengthTime, shieldTime, superTime; - // Initialize items if(!autocvar__hud_configure) { @@ -83,37 +81,11 @@ void HUD_Powerups() 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) @@ -205,10 +177,15 @@ void HUD_Powerups() 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 diff --git a/qcsrc/client/hud/panel/powerups.qh b/qcsrc/client/hud/panel/powerups.qh index a109062a4..5d4df01d0 100644 --- a/qcsrc/client/hud/panel/powerups.qh +++ b/qcsrc/client/hud/panel/powerups.qh @@ -13,4 +13,4 @@ vector autocvar_hud_progressbar_shield_color; 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); diff --git a/qcsrc/client/view.qc b/qcsrc/client/view.qc index 871fab3bd..9eeb6246d 100644 --- a/qcsrc/client/view.qc +++ b/qcsrc/client/view.qc @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -1299,7 +1300,7 @@ void View_PostProcessing() } // 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); } diff --git a/qcsrc/common/items/item.qh b/qcsrc/common/items/item.qh index 5c6ec494a..8f651ad04 100644 --- a/qcsrc/common/items/item.qh +++ b/qcsrc/common/items/item.qh @@ -63,6 +63,7 @@ const int ITS_GLOW = BIT(6); #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)) \ diff --git a/qcsrc/common/mutators/mutator/_mod.inc b/qcsrc/common/mutators/mutator/_mod.inc index 40a763c8e..8db241e76 100644 --- a/qcsrc/common/mutators/mutator/_mod.inc +++ b/qcsrc/common/mutators/mutator/_mod.inc @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include diff --git a/qcsrc/common/mutators/mutator/_mod.qh b/qcsrc/common/mutators/mutator/_mod.qh index 6a9261dd4..7c8a809ba 100644 --- a/qcsrc/common/mutators/mutator/_mod.qh +++ b/qcsrc/common/mutators/mutator/_mod.qh @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include diff --git a/qcsrc/common/mutators/mutator/buffs/all.inc b/qcsrc/common/mutators/mutator/buffs/all.inc index 5b53b3d65..46a96f661 100644 --- a/qcsrc/common/mutators/mutator/buffs/all.inc +++ b/qcsrc/common/mutators/mutator/buffs/all.inc @@ -15,6 +15,7 @@ string Buff_UndeprecateName(string buffname) 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'; } @@ -24,6 +25,7 @@ BUFF_SPAWNFUNC_Q3TA_COMPAT(ammoregen, BUFF_AMMO) 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'; } @@ -33,6 +35,7 @@ BUFF_SPAWNFUNC_Q3TA_COMPAT(guard, BUFF_RESISTANCE) 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'; } @@ -43,6 +46,7 @@ BUFF_SPAWNFUNC_Q3TA_COMPAT(scout, BUFF_SPEED) 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'; } @@ -53,6 +57,7 @@ BUFF_SPAWNFUNC_Q3TA_COMPAT(revival, BUFF_MEDIC) 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'; } @@ -62,6 +67,7 @@ BUFF_SPAWNFUNC_Q3TA_COMPAT(doubler, BUFF_BASH) 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'; } @@ -70,6 +76,7 @@ BUFF_SPAWNFUNCS(vampire, BUFF_VAMPIRE) 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'; } @@ -78,6 +85,7 @@ BUFF_SPAWNFUNCS(disability, BUFF_DISABILITY) 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'; } @@ -86,6 +94,7 @@ BUFF_SPAWNFUNCS(vengeance, BUFF_VENGEANCE) 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'; } @@ -95,6 +104,7 @@ BUFF_SPAWNFUNC_Q3TA_COMPAT(jumper, BUFF_JUMP) 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'; } @@ -104,6 +114,7 @@ BUFF_SPAWNFUNC_Q3TA_COMPAT(invis, BUFF_INVISIBLE) 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'; } @@ -112,6 +123,7 @@ BUFF_SPAWNFUNCS(inferno, BUFF_INFERNO) 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'; } @@ -120,6 +132,7 @@ BUFF_SPAWNFUNCS(swapper, BUFF_SWAPPER) 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'; } @@ -128,6 +141,7 @@ BUFF_SPAWNFUNCS(magnet, BUFF_MAGNET) 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'; } @@ -136,6 +150,7 @@ BUFF_SPAWNFUNCS(luck, BUFF_LUCK) 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'; } diff --git a/qcsrc/common/mutators/mutator/buffs/buffs.qc b/qcsrc/common/mutators/mutator/buffs/buffs.qc index 2ab07ddf2..15cc19960 100644 --- a/qcsrc/common/mutators/mutator/buffs/buffs.qc +++ b/qcsrc/common/mutators/mutator/buffs/buffs.qc @@ -2,15 +2,6 @@ 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; -} diff --git a/qcsrc/common/mutators/mutator/buffs/buffs.qh b/qcsrc/common/mutators/mutator/buffs/buffs.qh index 91f56066f..5b93fa795 100644 --- a/qcsrc/common/mutators/mutator/buffs/buffs.qh +++ b/qcsrc/common/mutators/mutator/buffs/buffs.qh @@ -12,24 +12,22 @@ REGISTER_WAYPOINT(Buff, _("Buff"), "", '1 0.5 0', 1); 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 -CLASS(Buff, Pickup) +#include +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)) @@ -38,17 +36,18 @@ CLASS(Buff, Pickup) 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); \ } @@ -66,10 +65,7 @@ STATIC_INIT(REGISTER_BUFFS) { #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" diff --git a/qcsrc/common/mutators/mutator/buffs/cl_buffs.qc b/qcsrc/common/mutators/mutator/buffs/cl_buffs.qc index f33e3ff63..c6e7377c6 100644 --- a/qcsrc/common/mutators/mutator/buffs/cl_buffs.qc +++ b/qcsrc/common/mutators/mutator/buffs/cl_buffs.qc @@ -1,24 +1,16 @@ #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; } } diff --git a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc index 6fa6e8d05..bf680f9a4 100644 --- a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc +++ b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc @@ -71,12 +71,13 @@ void buff_Effect(entity player, string eff) // 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); @@ -86,7 +87,7 @@ void buff_Waypoint_Spawn(entity e) { 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); @@ -165,6 +166,7 @@ void buff_Touch(entity this, entity toucher) 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) ) { @@ -172,17 +174,19 @@ void buff_Touch(entity this, entity toucher) 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 @@ -191,23 +195,27 @@ void buff_Touch(entity this, entity toucher) 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; @@ -221,15 +229,35 @@ float buff_Available(entity buff) 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) @@ -237,9 +265,9 @@ 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; @@ -256,7 +284,7 @@ void buff_Think(entity this) WaypointSprite_UpdateBuildFinished(this.buff_waypoint, time + this.buff_activetime - frametime); } - this.oldbuffs = STAT(BUFFS, this); + this.oldbuffs = this.buffdef; } if(!game_stopped) @@ -268,7 +296,8 @@ void buff_Think(entity this) } 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; @@ -329,7 +358,7 @@ void buff_Reset(entity this) 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); } @@ -357,9 +386,9 @@ void buff_Init(entity this) 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"; @@ -371,6 +400,8 @@ void buff_Init(entity this) 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; @@ -388,15 +419,15 @@ void buff_Init(entity this) 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); } @@ -408,7 +439,7 @@ void buff_Init_Compat(entity ent, entity replacement) else if (ent.spawnflags & 4) ent.team = NUM_TEAM_2; - STAT(BUFFS, ent) = replacement.m_itemid; + ent.buffdef = replacement; buff_Init(ent); } @@ -447,28 +478,28 @@ MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate) 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)) @@ -482,11 +513,11 @@ MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate) 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) @@ -495,11 +526,11 @@ MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate) 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; @@ -507,13 +538,13 @@ MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate) 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, @@ -534,7 +565,7 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_SplitHealthArmor) { 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)); @@ -553,7 +584,7 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerSpawn) 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; @@ -564,7 +595,7 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPhysics_UpdateStats) 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) @@ -576,7 +607,7 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPhysics) 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; } @@ -595,13 +626,12 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerDies) { 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); } @@ -613,15 +643,15 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerUseKey, CBC_ORDER_FIRST) 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; @@ -633,7 +663,7 @@ MUTATOR_HOOKFUNCTION(buffs, ForbidThrowCurrentWeapon) 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; @@ -701,7 +731,7 @@ MUTATOR_HOOKFUNCTION(buffs, ForbidThrowCurrentWeapon) 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; } } @@ -729,7 +759,7 @@ MUTATOR_HOOKFUNCTION(buffs, CustomizeWaypoint) // 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; } @@ -762,7 +792,7 @@ MUTATOR_HOOKFUNCTION(buffs, WeaponRateFactor) { 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) @@ -773,7 +803,7 @@ MUTATOR_HOOKFUNCTION(buffs, WeaponSpeedFactor) { 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) @@ -786,9 +816,9 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) { 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; @@ -811,38 +841,34 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) // 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; @@ -855,7 +881,7 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) }); } - if(STAT(BUFFS, player) & BUFF_AMMO.m_itemid) + if(StatusEffects_active(BUFF_AMMO, player)) { for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) { @@ -865,25 +891,27 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) } } - 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) { @@ -903,7 +931,7 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) 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) { @@ -916,19 +944,23 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) 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) @@ -941,15 +973,15 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) 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; } @@ -974,27 +1006,18 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) #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 } diff --git a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qh b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qh index 0a39b35eb..1b095c0f2 100644 --- a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qh +++ b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qh @@ -80,7 +80,7 @@ float autocvar_g_buffs_luck_damagemultiplier = 3; .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) @@ -91,3 +91,7 @@ const vector BUFF_MAX = ('16 16 60'); .float cvar_cl_buffs_autoreplace; float buff_Available(entity buff); + +void buff_RemoveAll(entity actor, int removal_type); + +entity buff_FirstFromFlags(entity actor); diff --git a/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc b/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc index 44c476a3a..51d65a917 100644 --- a/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc +++ b/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc @@ -2,6 +2,7 @@ #include #include +#include #include "../random_items/sv_random_items.qh" bool autocvar_g_instagib_damagedbycontents = true; @@ -181,13 +182,12 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPowerups) { 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 { @@ -200,7 +200,7 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPowerups) } 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 { @@ -215,8 +215,8 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPowerups) 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); @@ -224,7 +224,7 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPowerups) } 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); diff --git a/qcsrc/common/mutators/mutator/nades/nades.qc b/qcsrc/common/mutators/mutator/nades/nades.qc index e71c61855..96729df44 100644 --- a/qcsrc/common/mutators/mutator/nades/nades.qc +++ b/qcsrc/common/mutators/mutator/nades/nades.qc @@ -224,7 +224,7 @@ void napalm_damage(entity this, float dist, float damage, float edgedamage, floa 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) diff --git a/qcsrc/common/mutators/mutator/status_effects/_mod.inc b/qcsrc/common/mutators/mutator/status_effects/_mod.inc new file mode 100644 index 000000000..7279f0dc2 --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/_mod.inc @@ -0,0 +1,11 @@ +// generated file; do not modify +#include +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif + +#include diff --git a/qcsrc/common/mutators/mutator/status_effects/_mod.qh b/qcsrc/common/mutators/mutator/status_effects/_mod.qh new file mode 100644 index 000000000..59a5dcf6d --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/_mod.qh @@ -0,0 +1,11 @@ +// generated file; do not modify +#include +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif + +#include diff --git a/qcsrc/common/mutators/mutator/status_effects/all.qc b/qcsrc/common/mutators/mutator/status_effects/all.qc new file mode 100644 index 000000000..b05675162 --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/all.qc @@ -0,0 +1 @@ +#include "all.qh" diff --git a/qcsrc/common/mutators/mutator/status_effects/all.qh b/qcsrc/common/mutators/mutator/status_effects/all.qh new file mode 100644 index 000000000..6fc475738 --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/all.qh @@ -0,0 +1,59 @@ +#pragma once + +#ifdef GAMEQC + #include +#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) diff --git a/qcsrc/common/mutators/mutator/status_effects/cl_status_effects.qc b/qcsrc/common/mutators/mutator/status_effects/cl_status_effects.qc new file mode 100644 index 000000000..b65555eb9 --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/cl_status_effects.qc @@ -0,0 +1,25 @@ +#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); +} diff --git a/qcsrc/common/mutators/mutator/status_effects/cl_status_effects.qh b/qcsrc/common/mutators/mutator/status_effects/cl_status_effects.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/cl_status_effects.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.inc b/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.inc new file mode 100644 index 000000000..f02099f1d --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.inc @@ -0,0 +1,3 @@ +// generated file; do not modify +#include +#include diff --git a/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.qh b/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.qh new file mode 100644 index 000000000..c91959bed --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.qh @@ -0,0 +1,3 @@ +// generated file; do not modify +#include +#include diff --git a/qcsrc/common/mutators/mutator/status_effects/status_effect/burning.qc b/qcsrc/common/mutators/mutator/status_effects/status_effect/burning.qc new file mode 100644 index 000000000..2e632c70f --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/status_effect/burning.qc @@ -0,0 +1,24 @@ +#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 diff --git a/qcsrc/common/mutators/mutator/status_effects/status_effect/burning.qh b/qcsrc/common/mutators/mutator/status_effects/status_effect/burning.qh new file mode 100644 index 000000000..0b4994ce2 --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/status_effect/burning.qh @@ -0,0 +1,22 @@ +#pragma once + +#include + +#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)); diff --git a/qcsrc/common/mutators/mutator/status_effects/status_effect/powerups.qc b/qcsrc/common/mutators/mutator/status_effects/status_effect/powerups.qc new file mode 100644 index 000000000..8c031aae4 --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/status_effect/powerups.qc @@ -0,0 +1,57 @@ +#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 diff --git a/qcsrc/common/mutators/mutator/status_effects/status_effect/powerups.qh b/qcsrc/common/mutators/mutator/status_effects/status_effect/powerups.qh new file mode 100644 index 000000000..62321d7f1 --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/status_effect/powerups.qh @@ -0,0 +1,33 @@ +#pragma once + +#include + +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)); diff --git a/qcsrc/common/mutators/mutator/status_effects/status_effects.qc b/qcsrc/common/mutators/mutator/status_effects/status_effects.qc new file mode 100644 index 000000000..eb1ced075 --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/status_effects.qc @@ -0,0 +1,61 @@ +#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 diff --git a/qcsrc/common/mutators/mutator/status_effects/status_effects.qh b/qcsrc/common/mutators/mutator/status_effects/status_effects.qh new file mode 100644 index 000000000..f7795094c --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/status_effects.qh @@ -0,0 +1,203 @@ +#pragma once + +#ifdef GAMEQC +#include + +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 diff --git a/qcsrc/common/mutators/mutator/status_effects/sv_status_effects.qc b/qcsrc/common/mutators/mutator/status_effects/sv_status_effects.qc new file mode 100644 index 000000000..df97b0589 --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/sv_status_effects.qc @@ -0,0 +1,116 @@ +#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); +} diff --git a/qcsrc/common/mutators/mutator/status_effects/sv_status_effects.qh b/qcsrc/common/mutators/mutator/status_effects/sv_status_effects.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mutators/mutator/status_effects/sv_status_effects.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/superspec/sv_superspec.qc b/qcsrc/common/mutators/mutator/superspec/sv_superspec.qc index 5328be79c..edb61b91c 100644 --- a/qcsrc/common/mutators/mutator/superspec/sv_superspec.qc +++ b/qcsrc/common/mutators/mutator/superspec/sv_superspec.qc @@ -346,7 +346,7 @@ MUTATOR_HOOKFUNCTION(superspec, SV_ParseClientCommand) 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; @@ -354,7 +354,7 @@ MUTATOR_HOOKFUNCTION(superspec, SV_ParseClientCommand) 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; @@ -362,7 +362,7 @@ MUTATOR_HOOKFUNCTION(superspec, SV_ParseClientCommand) 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; diff --git a/qcsrc/common/physics/player.qh b/qcsrc/common/physics/player.qh index 9d92581f4..e5eb72c0e 100644 --- a/qcsrc/common/physics/player.qh +++ b/qcsrc/common/physics/player.qh @@ -82,8 +82,6 @@ bool IsFlying(entity a); #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) diff --git a/qcsrc/common/state.qc b/qcsrc/common/state.qc index 018f626e0..d174eeb6b 100644 --- a/qcsrc/common/state.qc +++ b/qcsrc/common/state.qc @@ -1,5 +1,6 @@ #include "state.qh" +#include #include void Inventory_new(PlayerState this); @@ -58,6 +59,7 @@ void ClientState_attach(entity this) anticheat_init(this); W_HitPlotOpen(this); InventoryStorage_attach(this); + StatusEffectsStorage_attach(this); } void bot_clientdisconnect(entity this); @@ -77,6 +79,7 @@ void ClientState_detach(entity this) ClientData_Detach(this); entcs_detach(this); InventoryStorage_delete(this); + StatusEffectsStorage_delete(this); delete(CS(this)); this._cs = NULL; diff --git a/qcsrc/common/stats.qh b/qcsrc/common/stats.qh index e5621c29e..e476969fa 100644 --- a/qcsrc/common/stats.qh +++ b/qcsrc/common/stats.qh @@ -84,8 +84,6 @@ int autocvar_leadlimit; 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 */ @@ -102,7 +100,6 @@ REGISTER_STAT(HUD, int) 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) @@ -119,7 +116,6 @@ REGISTER_STAT(RESPAWN_TIME, float) 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) @@ -129,7 +125,6 @@ REGISTER_STAT(PLASMA, int) 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) diff --git a/qcsrc/common/weapons/weapon/devastator.qc b/qcsrc/common/weapons/weapon/devastator.qc index 891c44583..8ada68244 100644 --- a/qcsrc/common/weapons/weapon/devastator.qc +++ b/qcsrc/common/weapons/weapon/devastator.qc @@ -394,7 +394,7 @@ METHOD(Devastator, wr_aim, void(entity thiswep, entity actor, .entity weaponenti }); 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; diff --git a/qcsrc/common/weapons/weapon/fireball.qc b/qcsrc/common/weapons/weapon/fireball.qc index ce070f75e..3021843be 100644 --- a/qcsrc/common/weapons/weapon/fireball.qc +++ b/qcsrc/common/weapons/weapon/fireball.qc @@ -92,7 +92,7 @@ void W_Fireball_LaserPlay(entity this, float dt, float dist, float damage, float 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) diff --git a/qcsrc/common/weapons/weapon/minelayer.qc b/qcsrc/common/weapons/weapon/minelayer.qc index 6fbe11195..7f93e8fde 100644 --- a/qcsrc/common/weapons/weapon/minelayer.qc +++ b/qcsrc/common/weapons/weapon/minelayer.qc @@ -379,7 +379,7 @@ METHOD(MineLayer, wr_aim, void(entity thiswep, entity actor, .entity weaponentit 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; diff --git a/qcsrc/server/bot/default/havocbot/havocbot.qc b/qcsrc/server/bot/default/havocbot/havocbot.qc index 41851afc0..fbe14c90f 100644 --- a/qcsrc/server/bot/default/havocbot/havocbot.qc +++ b/qcsrc/server/bot/default/havocbot/havocbot.qc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -569,8 +570,8 @@ void havocbot_movetogoal(entity this) 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) diff --git a/qcsrc/server/bot/default/havocbot/roles.qc b/qcsrc/server/bot/default/havocbot/roles.qc index 94c1cf442..c000bbdac 100644 --- a/qcsrc/server/bot/default/havocbot/roles.qc +++ b/qcsrc/server/bot/default/havocbot/roles.qc @@ -1,6 +1,7 @@ #include "roles.qh" #include +#include #include #include #include @@ -200,10 +201,10 @@ void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org 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; diff --git a/qcsrc/server/cheats.qc b/qcsrc/server/cheats.qc index 20a13f83e..6091c5344 100644 --- a/qcsrc/server/cheats.qc +++ b/qcsrc/server/cheats.qc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -151,15 +152,12 @@ float CheatImpulse(entity this, int imp) 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: @@ -212,15 +210,13 @@ float CheatImpulse(entity this, int imp) 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(); diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc index 7d3c1faca..f3e8ca46f 100644 --- a/qcsrc/server/client.qc +++ b/qcsrc/server/client.qc @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -335,9 +336,6 @@ void PutObserverInServer(entity this) 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; @@ -373,7 +371,6 @@ void PutObserverInServer(entity this) 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; @@ -593,8 +590,6 @@ void PutPlayerInServer(entity this) 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; @@ -641,16 +636,9 @@ void PutPlayerInServer(entity this) 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; @@ -753,6 +741,10 @@ void PutPlayerInServer(entity this) } }); + 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 @@ -762,10 +754,6 @@ void PutPlayerInServer(entity this) spot.target = s; } - Unfreeze(this, false); - - MUTATOR_CALLHOOK(PlayerSpawn, this, spot); - if (autocvar_spawn_debug) { sprint(this, strcat("spawnpoint origin: ", vtos(spot.origin), "\n")); @@ -1476,7 +1464,7 @@ void player_powerups(entity this) 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); @@ -1487,16 +1475,13 @@ void player_powerups(entity 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); @@ -1505,7 +1490,7 @@ void player_powerups(entity this) } 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) @@ -1515,9 +1500,9 @@ void player_powerups(entity this) } 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); @@ -1526,7 +1511,7 @@ void player_powerups(entity this) } 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) @@ -1538,7 +1523,7 @@ void player_powerups(entity this) { 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); @@ -1549,8 +1534,8 @@ void player_powerups(entity this) } 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; @@ -1561,7 +1546,7 @@ void player_powerups(entity this) } 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)) @@ -1573,13 +1558,14 @@ void player_powerups(entity this) } 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); } } @@ -1769,9 +1755,6 @@ void SpectateCopy(entity this, entity spectatee) 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); diff --git a/qcsrc/server/compat/quake3.qc b/qcsrc/server/compat/quake3.qc index ea951ddce..ae9dbd357 100644 --- a/qcsrc/server/compat/quake3.qc +++ b/qcsrc/server/compat/quake3.qc @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -130,17 +132,17 @@ void target_init_use(entity this, entity actor, entity trigger) 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); } } @@ -196,9 +198,9 @@ void target_give_init(entity this) 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: diff --git a/qcsrc/server/damage.qc b/qcsrc/server/damage.qc index 7e052de04..95e1488fe 100644 --- a/qcsrc/server/damage.qc +++ b/qcsrc/server/damage.qc @@ -9,7 +9,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -93,9 +95,9 @@ string AppendItemcodes(string s, entity player) 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"); @@ -415,7 +417,7 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .en 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); @@ -1101,11 +1103,6 @@ bool Heal(entity targ, entity inflictor, float amount, float limit) 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; @@ -1116,23 +1113,14 @@ float Fire_AddDamage(entity e, entity o, float d, float t, float dt) 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; @@ -1195,7 +1183,7 @@ float Fire_AddDamage(entity e, entity o, float d, float t, float dt) // 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; @@ -1215,7 +1203,7 @@ float Fire_AddDamage(entity e, entity o, float d, float t, float 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; @@ -1230,18 +1218,12 @@ void Fire_ApplyDamage(entity e) 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; @@ -1261,37 +1243,10 @@ void Fire_ApplyDamage(entity e) 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; -} diff --git a/qcsrc/server/damage.qh b/qcsrc/server/damage.qh index ea64dcf57..3d6e02481 100644 --- a/qcsrc/server/damage.qh +++ b/qcsrc/server/damage.qh @@ -143,21 +143,14 @@ const float MAX_DAMAGEEXTRARADIUS = 16; 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(); } diff --git a/qcsrc/server/items/items.qc b/qcsrc/server/items/items.qc index 403125eea..5cf659cff 100644 --- a/qcsrc/server/items/items.qc +++ b/qcsrc/server/items/items.qc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -549,17 +550,17 @@ bool Item_GiveTo(entity item, entity player) 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 @@ -1223,14 +1224,14 @@ spawnfunc(target_items) 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; } }); @@ -1291,9 +1292,7 @@ spawnfunc(target_items) 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); @@ -1341,8 +1340,8 @@ float GiveWeapon(entity e, float wpn, float op, float val) 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: @@ -1363,16 +1362,14 @@ bool GiveBuff(entity e, Buff thebuff, int op, int val) } 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); } @@ -1416,6 +1413,35 @@ bool GiveResourceValue(entity e, int res_type, int op, int val) 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) { @@ -1440,16 +1466,19 @@ 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); @@ -1488,9 +1517,9 @@ float GiveItems(entity e, float beginarg, float endarg) 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); @@ -1499,7 +1528,7 @@ float GiveItems(entity e, float beginarg, float endarg) 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); @@ -1525,13 +1554,13 @@ float GiveItems(entity e, float beginarg, float endarg) 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); @@ -1559,7 +1588,7 @@ float GiveItems(entity e, float beginarg, float endarg) 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; @@ -1584,9 +1613,8 @@ float GiveItems(entity e, float beginarg, float endarg) 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); @@ -1596,26 +1624,24 @@ float GiveItems(entity e, float beginarg, float endarg) 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) { diff --git a/qcsrc/server/items/items.qh b/qcsrc/server/items/items.qh index 7fc9f0fa3..39009fe90 100644 --- a/qcsrc/server/items/items.qh +++ b/qcsrc/server/items/items.qh @@ -109,9 +109,11 @@ spawnfunc(target_items); #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) diff --git a/qcsrc/server/player.qc b/qcsrc/server/player.qc index 74259e3e8..291c9588c 100644 --- a/qcsrc/server/player.qc +++ b/qcsrc/server/player.qc @@ -558,9 +558,6 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, // 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 diff --git a/qcsrc/server/weapons/throwing.qc b/qcsrc/server/weapons/throwing.qc index 1b3bb661c..66b62c6e2 100644 --- a/qcsrc/server/weapons/throwing.qc +++ b/qcsrc/server/weapons/throwing.qc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -69,15 +70,20 @@ float W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector }); 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); + } } } }