]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into 'Mario/status_effects'
authorterencehill <piuntn@gmail.com>
Tue, 15 Jun 2021 13:24:58 +0000 (13:24 +0000)
committerterencehill <piuntn@gmail.com>
Tue, 15 Jun 2021 13:24:58 +0000 (13:24 +0000)
# Conflicts:
#   qcsrc/common/stats.qh

52 files changed:
.gitlab-ci.yml
qcsrc/client/hud/hud.qc
qcsrc/client/hud/hud.qh
qcsrc/client/hud/panel/healtharmor.qc
qcsrc/client/hud/panel/powerups.qc
qcsrc/client/hud/panel/powerups.qh
qcsrc/client/view.qc
qcsrc/common/items/item.qh
qcsrc/common/mutators/mutator/_mod.inc
qcsrc/common/mutators/mutator/_mod.qh
qcsrc/common/mutators/mutator/buffs/all.inc
qcsrc/common/mutators/mutator/buffs/buffs.qc
qcsrc/common/mutators/mutator/buffs/buffs.qh
qcsrc/common/mutators/mutator/buffs/cl_buffs.qc
qcsrc/common/mutators/mutator/buffs/sv_buffs.qc
qcsrc/common/mutators/mutator/buffs/sv_buffs.qh
qcsrc/common/mutators/mutator/instagib/sv_instagib.qc
qcsrc/common/mutators/mutator/nades/nades.qc
qcsrc/common/mutators/mutator/status_effects/_mod.inc [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/_mod.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/all.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/all.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/cl_status_effects.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/cl_status_effects.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.inc [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/status_effect/_mod.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/status_effect/burning.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/status_effect/burning.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/status_effect/powerups.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/status_effect/powerups.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/status_effects.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/status_effects.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/sv_status_effects.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/status_effects/sv_status_effects.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/superspec/sv_superspec.qc
qcsrc/common/physics/player.qh
qcsrc/common/state.qc
qcsrc/common/stats.qh
qcsrc/common/weapons/weapon/devastator.qc
qcsrc/common/weapons/weapon/fireball.qc
qcsrc/common/weapons/weapon/minelayer.qc
qcsrc/server/bot/default/havocbot/havocbot.qc
qcsrc/server/bot/default/havocbot/roles.qc
qcsrc/server/cheats.qc
qcsrc/server/client.qc
qcsrc/server/compat/quake3.qc
qcsrc/server/damage.qc
qcsrc/server/damage.qh
qcsrc/server/items/items.qc
qcsrc/server/items/items.qh
qcsrc/server/player.qc
qcsrc/server/weapons/throwing.qc

index 1eb84dbe87c84c09730077b247ad28d409a56795..aa03c51a92113b594180be6aba311e7f97252994 100644 (file)
@@ -32,7 +32,7 @@ test_sv_game:
     - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints\r
     - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache\r
     - make\r
-    - EXPECT=dfb2a117b18258dfcc0b95e7e9f54189\r
+    - EXPECT=081ecefbcad92adf350377334733c1d5\r
     - HASH=$(${ENGINE} -noconfig -nohome +timestamps 1 +exec serverbench.cfg\r
       | tee /dev/stderr\r
       | sed -e 's,^\[[^]]*\] ,,'\r
index 9593e193652a6843a290fe7b47f55c22c52907c1..cfb819efab9c30b5899d022412719d222fcadfcd 100644 (file)
@@ -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);
 }
 
 /*
index a7080f90cf31415ebd07439ddebe16fb06c12d0a..a02ff3688f262bac6f3581165e8e5f4b86d020a9 100644 (file)
@@ -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);
 
index 7e6e33339cdf68704ddd68ac79bcb46ed0bc6f42..bc5b68b0a94c53bf9b0c782482c3eaa3586e6d4f 100644 (file)
@@ -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;
index 9a4ab1df3ff62904f109abfa5fe276fc6bb21187..70d41ec47e71f955b242fd72511ed948c80e6f09 100644 (file)
@@ -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
index a109062a489114c7aa107181d65a8f1175d371f7..5d4df01d077cd012244b408a8e2d353aca8bc14e 100644 (file)
@@ -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);
index 389afebfeb117f5bb3fd1c836070760fc3a7e9b8..55d2086923c6ca96d22ce26fe81bb16010a2fd55 100644 (file)
@@ -21,6 +21,7 @@
 #include <common/mapobjects/trigger/viewloc.qh>
 #include <common/minigames/cl_minigames.qh>
 #include <common/minigames/cl_minigames_hud.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
 #include <common/mutators/mutator/waypoints/all.qh>
 #include <common/net_linked.qh>
 #include <common/net_notice.qh>
@@ -1229,7 +1230,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); }
 
index 5c6ec494a3d33bd92536aa5a701d7d8da2971514..8f651ad049bb19fd8d44591af923f9bbbc95357c 100644 (file)
@@ -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)) \
index 40a763c8ec04b39b9a39dfb3d8d915bf647e1324..8db241e76bf67789506e354e345400612cf06b4b 100644 (file)
@@ -34,6 +34,7 @@
 #include <common/mutators/mutator/sandbox/_mod.inc>
 #include <common/mutators/mutator/spawn_near_teammate/_mod.inc>
 #include <common/mutators/mutator/stale_move_negation/_mod.inc>
+#include <common/mutators/mutator/status_effects/_mod.inc>
 #include <common/mutators/mutator/superspec/_mod.inc>
 #include <common/mutators/mutator/touchexplode/_mod.inc>
 #include <common/mutators/mutator/vampire/_mod.inc>
index 6a9261dd41f394cd3496bf119ec6fa92e6ebb0f1..7c8a809ba23d961d03b0286463d5fb8c81b79858 100644 (file)
@@ -34,6 +34,7 @@
 #include <common/mutators/mutator/sandbox/_mod.qh>
 #include <common/mutators/mutator/spawn_near_teammate/_mod.qh>
 #include <common/mutators/mutator/stale_move_negation/_mod.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
 #include <common/mutators/mutator/superspec/_mod.qh>
 #include <common/mutators/mutator/touchexplode/_mod.qh>
 #include <common/mutators/mutator/vampire/_mod.qh>
index 5b53b3d651c2d76e49ac25e6778fc24a781e7fde..46a96f6612940e29ab53ee86a6f713e8d91722c0 100644 (file)
@@ -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';
 }
index 2ab07ddf28fd1785297497b87122ad43e613537a..15cc199601b1999075e4812896733baa8a9bbee3 100644 (file)
@@ -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;
-}
index 91f56066f61b0f7a9eed9532cdff15c9ffb295a5..5b93fa79595a78fab9e741b81ce1408873d65031 100644 (file)
@@ -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 <common/items/item/pickup.qh>
-CLASS(Buff, Pickup)
+#include <common/mutators/mutator/status_effects/_mod.qh>
+CLASS(Buff, StatusEffects)
        /** bit index */
        ATTRIB(Buff, m_itemid, int, 0);
        ATTRIB(Buff, netname, string, "buff");
+       ATTRIB(Buff, m_icon, string, "buff");
        ATTRIB(Buff, m_color, vector, '1 1 1');
        ATTRIB(Buff, m_name, string, "Buff");
        ATTRIB(Buff, m_skin, int, 0);
+       ATTRIB(Buff, m_lifetime, float, 60);
        ATTRIB(Buff, m_sprite, string, "");
        METHOD(Buff, display, void(entity this, void(string name, string icon) returns)) {
-               returns(this.m_name, sprintf("/gfx/hud/%s/buff_%s", cvar_string("menu_skin"), this.netname));
+               returns(this.m_name, sprintf("/gfx/hud/%s/%s", cvar_string("menu_skin"), this.m_icon));
        }
 #ifdef SVQC
        METHOD(Buff, m_time, float(Buff this))
@@ -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"
index f33e3ff63a93fe1a1bbea3dfb5936f3a09f108d6..c6e7377c61e4db0446e145d06995b6e4a7162d73 100644 (file)
@@ -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;
     }
 }
