diff --git a/sources/ClientRealm/ClientGlobal.uc b/sources/ClientRealm/ClientGlobal.uc
index e4a00ab..84236bd 100644
--- a/sources/ClientRealm/ClientGlobal.uc
+++ b/sources/ClientRealm/ClientGlobal.uc
@@ -19,8 +19,9 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see .
*/
-class ClientGlobal extends Object;
+class ClientGlobal extends CoreGlobal;
+var protected bool initialized;
// `Global` is expected to behave like a singleton and will store it's
// main instance in this variable's default value.
var protected ClientGlobal myself;
@@ -38,6 +39,11 @@ public final static function ClientGlobal GetInstance()
protected function Initialize()
{
+ if (initialized) {
+ return;
+ }
+ super.Initialize();
+ initialized = true;
}
defaultproperties
diff --git a/sources/CoreRealm/API/SideEffects/SideEffect.uc b/sources/CoreRealm/API/SideEffects/SideEffect.uc
new file mode 100644
index 0000000..7b95877
--- /dev/null
+++ b/sources/CoreRealm/API/SideEffects/SideEffect.uc
@@ -0,0 +1,164 @@
+/**
+ * Object representing a side effect introduced into the game/server.
+ * Side effects in Acedia refer to changes that aren't a part of mod's main
+ * functionality, but rather something necessary to make that functionality
+ * possible that might also affect how other mods work.
+ * This is a simple data container that is meant to describe relevant
+ * changes to the human user.
+ * Copyright 2022 Anton Tarasenko
+ *------------------------------------------------------------------------------
+ * This file is part of Acedia.
+ *
+ * Acedia is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Acedia is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Acedia. If not, see .
+ */
+class SideEffect extends AcediaObject
+ abstract;
+
+/**
+ * # Side effects
+ *
+ * In Acedia "side effect" refers to changes that aren't a part of mod's
+ * main functionality, but rather something necessary to make that
+ * functionality possible that might also affect how other mods work. Their
+ * purpose is to help developers and server admins figure out what changes were
+ * performed by Acedia or mods based on it.
+ * What needs to be considered a side effect is loosely defined and they
+ * are simply a tool to inform others that something they might not have
+ * expected has happened, that can possibly break other (their) mods.
+ * AcediaCore, for example, tried to leave a minimal footpring, avoiding
+ * making any changes to the game classes unless requested, but it still has to
+ * do some changes (adding `GameRules`, replacing damage types for some zeds,
+ * etc.) and `SideEffect`s can be used to document these changes. They can be
+ * used in a similar way for AcediaFixes - a package that is only meant for
+ * fixing bugs, but inevitably has to make a lot of under the hood changes to
+ * achieve that.
+ * On the other hand gameplay mods like Futility can make a lot of changes,
+ * but they can all be just expected part of its direct functionality: we
+ * expect feature that shares dosh of leavers to alter players' dosh values, so
+ * this is not a side effect. Such mods are likely not going to have to specify
+ * any side effects whatsoever.
+ *
+ * ## Implemention your own `SideEffect`s
+ *
+ * Simply make a non-abstract child class based on `SideEffect`, create its
+ * instance filled with necessary data and register it in `SideEffectAPI` once
+ * side effect is introduced. If you revert introduced side effect, you should
+ * remove registered object through the same API.
+ * Each class of the `SideEffect` is supposed to represent a particular
+ * side effect introduced into the game engine. Whether side effect is active
+ * is decided by whether it is currently registered in `SideEffectAPI`.
+ *
+ * NOTE: `SideEffect` should not have any logic and should serve as
+ * an immutable data container.
+ */
+
+var protected Text sideEffectName;
+var protected Text sideEffectDescription;
+var protected Text sideEffectPackage;
+var protected Text sideEffectSource;
+var protected Text sideEffectStatus;
+
+/**
+ * Returns name (short description) of the caller `SideEffect`. User need to
+ * be able to tell what this `SideEffect` is generally about from the glance at
+ * this name.
+ *
+ * Guideline is for this value to not exceed `80` cahracters, but this is not
+ * enforced.
+ *
+ * Must not be `none`.
+ *
+ * @return Name (short description) of the caller `SideEffect`.
+ * Guaranteed to not be `none`.
+ */
+public function Text GetName()
+{
+ if (sideEffectName != none) {
+ return sideEffectName.Copy();
+ }
+ return none;
+}
+
+/**
+ * Returns description of the caller `SideEffect`. This should describe what
+ * was done and why relevant change was necessary.
+ *
+ * Must not be `none`.
+ *
+ * @return Description of the caller `SideEffect`.
+ * Guaranteed to not be `none`.
+ */
+public function Text GetDescription()
+{
+ if (sideEffectDescription != none) {
+ return sideEffectDescription.Copy();
+ }
+ return none;
+}
+
+/**
+ * Returns name of the package ("*.u" file) that introduced this change.
+ *
+ * Note that if package "A" actually performed the change because another
+ * package "B" requested certain functionality, it is still package "A" that is
+ * responsible for the side effect.
+ *
+ * Must not be `none`.
+ *
+ * @return Name of the package ("*.u" file) that introduced this change.
+ * Guaranteed to not be `none`.
+ */
+public function Text GetPackage()
+{
+ if (sideEffectPackage != none) {
+ return sideEffectPackage.Copy();
+ }
+ return none;
+}
+
+/**
+ * What part of package caused this change. For huge packages can be used to
+ * further specify what introduced the change.
+ *
+ * Returned value can be `none` (e.g. when it is unnecessary for small
+ * packages).
+ *
+ * @return Name (short description) of the part of the package that caused
+ * caller `SideEffect`.
+ */
+public function Text GetSource()
+{
+ if (sideEffectSource != none) {
+ return sideEffectSource.Copy();
+ }
+ return none;
+}
+
+/**
+ * Status of the caller `SideEffect`. Some side effects can be introduced in
+ * several different ways - this value needs to help distinguish between them.
+ *
+ * @return Status of the caller `SideEffect`.
+ */
+public function Text GetStatus()
+{
+ if (sideEffectStatus != none) {
+ return sideEffectStatus.Copy();
+ }
+ return none;
+}
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/CoreRealm/API/SideEffects/SideEffectAPI.uc b/sources/CoreRealm/API/SideEffects/SideEffectAPI.uc
new file mode 100644
index 0000000..396fd5d
--- /dev/null
+++ b/sources/CoreRealm/API/SideEffects/SideEffectAPI.uc
@@ -0,0 +1,155 @@
+/**
+ * Simple API for managing a list of `SideEffect` info objects: can add,
+ * remove, return all and by package.
+ * Copyright 2022 Anton Tarasenko
+ *------------------------------------------------------------------------------
+ * This file is part of Acedia.
+ *
+ * Acedia is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Acedia is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Acedia. If not, see .
+ */
+class SideEffectAPI extends AcediaObject;
+
+var private array activeSideEffects;
+
+/**
+ * Returns all so far registered `SideEffect`s.
+ *
+ * @return Array of all registered `SideEffect`s.
+ */
+public final function array GetAll()
+{
+ local int i;
+
+ for (i = 0; i < activeSideEffects.length; i += 1) {
+ activeSideEffects[i].NewRef();
+ }
+ return activeSideEffects;
+}
+
+/**
+ * Returns active `SideEffect` instance of the specified class
+ * `sideEffectClass`.
+ *
+ * @param sideEffectClass Class of side effect to return active instance of.
+ * @return Active `SideEffect` instance of the specified class
+ * `sideEffectClass`.
+ * `none` if either `sideEffectClass` is `none` or side effect of such
+ * class is not currently active.
+ */
+public final function SideEffect GetClass(class sideEffectClass)
+{
+ local int i;
+
+ if (sideEffectClass == none) {
+ return none;
+ }
+ for (i = 0; i < activeSideEffects.length; i += 1)
+ {
+ if (activeSideEffects[i].class == sideEffectClass)
+ {
+ activeSideEffects[i].NewRef();
+ return activeSideEffects[i];
+ }
+ }
+ return none;
+}
+
+/**
+ * Returns all so far registered `SideEffect`s from a package `packageName`
+ * (case insensitive).
+ *
+ * @param packageName Name opf the package, in `SideEffect`s from which we are
+ * interested. Must be not `none`.
+ * @return Array of all registered `SideEffect`s from a package `packageName`.
+ * If `none`, returns an empty array.
+ */
+public final function array GetFromPackage(BaseText packageName)
+{
+ local int i;
+ local Text nextPackage;
+ local array result;
+
+ if (packageName == none) {
+ return result;
+ }
+ for (i = 0; i < activeSideEffects.length; i += 1)
+ {
+ nextPackage = activeSideEffects[i].GetPackage();
+ if (nextPackage.Compare(packageName, SCASE_INSENSITIVE))
+ {
+ activeSideEffects[i].NewRef();
+ result[result.length] = activeSideEffects[i];
+ }
+ _.memory.Free(nextPackage);
+ }
+ return result;
+}
+
+/**
+ * Registers a new `SideEffect` object as active.
+ *
+ * @param newSideEffect Instance of some `SideEffect` class to register as
+ * active side effect. Must not be `none`.
+ * @return `true` if new side effect was added and `false` otherwise.
+ */
+public final function bool AddSideEffect(SideEffect newSideEffect)
+{
+ local int i;
+
+ if (newSideEffect == none) {
+ return false;
+ }
+ for (i = 0; i < activeSideEffects.length; i += 1)
+ {
+ if (activeSideEffects[i].class == newSideEffect.class) {
+ return false;
+ }
+ }
+ newSideEffect.NewRef();
+ activeSideEffects[activeSideEffects.length] = newSideEffect;
+ return true;
+}
+
+/**
+ * Removes `SideEffect` of the specified sub-class from the list of active
+ * side effects.
+ *
+ * @param sideEffectClass Class of the side effect to remove.
+ * @return `true` if some side effect was removed as a result of this operation
+ * and `false` otherise (even if there was no side effect of specified
+ * class to begin with).
+ */
+public final function bool RemoveSideEffectClass(
+ class sideEffectClass)
+{
+ local int i;
+
+ if (sideEffectClass == none) {
+ return false;
+ }
+ for (i = 0; i < activeSideEffects.length; i += 1)
+ {
+ if (activeSideEffects[i].class == sideEffectClass)
+ {
+ _.memory.Free(activeSideEffects[i]);
+ activeSideEffects.Remove(i, 1);
+ return true;
+ }
+ }
+ return false;
+}
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/CoreRealm/CoreGlobal.uc b/sources/CoreRealm/CoreGlobal.uc
new file mode 100644
index 0000000..ffc8709
--- /dev/null
+++ b/sources/CoreRealm/CoreGlobal.uc
@@ -0,0 +1,36 @@
+/**
+ * Base class for objects that will provide an access to a Acedia's client- and
+ * server-specific functionality by giving a reference to this object to all
+ * Acedia's objects and actors, emulating a global API namespace.
+ * Copyright 2022 Anton Tarasenko
+ *------------------------------------------------------------------------------
+ * This file is part of Acedia.
+ *
+ * Acedia is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Acedia is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Acedia. If not, see .
+ */
+class CoreGlobal extends Object;
+
+var public SideEffectAPI sideEffects;
+
+protected function Initialize()
+{
+ local MemoryAPI api;
+
+ api = class'Global'.static.GetInstance().memory;
+ sideEffects = SideEffectAPI(api.Allocate(class'SideEffectAPI'));
+}
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/ServerRealm/ServerGlobal.uc b/sources/ServerRealm/ServerGlobal.uc
index 0bdd8cc..3cce56e 100644
--- a/sources/ServerRealm/ServerGlobal.uc
+++ b/sources/ServerRealm/ServerGlobal.uc
@@ -19,7 +19,7 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see .
*/
-class ServerGlobal extends Object;
+class ServerGlobal extends CoreGlobal;
var protected bool initialized;
// `Global` is expected to behave like a singleton and will store it's
@@ -46,6 +46,7 @@ protected function Initialize()
if (initialized) {
return;
}
+ super.Initialize();
_ = class'Global'.static.GetInstance();
kf = KFFrontend(_.memory.Allocate(class'KF1_Frontend'));
// TODO: this is hack, needs to be redone later