+#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