index 3433aad9db634f0e4513d860e519ac807b7c480b..a176e512f31e744b9a8f5390e30d03c518294248 100644 (file)
@@ -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,
@@ -535,7 +566,7 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_SplitHealthArmor)
        // NOTE: vampire PlayerDamage_SplitHealthArmor code is similar
        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));
 
@@ -551,7 +582,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;
@@ -562,7 +593,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)
@@ -574,7 +605,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;
 }
 
@@ -593,13 +624,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);
        }
@@ -611,15 +641,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;
@@ -631,7 +661,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;
@@ -699,7 +729,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;
                }
        }
@@ -727,7 +757,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;
 }
@@ -760,7 +790,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)
@@ -771,7 +801,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)
@@ -784,9 +814,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;
@@ -809,38 +839,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;
@@ -853,7 +879,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)
                {
@@ -863,25 +889,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)
                                {
@@ -901,7 +929,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)
                                {
@@ -914,19 +942,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)
@@ -939,15 +971,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;
                }
@@ -972,27 +1004,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
 }
 
index 0a39b35eb42a0a486d1fb7b2cf4e0a8372c5dd76..1b095c0f2c50db47956e4e85ec6034648af1ea04 100644 (file)
@@ -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);
index 44c476a3a9ef954685d9aa866b9c15896a20adb3..51d65a917ece56e5368d32e86e7c97be2273c85d 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <server/client.qh>
 #include <common/items/_mod.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
 #include "../random_items/sv_random_items.qh"
 
 bool autocvar_g_instagib_damagedbycontents = true;
@@ -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);
index e71c61855fc7f749692e7af12cfed3052590f8fd..96729df449a078cdbe6746d93d5697a680d38ad7 100644 (file)
@@ -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 (file)
index 0000000..7279f0d
--- /dev/null
@@ -0,0 +1,11 @@
+// generated file; do not modify
+#include <common/mutators/mutator/status_effects/all.qc>
+#include <common/mutators/mutator/status_effects/status_effects.qc>
+#ifdef CSQC
+    #include <common/mutators/mutator/status_effects/cl_status_effects.qc>
+#endif
+#ifdef SVQC
+    #include <common/mutators/mutator/status_effects/sv_status_effects.qc>
+#endif
+
+#include <common/mutators/mutator/status_effects/status_effect/_mod.inc>
diff --git a/qcsrc/common/mutators/mutator/status_effects/_mod.qh b/qcsrc/common/mutators/mutator/status_effects/_mod.qh
new file mode 100644 (file)
index 0000000..59a5dcf
--- /dev/null
@@ -0,0 +1,11 @@
+// generated file; do not modify
+#include <common/mutators/mutator/status_effects/all.qh>
+#include <common/mutators/mutator/status_effects/status_effects.qh>
+#ifdef CSQC
+    #include <common/mutators/mutator/status_effects/cl_status_effects.qh>
+#endif
+#ifdef SVQC
+    #include <common/mutators/mutator/status_effects/sv_status_effects.qh>
+#endif
+
+#include <common/mutators/mutator/status_effects/status_effect/_mod.qh>
diff --git a/qcsrc/common/mutators/mutator/status_effects/all.qc b/qcsrc/common/mutators/mutator/status_effects/all.qc
new file mode 100644 (file)
index 0000000..b056751
--- /dev/null
@@ -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 (file)
index 0000000..6fc4757
--- /dev/null
@@ -0,0 +1,59 @@
+#pragma once
+
+#ifdef GAMEQC
+    #include <common/sounds/all.qh>
+#endif
+
+REGISTRY(StatusEffect, 32)
+REGISTER_REGISTRY(StatusEffect)
+#define REGISTER_STATUSEFFECT(id, inst) REGISTER(StatusEffect, STATUSEFFECT, id, m_id, inst)
+
+REGISTRY_SORT(StatusEffect)
+REGISTRY_CHECK(StatusEffect)
+
+REGISTRY_DEFINE_GET(StatusEffect, NULL)
+STATIC_INIT(StatusEffect) { FOREACH(StatusEffect, true, it.m_id = i); }
+
+enum
+{
+    STATUSEFFECT_FLAG_ACTIVE = BIT(0),
+    STATUSEFFECT_FLAG_PERSISTENT = BIT(1) ///< Effect is currently being granted passively.
+};
+
+enum
+{
+    STATUSEFFECT_REMOVE_NORMAL,        ///< Effect is being removed by a function, calls regular removal mechanics.
+    STATUSEFFECT_REMOVE_TIMEOUT,
+    STATUSEFFECT_REMOVE_CLEAR          ///< Effect is being forcibly removed without calling any additional mechanics.
+};
+
+CLASS(StatusEffects, Object)
+    ATTRIB(StatusEffects, m_id, int, 0);
+    ATTRIB(StatusEffects, m_name, string);
+    ATTRIB(StatusEffects, m_icon, string);
+    ATTRIB(StatusEffects, m_color, vector, '1 1 1');
+    /** Whether the effect is displayed in the HUD */
+    ATTRIB(StatusEffects, m_hidden, bool, false);
+    /** Lifetime scale for HUD progress bars */
+    ATTRIB(StatusEffects, m_lifetime, float, 30);
+#ifdef GAMEQC
+    ATTRIB(StatusEffects, m_sound, Sound, SND_Null);
+    ATTRIB(StatusEffects, m_sound_rm, Sound, SND_Null);
+    METHOD(StatusEffects, m_tick, void(StatusEffects this, entity actor));
+    METHOD(StatusEffects, m_active, bool(StatusEffects this, entity actor));
+    /** Stores times of status effects, the id being the index */
+    ATTRIBARRAY(StatusEffects, statuseffect_time, float, REGISTRY_MAX(StatusEffect));
+    ATTRIBARRAY(StatusEffects, statuseffect_flags, int, REGISTRY_MAX(StatusEffect));
+#endif
+#ifdef SVQC
+    METHOD(StatusEffects, m_apply, void(StatusEffects this, entity actor, float eff_time, int eff_flags));
+    METHOD(StatusEffects, m_remove, void(StatusEffects this, entity actor, int removal_type));
+    /** Sets the persistent flag and updates client side if returning true */
+    METHOD(StatusEffects, m_persistent, bool(StatusEffects this, entity actor)) { return false; };
+#endif
+    METHOD(StatusEffects, display, void(StatusEffects this, void(string name, string icon) returns))
+    {
+        TC(StatusEffects, this);
+        returns(this.m_name, this.m_icon ? sprintf("/gfx/hud/%s/%s", cvar_string("menu_skin"), this.m_icon) : string_null);
+    }
+ENDCLASS(StatusEffects)
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 (file)
index 0000000..b65555e
--- /dev/null
@@ -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 (file)
index 0000000..6f70f09
--- /dev/null
@@ -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 (file)
index 0000000..f02099f
--- /dev/null
@@ -0,0 +1,3 @@
+// generated file; do not modify
+#include <common/mutators/mutator/status_effects/status_effect/burning.qc>
+#include <common/mutators/mutator/status_effects/status_effect/powerups.qc>
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 (file)
index 0000000..c91959b
--- /dev/null
@@ -0,0 +1,3 @@
+// generated file; do not modify
+#include <common/mutators/mutator/status_effects/status_effect/burning.qh>
+#include <common/mutators/mutator/status_effects/status_effect/powerups.qh>
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 (file)
index 0000000..2e632c7
--- /dev/null
@@ -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 (file)
index 0000000..8935d49
--- /dev/null
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <common/mutators/mutator/status_effects/all.qh>
+
+#ifdef SVQC
+void Fire_ApplyDamage(entity e);
+#endif
+#ifdef GAMEQC
+SOUND(Burning_Remove, "desertfactory/steam_burst");
+#endif
+CLASS(Burning, StatusEffects)
+    ATTRIB(Burning, netname, string, "burning");
+#if 0
+    // NOTE: status effect name and icon disabled as they are not displayed
+    // re-enable if status effects are given a visual element
+    ATTRIB(Burning, m_name, string, _("Burning"));
+    ATTRIB(Burning, m_icon, string, "buff_inferno");
+#endif
+    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 (file)
index 0000000..8c031aa
--- /dev/null
@@ -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 (file)
index 0000000..62321d7
--- /dev/null
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <common/mutators/mutator/status_effects/all.qh>
+
+CLASS(Powerups, StatusEffects)
+#ifdef GAMEQC
+    ATTRIB(Powerups, m_sound_rm, Sound, SND_POWEROFF);
+#endif
+ENDCLASS(Powerups)
+
+CLASS(Strength, Powerups)
+    ATTRIB(Strength, netname, string, "strength");
+    ATTRIB(Strength, m_name, string, _("Strength"));
+    ATTRIB(Strength, m_icon, string, "strength");
+ENDCLASS(Strength)
+REGISTER_STATUSEFFECT(Strength, NEW(Strength));
+
+CLASS(Shield, Powerups)
+    ATTRIB(Shield, netname, string, "shield");
+    ATTRIB(Shield, m_name, string, _("Shield"));
+    ATTRIB(Shield, m_icon, string, "shield");
+ENDCLASS(Shield)
+REGISTER_STATUSEFFECT(Shield, NEW(Shield));
+
+CLASS(Superweapons, StatusEffects)
+    ATTRIB(Superweapons, netname, string, "superweapons");
+    ATTRIB(Superweapons, m_name, string, _("Superweapons"));
+    ATTRIB(Superweapons, m_icon, string, "superweapons");
+#ifdef GAMEQC
+    ATTRIB(Superweapons, m_sound_rm, Sound, SND_POWEROFF);
+#endif
+ENDCLASS(Superweapons)
+REGISTER_STATUSEFFECT(Superweapons, NEW(Superweapons));
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 (file)
index 0000000..eb1ced0
--- /dev/null
@@ -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 (file)
index 0000000..f779509
--- /dev/null
@@ -0,0 +1,203 @@
+#pragma once
+
+#ifdef GAMEQC
+#include <common/mutators/base.qh>
+
+REGISTER_MUTATOR(status_effects, true);
+#endif
+
+#include "all.qh"
+
+#ifdef GAMEQC
+/** Entity statuseffects */
+.StatusEffects statuseffects;
+/** Player statuseffects storage (holds previous state) */
+.StatusEffects statuseffects_store;
+
+REGISTER_NET_LINKED(ENT_CLIENT_STATUSEFFECTS)
+
+const int StatusEffects_groups_minor = 8; // must be a multiple of 8 (one byte) to optimize bandwidth usage
+const int StatusEffects_groups_major = 4; // must be >= ceil(REGISTRY_COUNT(StatusEffect) / StatusEffects_groups_minor)
+#endif
+
+// no need to perform these checks on both server and client
+#ifdef CSQC
+STATIC_INIT(StatusEffects)
+{
+       if (StatusEffects_groups_minor / 8 != floor(StatusEffects_groups_minor / 8))
+               error("StatusEffects_groups_minor is not a multiple of 8.");
+       int min_major_value = ceil(REGISTRY_COUNT(StatusEffect) / StatusEffects_groups_minor);
+       if (StatusEffects_groups_major < min_major_value)
+               error(sprintf("StatusEffects_groups_major can not be < %d.", min_major_value));
+}
+#endif
+
+#ifdef SVQC
+#define G_MAJOR(id) (floor((id) / StatusEffects_groups_minor))
+#define G_MINOR(id) ((id) % StatusEffects_groups_minor)
+#endif
+
+#ifdef CSQC
+StatusEffects g_statuseffects;
+void StatusEffects_entremove(entity this)
+{
+    if(g_statuseffects == this)
+        g_statuseffects = NULL;
+}
+
+NET_HANDLE(ENT_CLIENT_STATUSEFFECTS, bool isnew)
+{
+    make_pure(this);
+    g_statuseffects = this;
+    this.entremove = StatusEffects_entremove;
+    const int majorBits = Readbits(StatusEffects_groups_major);
+    for (int i = 0; i < StatusEffects_groups_major; ++i) {
+        if (!(majorBits & BIT(i))) {
+            continue;
+        }
+        const int minorBits = Readbits(StatusEffects_groups_minor);
+        for (int j = 0; j < StatusEffects_groups_minor; ++j) {
+            if (!(minorBits & BIT(j))) {
+                continue;
+            }
+            const StatusEffects it = REGISTRY_GET(StatusEffect, StatusEffects_groups_minor * i + j);
+            this.statuseffect_time[it.m_id] = ReadFloat();
+            this.statuseffect_flags[it.m_id] = ReadByte();
+        }
+    }
+    return true;
+}
+#endif
+
+#ifdef SVQC
+int SEFminorBitsArr[StatusEffects_groups_major];
+void StatusEffects_Write(StatusEffects data, StatusEffects store)
+{
+    if (!data) {
+        WriteShort(MSG_ENTITY, 0);
+        return;
+    }
+    TC(StatusEffects, data);
+
+       for (int i = 0; i < StatusEffects_groups_major; ++i)
+               SEFminorBitsArr[i] = 0;
+
+    int majorBits = 0;
+    FOREACH(StatusEffect, true, {
+        .float fld = statuseffect_time[it.m_id];
+        .int flg = statuseffect_flags[it.m_id];
+        const bool changed = (store.(fld) != data.(fld) || store.(flg) != data.(flg));
+        store.(fld) = data.(fld);
+        store.(flg) = data.(flg);
+        if (changed) {
+                       int maj = G_MAJOR(it.m_id);
+                       majorBits = BITSET(majorBits, BIT(maj), true);
+                       SEFminorBitsArr[maj] = BITSET(SEFminorBitsArr[maj], BIT(G_MINOR(it.m_id)), true);
+        }
+    });
+
+       Writebits(MSG_ENTITY, majorBits, StatusEffects_groups_major);
+       for (int i = 0; i < StatusEffects_groups_major; ++i)
+       {
+               if (!(majorBits & BIT(i)))
+                       continue;
+
+               const int minorBits = SEFminorBitsArr[i];
+               Writebits(MSG_ENTITY, minorBits, StatusEffects_groups_minor);
+               for (int j = 0; j < StatusEffects_groups_minor; ++j)
+               {
+                       if (!(minorBits & BIT(j)))
+                               continue;
+
+                       const entity it = REGISTRY_GET(StatusEffect, StatusEffects_groups_minor * i + j);
+            WriteFloat(MSG_ENTITY, data.statuseffect_time[it.m_id]);
+                       WriteByte(MSG_ENTITY, data.statuseffect_flags[it.m_id]);
+               }
+       }
+}
+#endif
+
+#undef G_MAJOR
+#undef G_MINOR
+
+#ifdef SVQC
+bool StatusEffects_Send(StatusEffects this, Client to, int sf)
+{
+    TC(StatusEffects, this);
+    WriteHeader(MSG_ENTITY, ENT_CLIENT_STATUSEFFECTS);
+    StatusEffects_Write(this, to.statuseffects_store);
+    return true;
+}
+
+bool StatusEffects_customize(entity this, entity client)
+{
+    // sends to spectators too!
+    return (client.statuseffects == this);
+}
+
+void StatusEffects_new(entity this)
+{
+    StatusEffects eff = NEW(StatusEffects);
+       this.statuseffects = eff;
+       eff.owner = this;
+    if(this.statuseffects_store)
+    {
+        setcefc(eff, StatusEffects_customize);
+        Net_LinkEntity(eff, false, 0, StatusEffects_Send);
+    }
+}
+void StatusEffects_delete(entity e) { delete(e.statuseffects); e.statuseffects = NULL; }
+// may be called on non-player entities, should be harmless!
+void StatusEffects_update(entity e) { e.statuseffects.SendFlags = 0xFFFFFF; }
+
+// this clears the storage entity instead of the statuseffects object, useful for map resets and such
+void StatusEffects_clearall(entity store)
+{
+    if(!store)
+        return; // safety net
+    // NOTE: you will need to perform StatusEffects_update after this to update the storage entity
+    // (unless store is the storage entity)
+    FOREACH(StatusEffect, true, {
+        store.statuseffect_time[it.m_id] = 0;
+        store.statuseffect_flags[it.m_id] = 0;
+    });
+}
+
+void StatusEffectsStorage_attach(entity e) { e.statuseffects_store = NEW(StatusEffects); e.statuseffects_store.drawonlytoclient = e; }
+void StatusEffectsStorage_delete(entity e) { delete(e.statuseffects_store); e.statuseffects_store = NULL; }
+
+// called when an entity is deleted with delete() / remove()
+// or when a player disconnects
+void ONREMOVE(entity this)
+{
+    // remove statuseffects object attached to 'this'
+    if(this.statuseffects && this.statuseffects.owner == this)
+        StatusEffects_delete(this);
+}
+#endif
+
+#ifdef GAMEQC
+bool StatusEffects_active(StatusEffects this, entity actor);
+
+// runs every SV_StartFrame on the server
+// called by HUD_Powerups_add on the client
+void StatusEffects_tick(entity actor);
+
+// accesses the status effect timer, returns 0 if the entity has no statuseffects object
+// pass g_statuseffects as the actor on client side
+// pass the entity with a .statuseffects on server side
+float StatusEffects_gettime(StatusEffects this, entity actor);
+#endif
+#ifdef SVQC
+// call when applying the effect to an entity
+void StatusEffects_apply(StatusEffects this, entity actor, float eff_time, int eff_flags);
+
+// copies all the status effect fields to the specified storage entity
+// does not perform an update
+void StatusEffects_copy(StatusEffects this, entity store, float time_offset);
+
+// call when removing the effect
+void StatusEffects_remove(StatusEffects this, entity actor, int removal_type);
+
+void StatusEffects_removeall(entity actor, int removal_type);
+#endif
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 (file)
index 0000000..df97b05
--- /dev/null
@@ -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 (file)
index 0000000..6f70f09
--- /dev/null
@@ -0,0 +1 @@
+#pragma once
index 5328be79c1cddf1eea677096c4c5623bb9df6002..edb61b91c253cd41d6b24d1b2f0ec10fe8b5493e 100644 (file)
@@ -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;
index eb044fb24f31aace84f193b04fdb8320d3d0611a..7bfdeb94f209e32b45fe32fa8bd25d16f55cef57 100644 (file)
@@ -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)
index 018f626e0318bcf02dee8807f08260afd1262158..d174eeb6bac526e0bfd0a0fba21b779384456e97 100644 (file)
@@ -1,5 +1,6 @@
 #include "state.qh"
 
+#include <common/mutators/mutator/status_effects/_mod.qh>
 #include <server/command/getreplies.qh>
 
 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;
 
index 1d96dacded38bd8607d8b4b19ff000321e3d8158..12879a7531b1acd3f6bff24b18654d54bd1569cb 100644 (file)
@@ -87,8 +87,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)
 REGISTER_STAT(FUEL, int)
@@ -103,7 +101,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)
@@ -120,7 +117,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)
@@ -130,7 +126,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(CAPTURE_PROGRESS, float)
 REGISTER_STAT(ENTRAP_ORB, float)
 REGISTER_STAT(ENTRAP_ORB_ALPHA, float)
index 891c44583d65d1b491c372234c2e269979b77b19..8ada68244d4de7fd70caca90c82b672296c3a579 100644 (file)
@@ -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;
index ce070f75e8c9300804feb3265b6e5a88b1bab689..3021843be0da7d12e6f2594b2c61f6169e07b161 100644 (file)
@@ -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)
index 6fbe11195653b73ab0ac5dc5553cf86d59504fa6..7f93e8fde376402c653684e1a91a93509a99a89d 100644 (file)
@@ -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;
index 41851afc007cadbff723da1360dfa6c36e3a64a6..fbe14c90f74e5d9754361ba65fe07d04051f868c 100644 (file)
@@ -7,6 +7,7 @@
 #include <common/mapobjects/teleporters.qh>
 #include <common/mapobjects/trigger/hurt.qh>
 #include <common/mapobjects/trigger/jumppads.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
 #include <common/net_linked.qh>
 #include <common/physics/player.qh>
 #include <common/state.qh>
@@ -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)
index 94c1cf44204785d4b031a8b11813efeb64c9c846..c000bbdacfe0363b4f0d0dbbe3a17f1b120496f6 100644 (file)
@@ -1,6 +1,7 @@
 #include "roles.qh"
 
 #include <common/stats.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
 #include <common/weapons/_all.qh>
 #include <server/bot/default/bot.qh>
 #include <server/bot/default/cvars.qh>
@@ -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;
index 20a13f83e6cef5121b0c220a416fd8b756aab470..6091c5344077b4b147ef0d09e0e44eba4d715e53 100644 (file)
@@ -10,6 +10,7 @@
 #include <common/mapobjects/teleporters.qh>
 #include <common/mapobjects/triggers.qh>
 #include <common/monsters/_mod.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
 #include <common/physics/player.qh>
 #include <common/stats.qh>
 #include <common/util.qh>
@@ -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();
index d80bdffa78d8aaf09e9e7a6faea3479aa5b49438..7217c4b87b0df4ae0f2105bd633ca8a4abbca118 100644 (file)
@@ -23,6 +23,7 @@
 #include <common/mutators/mutator/instagib/sv_instagib.qh>
 #include <common/mutators/mutator/nades/nades.qh>
 #include <common/mutators/mutator/overkill/oknex.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
 #include <common/mutators/mutator/waypoints/all.qh>
 #include <common/net_linked.qh>
 #include <common/net_notice.qh>
@@ -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);
index ea951ddceb3e573b9b0137b4f9bc1fb45b8a1435..ae9dbd35760faf0e834f184d5f64d16fcac65d82 100644 (file)
@@ -5,6 +5,8 @@
 #include <common/mapobjects/trigger/counter.qh>
 #include <common/mapobjects/triggers.qh>
 #include <common/mutators/mutator/buffs/buffs.qh>
+#include <common/mutators/mutator/buffs/sv_buffs.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
 #include <common/notifications/all.qh>
 #include <common/stats.qh>
 #include <common/weapons/_all.qh>
@@ -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:
index 83252c00d86676ff4f9bf951e17c29e8d5da987d..7ce674f5187b29e000a77150132c1035c90d6dcc 100644 (file)
@@ -9,7 +9,9 @@
 #include <common/mapobjects/defs.qh>
 #include <common/mapobjects/triggers.qh>
 #include <common/mutators/mutator/buffs/buffs.qh>
+#include <common/mutators/mutator/buffs/sv_buffs.qh>
 #include <common/mutators/mutator/instagib/sv_instagib.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
 #include <common/mutators/mutator/waypoints/waypointsprites.qh>
 #include <common/notifications/all.qh>
 #include <common/physics/movetypes/movetypes.qh>
@@ -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);
@@ -1104,11 +1106,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;
@@ -1119,23 +1116,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;
@@ -1198,7 +1186,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;
@@ -1218,7 +1206,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;
@@ -1233,18 +1221,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;
@@ -1264,37 +1246,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;
-}
index ea64dcf575d15e06701bb0bf111aa3ef3f33ef1e..3d6e024812ba4790abebe702e83b793d5af27d3f 100644 (file)
@@ -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(); }
index f0d8e3835e38d9310af0c6525049a0ec54d8392d..1a5cfdda5e74e236618996ba66cd988cdc7752e2 100644 (file)
@@ -9,6 +9,7 @@
 #include <common/monsters/_mod.qh>
 #include <common/mutators/mutator/buffs/buffs.qh>
 #include <common/mutators/mutator/buffs/sv_buffs.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
 #include <common/notifications/all.qh>
 #include <common/util.qh>
 #include <common/weapons/_all.qh>
@@ -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)
        {
index 7fc9f0fa3154d384e449ef6d34decac85fb19ef9..39009fe90f3e62b8c7b0251f302ff2845b583d58 100644 (file)
@@ -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)
index dd0007e36b2cb6663161cc8b12e638b808002e27..916aa8a286355bcb187e38bbf8ad4acafda103b7 100644 (file)
@@ -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;
 
                this.death_time = time;
index 1b3bb661c3a939358a31b212287de2946a3837f4..66b62c6e2f502fb61db9ea4a364d4539752a480d 100644 (file)
@@ -3,6 +3,7 @@
 #include <common/items/item.qh>
 #include <common/mapinfo.qh>
 #include <common/mapobjects/subs.qh>
+#include <common/mutators/mutator/status_effects/_mod.qh>
 #include <common/notifications/all.qh>
 #include <common/state.qh>
 #include <common/util.qh>
@@ -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);
+                               }
                        }
                }
        }