Change file structure of Acedia

Improves grouping of some files in project's directories.
This commit is contained in:
Anton Tarasenko 2020-04-09 14:43:45 +07:00
commit 97569f9568
21 changed files with 0 additions and 0 deletions

141
sources/Core/Acedia.uc Normal file
View file

@ -0,0 +1,141 @@
/**
* Main and only Acedia mutator used for initialization of necessary services
* and providing access to mutator events' calls.
* Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
class Acedia extends Mutator
config(Acedia);
// Default value of this variable will be used to store
// reference to the active Acedia mutator,
// as well as to ensure there's only one copy of it.
// We can't use 'Singleton' class for that,
// as we have to derive from 'Mutator'.
var private Acedia selfReference;
// Array of predefined services that must be started along with Acedia mutator.
var private array< class<Service> > systemServices;
// All unit tests loaded from all packages.
var private array< class<TestCase> > testCases;
static public final function Acedia GetInstance()
{
return default.selfReference;
}
event PreBeginPlay()
{
// Enforce one copy rule and remember a reference to that copy
if (default.selfReference != none)
{
Destroy();
return;
}
default.selfReference = self;
// Boot up Acedia
Spawn(class'Global');
LoadManifest(class'Manifest');
LaunchServices();
InjectBroadcastHandler(); // TODO: move this to 'SideEffect' mechanic
}
private final function LoadManifest(class<Manifest> manifestClass)
{
local int i;
// Activate manifest's listeners
for (i = 0; i < manifestClass.default.requiredListeners.length; i += 1)
{
if (manifestClass.default.requiredListeners[i] == none) continue;
manifestClass.default.requiredListeners[i].static.SetActive(true);
}
// Enable features
for (i = 0; i < manifestClass.default.features.length; i += 1)
{
if (manifestClass.default.features[i] == none) continue;
if (manifestClass.default.features[i].static.IsAutoEnabled())
{
manifestClass.default.features[i].static.EnableMe();
}
}
// Load unit tests
for (i = 0; i < manifestClass.default.testCases.length; i += 1)
{
if (manifestClass.default.testCases[i] == none) continue;
testCases[testCases.length] = manifestClass.default.testCases[i];
}
}
private final function LaunchServices()
{
local int i;
for (i = 0; i < systemServices.length; i += 1)
{
if (systemServices[i] == none) continue;
Spawn(systemServices[i]);
}
}
private final function InjectBroadcastHandler()
{
local BroadcastHandler ourBroadcastHandler;
if (level == none || level.game == none) return;
ourBroadcastHandler = Spawn(class'BroadcastHandler');
// Swap out level's first handler with ours
// (needs to be done for both actor reference and it's class)
ourBroadcastHandler.nextBroadcastHandler = level.game.broadcastHandler;
ourBroadcastHandler.nextBroadcastHandlerClass = level.game.broadcastClass;
level.game.broadcastHandler = ourBroadcastHandler;
level.game.broadcastClass = class'BroadcastHandler';
}
// Acedia is only able to run in a server mode right now,
// so this function is just a stub.
public final function bool IsServerOnly()
{
return true;
}
// Provide a way to handle CheckReplacement event
function bool CheckReplacement(Actor other, out byte isSuperRelevant)
{
return class'MutatorEvents'.static.
CallCheckReplacement(other, isSuperRelevant);
}
function Mutate(string command, PlayerController sendingPlayer)
{
if (class'MutatorEvents'.static.CallMutate(command, sendingPlayer))
{
super.Mutate(command, sendingPlayer);
}
}
defaultproperties
{
// List built-in services
systemServices(0) = class'ConnectionService'
// This is a server-only mutator
remoteRole = ROLE_None
bAlwaysRelevant = true
// Mutator description
GroupName = "Core mutator"
FriendlyName = "Acedia"
Description = "Mutator for all your degenerate needs"
}

View file

@ -0,0 +1,40 @@
/**
* Actor base class to be used to Acedia instead of an `Actor`.
* The only difference is defined `_` member that provides convenient access to
* Acedia's API.
* It isn't guaranteed that `default._` will be defined for `AcediaActor`s.
* Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
class AcediaActor extends Actor
abstract;
var protected Global _;
event PreBeginPlay()
{
super.PreBeginPlay();
if (_ == none)
{
_ = Global(class'Global'.static.GetInstance());
default._ = _;
}
}
defaultproperties
{
}

View file

@ -0,0 +1,35 @@
/**
* Object base class to be used to Acedia instead of an `Object`.
* The only difference is defined `_` member that provides convenient access to
* Acedia's API.
* Since `Global` is an actor, we wish to avoid storing it's instance in
* the object because it can mess with garbage collection on level change.
* So we provide an accessor function `_()` instead.
* Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
class AcediaObject extends Object
abstract;
public static final function Global _()
{
return Global(class'Global'.static.GetInstance());
}
defaultproperties
{
}

View file

@ -0,0 +1,32 @@
/**
* Facilitates some core replicated functions between client and server.
* Copyright 2019 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 <https://www.gnu.org/licenses/>.
*/
class AcediaReplicationInfo extends ReplicationInfo;
var public PlayerController linkOwner;
replication
{
reliable if (role == ROLE_Authority)
linkOwner;
}
defaultproperties
{
}

View file

@ -0,0 +1,351 @@
/**
* This class implements JSON array storage capabilities.
* Array stores ordered JSON values that can be referred by their index.
* It can contain any mix of JSON value types and cannot have any gaps,
* i.e. in array of length N, there must be a valid value for all indices
* from 0 to N-1.
* Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
class JArray extends JSON;
// Data will simply be stored as an array of JSON values
var private array<JStorageAtom> data;
// Return type of value stored at a given index.
// Returns `JSON_Undefined` if and only if given index is out of bounds.
public final function JType GetTypeOf(int index)
{
if (index < 0) return JSON_Undefined;
if (index >= data.length) return JSON_Undefined;
return data[index].type;
}
// Returns current length of this array.
public final function int GetLength()
{
return data.length;
}
// Changes length of this array.
// In case of the increase - fills new indices with `null` values.
public final function SetLength(int newLength)
{
local int i;
local int oldLength;
oldLength = data.length;
data.length = newLength;
if (oldLength >= newLength)
{
return;
}
i = oldLength;
while (i < newLength)
{
SetNull(i);
i += 1;
}
}
// Following functions are getters for various types of variables.
// Getter for null value simply checks if it's null
// and returns true/false as a result.
// Getters for simple types (number, string, boolean) can have optional
// default value specified, that will be returned if requested variable
// doesn't exist or has a different type.
// Getters for object and array types don't take default values and
// will simply return `none`.
public final function float GetNumber(int index, optional float defaultValue)
{
if (index < 0) return defaultValue;
if (index >= data.length) return defaultValue;
if (data[index].type != JSON_Number) return defaultValue;
return data[index].numberValue;
}
public final function string GetString(int index, optional string defaultValue)
{
if (index < 0) return defaultValue;
if (index >= data.length) return defaultValue;
if (data[index].type != JSON_String) return defaultValue;
return data[index].stringValue;
}
public final function bool GetBoolean(int index, optional bool defaultValue)
{
if (index < 0) return defaultValue;
if (index >= data.length) return defaultValue;
if (data[index].type != JSON_Boolean) return defaultValue;
return data[index].booleanValue;
}
public final function bool IsNull(int index)
{
if (index < 0) return false;
if (index >= data.length) return false;
return (data[index].type == JSON_Null);
}
public final function JArray GetArray(int index)
{
if (index < 0) return none;
if (index >= data.length) return none;
if (data[index].type != JSON_Array) return none;
return JArray(data[index].complexValue);
}
public final function JObject GetObject(int index)
{
if (index < 0) return none;
if (index >= data.length) return none;
if (data[index].type != JSON_Object) return none;
return JObject(data[index].complexValue);
}
// Following functions provide simple setters for boolean, string, number
// and null values.
// If passed index is negative - does nothing.
// If index lies beyond array length (`>= GetLength()`), -
// these functions will expand array in the same way as `GetLength()` function.
// This can be prevented by setting optional parameter `preventExpansion` to
// `false` (nothing will be done in this case).
// They return object itself, allowing user to chain calls like this:
// `array.SetNumber("num1", 1).SetNumber("num2", 2);`.
public final function JArray SetNumber
(
int index,
float value,
optional bool preventExpansion
)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
}
newStorageValue.type = JSON_Number;
newStorageValue.numberValue = value;
data[index] = newStorageValue;
return self;
}
public final function JArray SetString
(
int index,
string value,
optional bool preventExpansion
)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
}
newStorageValue.type = JSON_String;
newStorageValue.stringValue = value;
data[index] = newStorageValue;
return self;
}
public final function JArray SetBoolean
(
int index,
bool value,
optional bool preventExpansion
)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
}
newStorageValue.type = JSON_Boolean;
newStorageValue.booleanValue = value;
data[index] = newStorageValue;
return self;
}
public final function JArray SetNull
(
int index,
optional bool preventExpansion
)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
}
newStorageValue.type = JSON_Null;
data[index] = newStorageValue;
return self;
}
// JSON array and object types don't have setters, but instead have
// functions to create a new, empty array/object under a certain name.
// If passed index is negative - does nothing.
// If index lies beyond array length (`>= GetLength()`), -
// these functions will expand array in the same way as `GetLength()` function.
// This can be prevented by setting optional parameter `preventExpansion` to
// `false` (nothing will be done in this case).
// They return object itself, allowing user to chain calls like this:
// `array.CreateObject("sub object").CreateArray("sub array");`.
public final function JArray CreateArray
(
int index,
optional bool preventExpansion
)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
}
newStorageValue.type = JSON_Array;
newStorageValue.complexValue = _.json.newArray();
data[index] = newStorageValue;
return self;
}
public final function JArray CreateObject
(
int index,
optional bool preventExpansion
)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
}
newStorageValue.type = JSON_Object;
newStorageValue.complexValue = _.json.newObject();
data[index] = newStorageValue;
return self;
}
// Wrappers for setter functions that don't take index or
// `preventExpansion` parameters and add/create value at the end of the array.
public final function JArray AddNumber(float value)
{
return SetNumber(data.length, value);
}
public final function JArray AddString(string value)
{
return SetString(data.length, value);
}
public final function JArray AddBoolean(bool value)
{
return SetBoolean(data.length, value);
}
public final function JArray AddNull()
{
return SetNull(data.length);
}
public final function JArray AddArray()
{
return CreateArray(data.length);
}
public final function JArray AddObject()
{
return CreateObject(data.length);
}
// Removes up to `amount` (minimum of `1`) of values, starting from
// a given index.
// If `index` falls outside array boundaries - nothing will be done.
// Returns `true` if value was actually removed and `false` if it didn't exist.
public final function bool RemoveValue(int index, optional int amount)
{
if (index < 0) return false;
if (index >= data.length) return false;
amount = Max(amount, 1);
amount = Min(amount, data.length - index);
data.Remove(index, amount);
return true;
}
defaultproperties
{
}

View file

@ -0,0 +1,265 @@
/**
* This class implements JSON object storage capabilities.
* Whenever one wants to store JSON data, they need to define such object.
* It stores name-value pairs, where names are strings and values can be:
* ~ Boolean, string, null or number (float in this implementation) data;
* ~ Other JSON objects;
* ~ JSON Arrays (see `JArray` class).
*
* This implementation provides getters and setters for boolean, string,
* null or number types that allow to freely set and fetch their values
* by name.
* JSON objects and arrays can be fetched by getters, but you cannot
* add existing object or array to another object. Instead one has to create
* a new, empty object with a certain name and then fill it with data.
* This allows to avoid loop situations, where object is contained in itself.
* Functions to remove existing values are also provided and are applicable
* to all variable types.
* Setters can also be used to overwrite any value by a different value,
* even of a different type.
* Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
class JObject extends JSON;
// We will store all our properties as a simple array of name-value pairs.
struct JProperty
{
var string name;
var JStorageAtom value;
};
var private array<JProperty> properties;
// Returns index of name-value pair in `properties` for a given name.
// Returns `-1` if such a pair does not exist.
private final function int GetPropertyIndex(string name)
{
local int i;
for (i = 0; i < properties.length; i += 1)
{
if (name == properties[i].name)
{
return i;
}
}
return -1;
}
// Returns `JType` of a variable with a given name in our properties.
// This function can be used to check if certain variable exists
// in this object, since if such variable does not exist -
// function will return `JSON_Undefined`.
public final function JType GetTypeOf(string name)
{
local int index;
index = GetPropertyIndex(name);
if (index < 0) return JSON_Undefined;
return properties[index].value.type;
}
// Following functions are getters for various types of variables.
// Getter for null value simply checks if it's null
// and returns true/false as a result.
// Getters for simple types (number, string, boolean) can have optional
// default value specified, that will be returned if requested variable
// doesn't exist or has a different type.
// Getters for object and array types don't take default values and
// will simply return `none`.
public final function float GetNumber(string name, optional float defaultValue)
{
local int index;
index = GetPropertyIndex(name);
if (index < 0) return defaultValue;
if (properties[index].value.type != JSON_Number) return defaultValue;
return properties[index].value.numberValue;
}
public final function string GetString
(
string name,
optional string defaultValue
)
{
local int index;
index = GetPropertyIndex(name);
if (index < 0) return defaultValue;
if (properties[index].value.type != JSON_String) return defaultValue;
return properties[index].value.stringValue;
}
public final function bool GetBoolean(string name, optional bool defaultValue)
{
local int index;
index = GetPropertyIndex(name);
if (index < 0) return defaultValue;
if (properties[index].value.type != JSON_Boolean) return defaultValue;
return properties[index].value.booleanValue;
}
public final function bool IsNull(string name)
{
local int index;
index = GetPropertyIndex(name);
if (index < 0) return false;
if (properties[index].value.type != JSON_Null) return false;
return (properties[index].value.type == JSON_Null);
}
public final function JArray GetArray(string name)
{
local int index;
index = GetPropertyIndex(name);
if (index < 0) return none;
if (properties[index].value.type != JSON_Array) return none;
return JArray(properties[index].value.complexValue);
}
public final function JObject GetObject(string name)
{
local int index;
index = GetPropertyIndex(name);
if (index < 0) return none;
if (properties[index].value.type != JSON_Object) return none;
return JObject(properties[index].value.complexValue);
}
// Following functions provide simple setters for boolean, string, number
// and null values.
// They return object itself, allowing user to chain calls like this:
// `object.SetNumber("num1", 1).SetNumber("num2", 2);`.
public final function JObject SetNumber(string name, float value)
{
local int index;
local JProperty newProperty;
index = GetPropertyIndex(name);
if (index < 0)
{
index = properties.length;
}
newProperty.name = name;
newProperty.value.type = JSON_Number;
newProperty.value.numberValue = value;
properties[index] = newProperty;
return self;
}
public final function JObject SetString(string name, string value)
{
local int index;
local JProperty newProperty;
index = GetPropertyIndex(name);
if (index < 0)
{
index = properties.length;
}
newProperty.name = name;
newProperty.value.type = JSON_String;
newProperty.value.stringValue = value;
properties[index] = newProperty;
return self;
}
public final function JObject SetBoolean(string name, bool value)
{
local int index;
local JProperty newProperty;
index = GetPropertyIndex(name);
if (index < 0)
{
index = properties.length;
}
newProperty.name = name;
newProperty.value.type = JSON_Boolean;
newProperty.value.booleanValue = value;
properties[index] = newProperty;
return self;
}
public final function JObject SetNull(string name)
{
local int index;
local JProperty newProperty;
index = GetPropertyIndex(name);
if (index < 0)
{
index = properties.length;
}
newProperty.name = name;
newProperty.value.type = JSON_Null;
properties[index] = newProperty;
return self;
}
// JSON array and object types don't have setters, but instead have
// functions to create a new, empty array/object under a certain name.
// They return object itself, allowing user to chain calls like this:
// `object.CreateObject("folded object").CreateArray("names list");`.
public final function JObject CreateArray(string name)
{
local int index;
local JProperty newProperty;
index = GetPropertyIndex(name);
if (index < 0)
{
index = properties.length;
}
newProperty.name = name;
newProperty.value.type = JSON_Array;
newProperty.value.complexValue = _.json.newArray();
properties[index] = newProperty;
return self;
}
public final function JObject CreateObject(string name)
{
local int index;
local JProperty newProperty;
index = GetPropertyIndex(name);
if (index < 0)
{
index = properties.length;
}
newProperty.name = name;
newProperty.value.type = JSON_Object;
newProperty.value.complexValue = _.json.newObject();
properties[index] = newProperty;
return self;
}
// Removes values with a given name.
// Returns `true` if value was actually removed and `false` if it didn't exist.
public final function bool RemoveValue(string name)
{
local int index;
index = GetPropertyIndex(name);
if (index < 0) return false;
properties.Remove(index, 1);
return true;
}
defaultproperties
{
}

View file

@ -0,0 +1,84 @@
/**
* JSON is an open standard file format, and data interchange format,
* that uses human-readable text to store and transmit data objects
* consisting of namevalue pairs and array data types.
* For more information refer to https://en.wikipedia.org/wiki/JSON
* This is a base class for implementation of JSON data storage for Acedia.
* It does not implement parsing and printing from/into human-readable
* text representation, just provides means to store such information.
*
* JSON data is stored as an object (represented via `JSONObject`) that
* contains a set of name-value pairs, where value can be
* a number, string, boolean value, another object or
* an array (represented by `JSONArray`).
* Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
class JSON extends AcediaActor
abstract;
// Enumeration for possible types of JSON values.
enum JType
{
// Technical type, used to indicate that requested value is missing.
// Undefined values are not part of JSON format.
JSON_Undefined,
// An empty value, in teste representation defined by a single word "null".
JSON_Null,
// A number, recorded as a float.
// JSON itself doesn't specify whether number is an integer or float.
JSON_Number,
// A string.
JSON_String,
// A bool value.
JSON_Boolean,
// Array of other JSON values, stored without names;
// Single array can contain any mix of value types.
JSON_Array,
// Another JSON object, i.e. associative array of name-value pairs
JSON_Object
};
// Stores a single JSON value
struct JStorageAtom
{
// What type is stored exactly?
// Depending on that, uses one of the other fields as a storage.
var protected JType type;
var protected float numberValue;
var protected string stringValue;
var protected bool booleanValue;
// Used for storing both JSON objects and arrays.
var protected JSON complexValue;
};
// TODO: Rewrite JSON object to use more efficient storage data structures
// that will support subtypes:
// ~ Number: byte, int, float
// ~ String: string, class
// (maybe move to auto generated code?).
// TODO: Add cleanup queue to efficiently and without crashes clean up
// removed objects.
// TODO: Add `JValue` - a reference type for number / string / boolean / null
// TODO: Add accessors for last values.
// TODO: Add path-getters.
// TODO: Add iterators.
// TODO: Add parsing/printing.
// TODO: Add functions for deep copy.
defaultproperties
{
}

View file

@ -0,0 +1,41 @@
/**
* Singleton is an auxiliary class, meant to be used as a base for others,
* that allows for only one instance of it to exist.
* To make sure your child class properly works, either don't overload
* 'PreBeginPlay' or make sure to call it's parent's version.
* Copyright 2019 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 <https://www.gnu.org/licenses/>.
*/
class JSONAPI extends Singleton;
public function JObject newObject()
{
local JObject newObject;
newObject = Spawn(class'JObject');
return newObject;
}
public function JArray newArray()
{
local JArray newArray;
newArray = Spawn(class'JArray');
return newArray;
}
defaultproperties
{
}

View file

@ -0,0 +1,711 @@
/**
* Set of tests for JSON data storage, implemented via
* `JObject` and `JArray`.
* Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
class TEST_JSON extends TestCase
abstract;
protected static function TESTS()
{
local JObject jsonData;
jsonData = _().json.newObject();
Test_ObjectGetSetRemove();
Test_ArrayGetSetRemove();
}
protected static function Test_ObjectGetSetRemove()
{
SubTest_Undefined();
SubTest_StringGetSetRemove();
SubTest_BooleanGetSetRemove();
SubTest_NumberGetSetRemove();
SubTest_NullGetSetRemove();
SubTest_MultipleVariablesGetSet();
SubTest_Object();
}
protected static function Test_ArrayGetSetRemove()
{
Context("Testing get/set/remove functions for JSON arrays");
SubTest_ArrayUndefined();
SubTest_ArrayStringGetSetRemove();
SubTest_ArrayBooleanGetSetRemove();
SubTest_ArrayNumberGetSetRemove();
SubTest_ArrayNullGetSetRemove();
SubTest_ArrayMultipleVariablesStorage();
SubTest_ArrayMultipleVariablesRemoval();
SubTest_ArrayRemovingMultipleVariablesAtOnce();
SubTest_ArrayExpansions();
}
protected static function SubTest_Undefined()
{
local JObject testJSON;
testJSON = _().json.newObject();
Context("Testing how `JObject` handles undefined values");
Issue("Undefined variable doesn't have proper type.");
TEST_ExpectTrue(testJSON.GetTypeOf("some_var") == JSON_Undefined);
Issue("There is a variable in an empty object after `GetTypeOf` call.");
TEST_ExpectTrue(testJSON.GetTypeOf("some_var") == JSON_Undefined);
Issue("Getters don't return default values for undefined variables.");
TEST_ExpectTrue(testJSON.GetNumber("some_var", 0) == 0);
TEST_ExpectTrue(testJSON.GetString("some_var", "") == "");
TEST_ExpectTrue(testJSON.GetBoolean("some_var", false) == false);
TEST_ExpectNone(testJSON.GetObject("some_var"));
TEST_ExpectNone(testJSON.GetArray("some_var"));
}
protected static function SubTest_BooleanGetSetRemove()
{
local JObject testJSON;
testJSON = _().json.newObject();
testJSON.SetBoolean("some_boolean", true);
Context("Testing `JObject`'s get/set/remove functions for" @
"boolean variables");
Issue("Boolean type isn't properly set by `SetBoolean`");
TEST_ExpectTrue(testJSON.GetTypeOf("some_boolean") == JSON_Boolean);
Issue("Variable value is incorrectly assigned by `SetBoolean`");
TEST_ExpectTrue(testJSON.GetBoolean("some_boolean") == true);
Issue("Variable value isn't correctly reassigned by `SetBoolean`");
testJSON.SetBoolean("some_boolean", false);
TEST_ExpectTrue(testJSON.GetBoolean("some_boolean") == false);
Issue( "Getting boolean variable as a wrong type" @
"doesn't yield default value");
TEST_ExpectTrue(testJSON.GetNumber("some_boolean", 7) == 7);
Issue("Boolean variable isn't being properly removed");
testJSON.RemoveValue("some_boolean");
TEST_ExpectTrue(testJSON.GetTypeOf("some_boolean") == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored boolean value, that got removed");
TEST_ExpectTrue(testJSON.GetBoolean("some_boolean", true) == true);
}
protected static function SubTest_StringGetSetRemove()
{
local JObject testJSON;
testJSON = _().json.newObject();
testJSON.SetString("some_string", "first string");
Context("Testing `JObject`'s get/set/remove functions for" @
"string variables");
Issue("String type isn't properly set by `SetString`");
TEST_ExpectTrue(testJSON.GetTypeOf("some_string") == JSON_String);
Issue("Value is incorrectly assigned by `SetString`");
TEST_ExpectTrue(testJSON.GetString("some_string") == "first string");
Issue( "Providing default variable value makes 'GetString'" @
"return wrong value");
TEST_ExpectTrue( testJSON.GetString("some_string", "alternative")
== "first string");
Issue("Variable value isn't correctly reassigned by `SetString`");
testJSON.SetString("some_string", "new string!~");
TEST_ExpectTrue(testJSON.GetString("some_string") == "new string!~");
Issue( "Getting string variable as a wrong type" @
"doesn't yield default value");
TEST_ExpectTrue(testJSON.GetBoolean("some_string", true) == true);
Issue("String variable isn't being properly removed");
testJSON.RemoveValue("some_string");
TEST_ExpectTrue(testJSON.GetTypeOf("some_string") == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored string value, but got removed");
TEST_ExpectTrue(testJSON.GetString("some_string", "other") == "other");
}
protected static function SubTest_NumberGetSetRemove()
{
local JObject testJSON;
testJSON = _().json.newObject();
testJSON.SetNumber("some_number", 3.5);
Context("Testing `JObject`'s get/set/remove functions for" @
"number variables");
Issue("Number type isn't properly set by `SetNumber`");
TEST_ExpectTrue(testJSON.GetTypeOf("some_number") == JSON_Number);
Issue("Value is incorrectly assigned by `SetNumber`");
TEST_ExpectTrue(testJSON.GetNumber("some_number") == 3.5);
Issue( "Providing default variable value makes 'GetNumber'" @
"return wrong value");
TEST_ExpectTrue(testJSON.GetNumber("some_number", 5) == 3.5);
Issue("Variable value isn't correctly reassigned by `SetNumber`");
testJSON.SetNumber("some_number", 7);
TEST_ExpectTrue(testJSON.GetNumber("some_number") == 7);
Issue( "Getting number variable as a wrong type" @
"doesn't yield default value.");
TEST_ExpectTrue(testJSON.GetString("some_number", "default") == "default");
Issue("Number type isn't being properly removed");
testJSON.RemoveValue("some_number");
TEST_ExpectTrue(testJSON.GetTypeOf("some_number") == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored number value, that got removed");
TEST_ExpectTrue(testJSON.GetNumber("some_number", 13) == 13);
}
protected static function SubTest_NullGetSetRemove()
{
local JObject testJSON;
testJSON = _().json.newObject();
Context("Testing `JObject`'s get/set/remove functions for" @
"null values");
Issue("Undefined variable is incorrectly considered `null`");
TEST_ExpectFalse(testJSON.IsNull("some_var"));
Issue("Number variable is incorrectly considered `null`");
testJSON.SetNumber("some_var", 4);
TEST_ExpectFalse(testJSON.IsNull("some_var"));
Issue("Boolean variable is incorrectly considered `null`");
testJSON.SetBoolean("some_var", true);
TEST_ExpectFalse(testJSON.IsNull("some_var"));
Issue("String variable is incorrectly considered `null`");
testJSON.SetString("some_var", "string");
TEST_ExpectFalse(testJSON.IsNull("some_var"));
Issue("Null value is incorrectly assigned");
testJSON.SetNull("some_var");
TEST_ExpectTrue(testJSON.IsNull("some_var"));
Issue("Null type isn't properly set by `SetNumber`");
TEST_ExpectTrue(testJSON.GetTypeOf("some_var") == JSON_Null);
Issue("Null value isn't being properly removed.");
testJSON.RemoveValue("some_var");
TEST_ExpectTrue(testJSON.GetTypeOf("some_var") == JSON_Undefined);
}
protected static function SubTest_MultipleVariablesGetSet()
{
local int i;
local bool correctValue, allValuesCorrect;
local JObject testJSON;
testJSON = _().json.newObject();
Context("Testing how `JObject` handles addition, change and removal" @
"of relatively large (hundreds) number of variables");
for (i = 0; i < 2000; i += 1)
{
testJSON.SetNumber("num" $ string(i), 4 * i*i - 2.6 * i + 0.75);
}
for (i = 0; i < 500; i += 1)
{
testJSON.SetString("num" $ string(i), "str" $ string(Sin(i)));
}
for (i = 1500; i < 2000; i += 1)
{
testJSON.RemoveValue("num" $ string(i));
}
allValuesCorrect = true;
for (i = 0; i < 200; i += 1)
{
if (i < 500)
{
correctValue = ( testJSON.GetString("num" $ string(i))
== ("str" $ string(Sin(i))) );
Issue("Variables are incorrectly overwritten");
}
else if(i < 1500)
{
correctValue = ( testJSON.GetNumber("num" $ string(i))
== 4 * i*i - 2.6 * i + 0.75);
Issue("Variables are lost");
}
else
{
correctValue = ( testJSON.GetTypeOf("num" $ string(i))
== JSON_Undefined);
Issue("Variables aren't removed");
}
if (!correctValue)
{
allValuesCorrect = false;
break;
}
}
TEST_ExpectTrue(allValuesCorrect);
}
protected static function SubTest_Object()
{
local JObject testObject;
Context("Testing setters and getters for folded objects");
testObject = _().json.newObject();
testObject.CreateObject("folded");
testObject.GetObject("folded").CreateObject("folded");
testObject.SetString("out", "string outside");
testObject.GetObject("folded").SetNumber("mid", 8);
testObject.GetObject("folded")
.GetObject("folded")
.SetString("in", "string inside");
Issue("Addressing variables in root object doesn't work");
TEST_ExpectTrue(testObject.GetString("out", "default") == "string outside");
Issue("Addressing variables in folded object doesn't work");
TEST_ExpectTrue(testObject.GetObject("folded").GetNumber("mid", 1) == 8);
Issue("Addressing plain variables in folded (twice) object doesn't work");
TEST_ExpectTrue(testObject.GetObject("folded").GetObject("folded")
.GetString("in", "default") == "string inside");
}
protected static function SubTest_ArrayUndefined()
{
local JArray testJSON;
testJSON = _().json.newArray();
Context("Testing how `JArray` handles undefined values");
Issue("Undefined variable doesn't have `JSON_Undefined` type");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined);
Issue("There is a variable in an empty object after `GetTypeOf` call");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined);
Issue("Negative index refers to a defined value");
TEST_ExpectTrue(testJSON.GetTypeOf(-1) == JSON_Undefined);
Issue("Getters don't return default values for undefined variables");
TEST_ExpectTrue(testJSON.GetNumber(0, 0) == 0);
TEST_ExpectTrue(testJSON.GetString(0, "") == "");
TEST_ExpectTrue(testJSON.GetBoolean(0, false) == false);
TEST_ExpectNone(testJSON.GetObject(0));
TEST_ExpectNone(testJSON.GetArray(0));
Issue( "Getters don't return user-defined default values for" @
"undefined variables");
TEST_ExpectTrue(testJSON.GetNumber(0, 10) == 10);
TEST_ExpectTrue(testJSON.GetString(0, "test") == "test");
TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true);
}
protected static function SubTest_ArrayBooleanGetSetRemove()
{
local JArray testJSON;
testJSON = _().json.newArray();
testJSON.SetBoolean(0, true);
Context("Testing `JArray`'s get/set/remove functions for" @
"boolean variables");
Issue("Boolean type isn't properly set by `SetBoolean`");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Boolean);
Issue("Value is incorrectly assigned by `SetBoolean`");
TEST_ExpectTrue(testJSON.GetBoolean(0) == true);
testJSON.SetBoolean(0, false);
Issue("Variable value isn't correctly reassigned by `SetBoolean`");
TEST_ExpectTrue(testJSON.GetBoolean(0) == false);
Issue( "Getting boolean variable as a wrong type" @
"doesn't yield default value");
TEST_ExpectTrue(testJSON.GetNumber(0, 7) == 7);
Issue("Boolean variable isn't being properly removed");
testJSON.RemoveValue(0);
TEST_ExpectTrue( testJSON.GetTypeOf(0) == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored boolean value, but got removed");
TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true);
}
protected static function SubTest_ArrayStringGetSetRemove()
{
local JArray testJSON;
testJSON = _().json.newArray();
testJSON.SetString(0, "first string");
Context("Testing `JArray`'s get/set/remove functions for" @
"string variables");
Issue("String type isn't properly set by `SetString`");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_String);
Issue("Value is incorrectly assigned by `SetString`");
TEST_ExpectTrue(testJSON.GetString(0) == "first string");
Issue( "Providing default variable value makes 'GetString'" @
"return incorrect value");
TEST_ExpectTrue(testJSON.GetString(0, "alternative") == "first string");
Issue("Variable value isn't correctly reassigned by `SetString`");
testJSON.SetString(0, "new string!~");
TEST_ExpectTrue(testJSON.GetString(0) == "new string!~");
Issue( "Getting string variable as a wrong type" @
"doesn't yield default value");
TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true);
Issue("Boolean variable isn't being properly removed");
testJSON.RemoveValue(0);
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored string value, but got removed");
TEST_ExpectTrue(testJSON.GetString(0, "other") == "other");
}
protected static function SubTest_ArrayNumberGetSetRemove()
{
local JArray testJSON;
testJSON = _().json.newArray();
testJSON.SetNumber(0, 3.5);
Context("Testing `JArray`'s get/set/remove functions for" @
"number variables");
Issue("Number type isn't properly set by `SetNumber`");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Number);
Issue("Value is incorrectly assigned by `SetNumber`");
TEST_ExpectTrue(testJSON.GetNumber(0) == 3.5);
Issue( "Providing default variable value makes 'GetNumber'" @
"return incorrect value");
TEST_ExpectTrue(testJSON.GetNumber(0, 5) == 3.5);
Issue("Variable value isn't correctly reassigned by `SetNumber`");
testJSON.SetNumber(0, 7);
TEST_ExpectTrue(testJSON.GetNumber(0) == 7);
Issue( "Getting number variable as a wrong type" @
"doesn't yield default value");
TEST_ExpectTrue(testJSON.GetString(0, "default") == "default");
Issue("Number type isn't being properly removed");
testJSON.RemoveValue(0);
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored number value, but got removed");
TEST_ExpectTrue(testJSON.GetNumber(0, 13) == 13);
}
protected static function SubTest_ArrayNullGetSetRemove()
{
local JArray testJSON;
testJSON = _().json.newArray();
Context("Testing `JArray`'s get/set/remove functions for" @
"null values");
Issue("Undefined variable is incorrectly considered `null`");
TEST_ExpectFalse(testJSON.IsNull(0));
TEST_ExpectFalse(testJSON.IsNull(2));
TEST_ExpectFalse(testJSON.IsNull(-1));
Issue("Number variable is incorrectly considered `null`");
testJSON.SetNumber(0, 4);
TEST_ExpectFalse(testJSON.IsNull(0));
Issue("Boolean variable is incorrectly considered `null`");
testJSON.SetBoolean(0, true);
TEST_ExpectFalse(testJSON.IsNull(0));
Issue("String variable is incorrectly considered `null`");
testJSON.SetString(0, "string");
TEST_ExpectFalse(testJSON.IsNull(0));
Issue("Null value is incorrectly assigned");
testJSON.SetNull(0);
TEST_ExpectTrue(testJSON.IsNull(0));
Issue("Null type isn't properly set by `SetNumber`");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Null);
Issue("Null value isn't being properly removed");
testJSON.RemoveValue(0);
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined);
}
// Returns following array:
// [10.0, "test string", "another string", true, 0.0, {"var": 7.0}]
protected static function JArray Prepare_Array()
{
local JArray testArray;
testArray = _().json.newArray();
testArray.AddNumber(10.0f)
.AddString("test string")
.AddString("another string")
.AddBoolean(true)
.AddNumber(0.0f)
.AddObject();
testArray.GetObject(5).SetNumber("var", 7);
return testArray;
}
protected static function SubTest_ArrayMultipleVariablesStorage()
{
local JArray testArray;
testArray = Prepare_Array();
Context("Testing how `JArray` handles adding and" @
"changing several variables");
Issue("Stored values are compromised.");
TEST_ExpectTrue(testArray.GetNumber(0) == 10.0f);
TEST_ExpectTrue(testArray.GetString(1) == "test string");
TEST_ExpectTrue(testArray.GetString(2) == "another string");
TEST_ExpectTrue(testArray.GetBoolean(3) == true);
TEST_ExpectTrue(testArray.GetNumber(4) == 0.0f);
TEST_ExpectTrue(testArray.GetObject(5).GetNumber("var") == 7);
Issue("Values incorrectly change their values.");
testArray.SetString(3, "new string");
TEST_ExpectTrue(testArray.GetString(3) == "new string");
Issue( "After overwriting boolean value with a different type," @
"attempting go get it as a boolean gives old value," @
"instead of default");
TEST_ExpectTrue(testArray.GetBoolean(3, false) == false);
Issue("Type of the variable is incorrectly changed.");
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_String);
}
protected static function SubTest_ArrayMultipleVariablesRemoval()
{
local JArray testArray;
testArray = Prepare_Array();
// Test removing variables
// After `Prepare_Array`, our array should be:
// [10.0, "test string", "another string", true, 0.0, {"var": 7.0}]
Context("Testing how `JArray` handles adding and" @
"removing several variables");
Issue("Values are incorrectly removed");
testArray.RemoveValue(2);
// [10.0, "test string", true, 0.0, {"var": 7.0}]
Issue("Values are incorrectly removed");
TEST_ExpectTrue(testArray.GetNumber(0) == 10.0);
TEST_ExpectTrue(testArray.GetString(1) == "test string");
TEST_ExpectTrue(testArray.GetBoolean(2) == true);
TEST_ExpectTrue(testArray.GetNumber(3) == 0.0f);
TEST_ExpectTrue(testArray.GetTypeOf(4) == JSON_Object);
Issue("First element incorrectly removed");
testArray.RemoveValue(0);
// ["test string", true, 0.0, {"var": 7.0}]
TEST_ExpectTrue(testArray.GetString(0) == "test string");
TEST_ExpectTrue(testArray.GetBoolean(1) == true);
TEST_ExpectTrue(testArray.GetNumber(2) == 0.0f);
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Object);
TEST_ExpectTrue(testArray.GetObject(3).GetNumber("var") == 7.0);
Issue("Last element incorrectly removed");
testArray.RemoveValue(3);
// ["test string", true, 0.0]
TEST_ExpectTrue(testArray.GetLength() == 3);
TEST_ExpectTrue(testArray.GetString(0) == "test string");
TEST_ExpectTrue(testArray.GetBoolean(1) == true);
TEST_ExpectTrue(testArray.GetNumber(2) == 0.0f);
Issue("Removing all elements is handled incorrectly");
testArray.RemoveValue(0);
testArray.RemoveValue(0);
testArray.RemoveValue(0);
TEST_ExpectTrue(testArray.Getlength() == 0);
TEST_ExpectTrue(testArray.GetTypeOf(0) == JSON_Undefined);
}
protected static function SubTest_ArrayRemovingMultipleVariablesAtOnce()
{
local JArray testArray;
testArray = _().json.newArray();
testArray.AddNumber(10.0f)
.AddString("test string")
.AddString("another string")
.AddNumber(7.0);
Context("Testing how `JArray`' handles removing" @
"multiple elements at once");
Issue("Multiple values are incorrectly removed");
testArray.RemoveValue(1, 2);
TEST_ExpectTrue(testArray.GetLength() == 2);
TEST_ExpectTrue(testArray.GetNumber(1) == 7.0);
testArray.AddNumber(4.0f)
.AddString("test string")
.AddString("another string")
.AddNumber(8.0);
// Current array:
// [10.0, 7.0, 4.0, "test string", "another string", 8.0]
Issue("Last value is incorrectly removed");
testArray.RemoveValue(5, 1);
TEST_ExpectTrue(testArray.GetLength() == 5);
TEST_ExpectTrue(testArray.GetString(4) == "another string");
// Current array:
// [10.0, 7.0, 4.0, "test string", "another string"]
Issue("Tail elements are incorrectly removed");
testArray.RemoveValue(3, 4);
TEST_ExpectTrue(testArray.GetLength() == 3);
TEST_ExpectTrue(testArray.GetNumber(0) == 10.0);
TEST_ExpectTrue(testArray.GetNumber(2) == 4.0);
Issue("Array empties incorrectly");
testArray.RemoveValue(0, testArray.GetLength());
TEST_ExpectTrue(testArray.GetLength() == 0);
TEST_ExpectTrue(testArray.GetTypeOf(0) == JSON_Undefined);
TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Undefined);
}
protected static function SubTest_ArrayExpansions()
{
local JArray testArray;
testArray = _().json.newArray();
Context("Testing how `JArray`' handles expansions/shrinking " @
"via `SetLength()`");
Issue("`SetLength()` doesn't properly expand empty array");
testArray.SetLength(2);
TEST_ExpectTrue(testArray.GetLength() == 2);
TEST_ExpectTrue(testArray.GetTypeOf(0) == JSON_Null);
TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Null);
Issue("`SetLength()` doesn't properly expand non-empty array");
testArray.AddNumber(1);
testArray.SetLength(4);
TEST_ExpectTrue(testArray.GetLength() == 4);
TEST_ExpectTrue(testArray.GetTypeOf(0) == JSON_Null);
TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Null);
TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Number);
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null);
TEST_ExpectTrue(testArray.GetNumber(2) == 1);
SubSubTest_ArraySetNumberExpansions();
SubSubTest_ArraySetStringExpansions();
SubSubTest_ArraySetBooleanExpansions();
}
protected static function SubSubTest_ArraySetNumberExpansions()
{
local JArray testArray;
testArray = _().json.newArray();
Context("Testing how `JArray`' handles expansions via" @
"`SetNumber()` function");
Issue("Setters don't create correct first element");
testArray.SetNumber(0, 1);
TEST_ExpectTrue(testArray.GetLength() == 1);
TEST_ExpectTrue(testArray.GetNumber(0) == 1);
Issue( "`SetNumber()` doesn't properly define array when setting" @
"value out-of-bounds");
testArray = _().json.newArray();
testArray.AddNumber(1);
testArray.SetNumber(4, 2);
TEST_ExpectTrue(testArray.GetLength() == 5);
TEST_ExpectTrue(testArray.GetNumber(0) == 1);
TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Null);
TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Null);
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null);
TEST_ExpectTrue(testArray.GetNumber(4) == 2);
Issue("`SetNumber()` expands array even when it told not to");
testArray.SetNumber(6, 7, true);
TEST_ExpectTrue(testArray.GetLength() == 5);
TEST_ExpectTrue(testArray.GetNumber(6) == 0);
TEST_ExpectTrue(testArray.GetTypeOf(5) == JSON_Undefined);
TEST_ExpectTrue(testArray.GetTypeOf(6) == JSON_Undefined);
}
protected static function SubSubTest_ArraySetStringExpansions()
{
local JArray testArray;
testArray = _().json.newArray();
Context("Testing how `JArray`' handles expansions via" @
"`SetString()` function");
Issue("Setters don't create correct first element");
testArray.SetString(0, "str");
TEST_ExpectTrue(testArray.GetLength() == 1);
TEST_ExpectTrue(testArray.GetString(0) == "str");
Issue( "`SetString()` doesn't properly define array when setting" @
"value out-of-bounds");
testArray = _().json.newArray();
testArray.AddString("str");
testArray.SetString(4, "str2");
TEST_ExpectTrue(testArray.GetLength() == 5);
TEST_ExpectTrue(testArray.GetString(0) == "str");
TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Null);
TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Null);
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null);
TEST_ExpectTrue(testArray.GetString(4) == "str2");
Issue("`SetString()` expands array even when it told not to");
testArray.SetString(6, "new string", true);
TEST_ExpectTrue(testArray.GetLength() == 5);
TEST_ExpectTrue(testArray.GetString(6) == "");
TEST_ExpectTrue(testArray.GetTypeOf(5) == JSON_Undefined);
TEST_ExpectTrue(testArray.GetTypeOf(6) == JSON_Undefined);
}
protected static function SubSubTest_ArraySetBooleanExpansions()
{
local JArray testArray;
testArray = _().json.newArray();
Context("Testing how `JArray`' handles expansions via" @
"`SetBoolean()` function");
Issue("Setters don't create correct first element");
testArray.SetBoolean(0, false);
TEST_ExpectTrue(testArray.GetLength() == 1);
TEST_ExpectTrue(testArray.GetBoolean(0) == false);
Issue( "`SetBoolean()` doesn't properly define array when setting" @
"value out-of-bounds");
testArray = _().json.newArray();
testArray.AddBoolean(true);
testArray.SetBoolean(4, true);
TEST_ExpectTrue(testArray.GetLength() == 5);
TEST_ExpectTrue(testArray.GetBoolean(0) == true);
TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Null);
TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Null);
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null);
TEST_ExpectTrue(testArray.GetBoolean(4) == true);
Issue("`SetBoolean()` expands array even when it told not to");
testArray.SetBoolean(6, true, true);
TEST_ExpectTrue(testArray.GetLength() == 5);
TEST_ExpectTrue(testArray.GetBoolean(6) == false);
TEST_ExpectTrue(testArray.GetTypeOf(5) == JSON_Undefined);
TEST_ExpectTrue(testArray.GetTypeOf(6) == JSON_Undefined);
}
defaultproperties
{
caseName = "JSON"
}

View file

@ -0,0 +1,142 @@
/**
* Event generator for events, related to broadcasting messages
* through standard Unreal Script means:
* 1. text messages, typed by a player;
* 2. localized messages, identified by a LocalMessage class and id.
* Allows to make decisions whether or not to propagate certain messages.
* Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
class BroadcastEvents extends Events
abstract;
struct LocalizedMessage
{
// Every localized message is described by a class and id.
// For example, consider 'KFMod.WaitingMessage':
// if passed 'id' is '1',
// then it's supposed to be a message about new wave,
// but if passed 'id' is '2',
// then it's about completing the wave.
var class<LocalMessage> class;
var int id;
// Localized messages in unreal script can be passed along with
// optional arguments, described by variables below.
var PlayerReplicationInfo relatedPRI1;
var PlayerReplicationInfo relatedPRI2;
var Object relatedObject;
};
static function bool CallCanBroadcast(Actor broadcaster, int recentSentTextSize)
{
local int i;
local bool result;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0;i < listeners.length;i += 1)
{
result = class<BroadcastListenerBase>(listeners[i])
.static.CanBroadcast(broadcaster, recentSentTextSize);
if (!result) return false;
}
return true;
}
static function bool CallHandleText
(
Actor sender,
out string message,
name messageType
)
{
local int i;
local bool result;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0;i < listeners.length;i += 1)
{
result = class<BroadcastListenerBase>(listeners[i])
.static.HandleText(sender, message, messageType);
if (!result) return false;
}
return true;
}
static function bool CallHandleTextFor
(
PlayerController receiver,
Actor sender,
out string message,
name messageType
)
{
local int i;
local bool result;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0;i < listeners.length;i += 1)
{
result = class<BroadcastListenerBase>(listeners[i])
.static.HandleTextFor(receiver, sender, message, messageType);
if (!result) return false;
}
return true;
}
static function bool CallHandleLocalized
(
Actor sender,
LocalizedMessage message
)
{
local int i;
local bool result;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0;i < listeners.length;i += 1)
{
result = class<BroadcastListenerBase>(listeners[i])
.static.HandleLocalized(sender, message);
if (!result) return false;
}
return true;
}
static function bool CallHandleLocalizedFor
(
PlayerController receiver,
Actor sender,
LocalizedMessage message
)
{
local int i;
local bool result;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0;i < listeners.length;i += 1)
{
result = class<BroadcastListenerBase>(listeners[i])
.static.HandleLocalizedFor(receiver, sender, message);
if (!result) return false;
}
return true;
}
defaultproperties
{
relatedListener = class'BroadcastListenerBase'
}

View file

@ -0,0 +1,197 @@
/**
* 'BroadcastHandler' class that used by Acedia to catch
* broadcasting events. For Acedia to work properly it needs to be added to
* the very beginning of the broadcast handlers' chain.
* Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
// TODO: make it work from any place in the chain.
class BroadcastHandler extends Engine.BroadcastHandler
dependson(BroadcastEvents);
// The way vanilla 'BroadcastHandler' works - it can check if broadcast is
// possible for any actor, but for actually sending the text messages it will
// try to extract player's data from it
// and will simply pass 'none' if it can't.
// We remember senders in this array in order to pass real ones to our events.
// Array instead of variable is to account for folded calls
// (when handling of broadcast events leads to another message generation).
var private array<Actor> storedSenders;
// We want to insert our code in some of the functions between
// 'AllowsBroadcast' check and actual broadcasting,
// so we can't just use a 'super.AllowsBroadcast()' call.
// Instead we first manually do this check, then perform our logic and then
// make a super call, but with 'blockAllowsBroadcast' flag set to 'true',
// which causes overloaded 'AllowsBroadcast()' to omit actual checks.
var private bool blockAllowsBroadcast;
// Functions below simply reroute vanilla's broadcast events to
// Acedia's 'BroadcastEvents', while keeping original senders
// and blocking 'AllowsBroadcast()' as described in comments for
// 'storedSenders' and 'blockAllowsBroadcast'.
public function bool HandlerAllowsBroadcast(Actor broadcaster, int sentTextNum)
{
local bool canBroadcast;
// Check listeners
canBroadcast = class'BroadcastEvents'.static
.CallCanBroadcast(broadcaster, sentTextNum);
// Check other broadcast handlers (if present)
if (canBroadcast && nextBroadcastHandler != none)
{
canBroadcast = nextBroadcastHandler
.HandlerAllowsBroadcast(broadcaster, sentTextNum);
}
return canBroadcast;
}
function Broadcast(Actor sender, coerce string message, optional name type)
{
local bool canTryToBroadcast;
if (!AllowsBroadcast(sender, Len(message)))
return;
canTryToBroadcast = class'BroadcastEvents'.static
.CallHandleText(sender, message, type);
if (canTryToBroadcast)
{
storedSenders[storedSenders.length] = sender;
blockAllowsBroadcast = true;
super.Broadcast(sender, message, type);
blockAllowsBroadcast = false;
storedSenders.length = storedSenders.length - 1;
}
}
function BroadcastTeam
(
Controller sender,
coerce string message,
optional name type
)
{
local bool canTryToBroadcast;
if (!AllowsBroadcast(sender, Len(message)))
return;
canTryToBroadcast = class'BroadcastEvents'.static
.CallHandleText(sender, message, type);
if (canTryToBroadcast)
{
storedSenders[storedSenders.length] = sender;
blockAllowsBroadcast = true;
super.BroadcastTeam(sender, message, type);
blockAllowsBroadcast = false;
storedSenders.length = storedSenders.length - 1;
}
}
event AllowBroadcastLocalized
(
Actor sender,
class<LocalMessage> message,
optional int switch,
optional PlayerReplicationInfo relatedPRI1,
optional PlayerReplicationInfo relatedPRI2,
optional Object optionalObject
)
{
local bool canTryToBroadcast;
local BroadcastEvents.LocalizedMessage packedMessage;
if (!AllowsBroadcast(sender, Len(message)))
return;
packedMessage.class = message;
packedMessage.id = switch;
packedMessage.relatedPRI1 = relatedPRI1;
packedMessage.relatedPRI2 = relatedPRI2;
packedMessage.relatedObject = optionalObject;
canTryToBroadcast = class'BroadcastEvents'.static
.CallHandleLocalized(sender, packedMessage);
if (canTryToBroadcast)
{
super.AllowBroadcastLocalized( sender, message, switch,
relatedPRI1, relatedPRI2,
optionalObject);
}
}
function bool AllowsBroadcast(actor broadcaster, int len)
{
if (blockAllowsBroadcast)
return true;
return super.AllowsBroadcast(broadcaster, len);
}
function bool AcceptBroadcastText
(
PlayerController receiver,
PlayerReplicationInfo senderPRI,
out string message,
optional name type
)
{
local bool canBroadcast;
local Actor sender;
if (senderPRI != none)
{
sender = PlayerController(senderPRI.owner);
}
if (sender == none && storedSenders.length > 0)
{
sender = storedSenders[storedSenders.length - 1];
}
canBroadcast = class'BroadcastEvents'.static
.CallHandleTextFor(receiver, sender, message, type);
if (!canBroadcast)
{
return false;
}
return super.AcceptBroadcastText(receiver, senderPRI, message, type);
}
function bool AcceptBroadcastLocalized
(
PlayerController receiver,
Actor sender,
class<LocalMessage> message,
optional int switch,
optional PlayerReplicationInfo relatedPRI1,
optional PlayerReplicationInfo relatedPRI2,
optional Object obj
)
{
local bool canBroadcast;
local BroadcastEvents.LocalizedMessage packedMessage;
packedMessage.class = message;
packedMessage.id = switch;
packedMessage.relatedPRI1 = relatedPRI1;
packedMessage.relatedPRI2 = relatedPRI2;
packedMessage.relatedObject = obj;
canBroadcast = class'BroadcastEvents'.static
.CallHandleLocalizedFor(receiver, sender, packedMessage);
if (!canBroadcast)
{
return false;
}
return super.AcceptBroadcastLocalized( receiver, sender, message, switch,
relatedPRI1, relatedPRI2, obj);
}
defaultproperties
{
blockAllowsBroadcast = false
}

View file

@ -0,0 +1,120 @@
/**
* Listener for events, related to broadcasting messages
* through standard Unreal Script means:
* 1. text messages, typed by a player;
* 2. localized messages, identified by a LocalMessage class and id.
* Allows to make decisions whether or not to propagate certain messages.
* Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
class BroadcastListenerBase extends Listener
abstract;
static final function PlayerController GetController(Actor sender)
{
local Pawn senderPawn;
senderPawn = Pawn(sender);
if (senderPawn != none) return PlayerController(senderPawn.controller);
return PlayerController(sender);
}
// This event is called whenever registered broadcast handlers are asked if
// they'd allow given actor ('broadcaster') to broadcast a text message,
// given that none so far rejected it and he recently already broadcasted
// or tried to broadcast 'recentSentTextSize' symbols of text
// (that value is periodically reset in 'GameInfo',
// by default should be each second).
// NOTE: this function is ONLY called when someone tries to
// broadcast TEXT messages.
// If one of the listeners returns 'false', -
// it will be treated just like one of broadcasters returning 'false'
// in 'AllowsBroadcast' and this method won't be called for remaining
// active listeners.
static function bool CanBroadcast(Actor broadcaster, int recentSentTextSize)
{
return true;
}
// This event is called whenever a someone is trying to broadcast
// a text message (typically the typed by a player).
// This function is called once per message and allows you to change it
// (by changing 'message' argument) before any of the players receive it.
// Return 'true' to allow the message through.
// If one of the listeners returns 'false', -
// it will be treated just like one of broadcasters returning 'false'
// in 'AcceptBroadcastText' and this method won't be called for remaining
// active listeners.
static function bool HandleText
(
Actor sender,
out string message,
optional name messageType
)
{
return true;
}
// This event is similar to 'HandleText', but is called for every player
// the message is sent to.
// If allows you to alter the message, but the changes are accumulated
// as events go through the players.
static function bool HandleTextFor
(
PlayerController receiver,
Actor sender,
out string message,
optional name messageType
)
{
return true;
}
// This event is called whenever a localized message is trying to
// get broadcasted to a certain player ('receiver').
// Return 'true' to allow the message through.
// If one of the listeners returns 'false', -
// it will be treated just like one of broadcasters returning 'false'
// in 'AcceptBroadcastText' and this method won't be called for remaining
// active listeners.
static function bool HandleLocalized
(
Actor sender,
BroadcastEvents.LocalizedMessage message
)
{
return true;
}
// This event is similar to 'HandleLocalized', but is called for
// every player the message is sent to.
static function bool HandleLocalizedFor
(
PlayerController receiver,
Actor sender,
BroadcastEvents.LocalizedMessage message
)
{
return true;
}
defaultproperties
{
relatedEvents = class'BroadcastEvents'
}
// Text messages can (optionally) have their type specified.
// Examples of it are names 'Say' and 'CriticalEvent'.

View file

@ -0,0 +1,133 @@
/**
* One of the two classes that make up a core of event system in Acedia.
*
* 'Events' (or it's child) class shouldn't be instantiated.
* Usually module would provide '...Events' class that defines
* certain set of static functions that can generate event calls to
* all it's active listeners.
* If you're simply using modules someone made, -
* you don't need to bother yourself with further specifics.
* If you wish to create your own event generator,
* then first create a '...ListenerBase' object
* (more about it in the description of 'Listener' class)
* and set 'relatedListener' variable to point to it's class.
* Then for each event create a caller function in your 'Event' class,
* following this template:
* ____________________________________________________________________________
* | static function CallEVENT_NAME(<ARGUMENTS>)
* | {
* | local int i;
* | local array< class<Listener> > listeners;
* | listeners = GetListeners();
* | for (i = 0; i < listeners.length; i += 1)
* | {
* | class<...ListenerBase>(listeners[i])
* | .static.EVENT_NAME(<ARGUMENTS>);
* | }
* | }
* |___________________________________________________________________________
* If each listener must indicate whether it gives it's permission for
* something to happen, then use this template:
* ____________________________________________________________________________
* | static function CallEVENT_NAME(<ARGUMENTS>)
* | {
* | local int i;
* | local bool result;
* | local array< class<Listener> > listeners;
* | listeners = GetListeners();
* | for (i = 0; i < listeners.length; i += 1)
* | {
* | result = class<...ListenerBase>(listeners[i])
* | .static.EVENT_NAME(<ARGUMENTS>);
* | if (!result) return false;
* | }
* | return true;
* | }
* |___________________________________________________________________________
* For concrete example look at
* 'MutatorEvents' and 'MutatorListenerBase'.
* Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
class Events extends Object
abstract;
var private array< class<Listener> > listeners;
var public const class<Listener> relatedListener;
static public final function array< class<Listener> > GetListeners()
{
return default.listeners;
}
// Make given listener active.
// If listener was already activated also returns 'false'.
static public final function bool ActivateListener(class<Listener> newListener)
{
local int i;
if (newListener == none) return false;
if (!ClassIsChildOf(newListener, default.relatedListener)) return false;
for (i = 0;i < default.listeners.length;i += 1)
{
if (default.listeners[i] == newListener)
{
return false;
}
}
default.listeners[default.listeners.length] = newListener;
return true;
}
// Make given listener inactive.
// If listener wasn't active returns 'false'.
static public final function bool DeactivateListener(class<Listener> listener)
{
local int i;
if (listener == none) return false;
for (i = 0; i < default.listeners.length; i += 1)
{
if (default.listeners[i] == listener)
{
default.listeners.Remove(i, 1);
return true;
}
}
return false;
}
static public final function bool IsActiveListener(class<Listener> listener)
{
local int i;
if (listener == none) return false;
for (i = 0; i < default.listeners.length; i += 1)
{
if (default.listeners[i] == listener)
{
return true;
}
}
return false;
}
defaultproperties
{
relatedListener = class'Listener'
}

View file

@ -0,0 +1,59 @@
/**
* One of the two classes that make up a core of event system in Acedia.
*
* 'Listener' (or it's child) class shouldn't be instantiated.
* Usually module would provide '...ListenerBase' class that defines
* certain set of static functions, corresponding to events it can listen to.
* In order to handle those events you must create it's child class and
* override said functions. But they will only be called if
* 'SetActive(true)' is called for that child class.
* To create you own '...ListenerBase' class you need to define
* a static function for each event you wish it to catch and
* set 'relatedEvents' variable to point at the 'Events' class
* that will generate your events.
* For concrete example look at
* 'ConnectionEvents' and 'ConnectionListenerBase'.
* Copyright 2019 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 <https://www.gnu.org/licenses/>.
*/
class Listener extends Object
abstract;
var public const class<Events> relatedEvents;
static public final function SetActive(bool active)
{
if (active)
{
default.relatedEvents.static.ActivateListener(default.class);
}
else
{
default.relatedEvents.static.DeactivateListener(default.class);
}
}
static public final function IsActive(bool active)
{
default.relatedEvents.static.IsActiveListener(default.class);
}
defaultproperties
{
relatedEvents = class'Events'
}

View file

@ -0,0 +1,56 @@
/**
* Event generator that repeats events of a mutator.
* Copyright 2019 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 <https://www.gnu.org/licenses/>.
*/
class MutatorEvents extends Events
abstract;
static function bool CallCheckReplacement(Actor other, out byte isSuperRelevant)
{
local int i;
local bool result;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0; i < listeners.length; i += 1)
{
result = class<MutatorListenerBase>(listeners[i])
.static.CheckReplacement(other, isSuperRelevant);
if (!result) return false;
}
return true;
}
static function bool CallMutate(string command, PlayerController sendingPlayer)
{
local int i;
local bool result;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0; i < listeners.length;i += 1)
{
result = class<MutatorListenerBase>(listeners[i])
.static.Mutate(command, sendingPlayer);
if (!result) return false;
}
return true;
}
defaultproperties
{
relatedListener = class'MutatorListenerBase'
}

View file

@ -0,0 +1,47 @@
/**
* Listener for events, normally propagated by mutators.
* Copyright 2019 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 <https://www.gnu.org/licenses/>.
*/
class MutatorListenerBase extends Listener
abstract;
// This event is called whenever 'CheckReplacement'
// check is propagated through mutators.
// If one of the listeners returns 'false', -
// it will be treated just like a mutator returning 'false'
// in 'CheckReplacement' and
// this method won't be called for remaining active listeners.
static function bool CheckReplacement(Actor other, out byte isSuperRelevant)
{
return true;
}
// This event is called whenever 'Mutate' is propagated through mutators.
// If one of the listeners returns 'false', -
// this method won't be called for remaining active listeners or mutators.
// If all listeners return 'true', -
// mutate command will be further propagated to the rest of the mutators.
static function bool Mutate(string command, PlayerController sendingPlayer)
{
return true;
}
defaultproperties
{
relatedEvents = class'MutatorEvents'
}

117
sources/Core/Feature.uc Normal file
View file

@ -0,0 +1,117 @@
/**
* Feature represents a certain subset of Acedia's functionality that
* can be enabled or disabled, according to server owner's wishes.
* In the current version of Acedia enabling or disabling a feature requires
* manually editing configuration file and restarting a server.
* Factually feature is just a collection of settings with one universal
* 'isActive' setting that tells Acedia whether or not to load a feature.
* Copyright 2019 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 <https://www.gnu.org/licenses/>.
*/
class Feature extends Singleton
abstract
config(Acedia);
// Setting that tells Acedia whether or not to enable this feature
// during initialization.
// Only it's default value is ever used.
var private config bool autoEnable;
// Listeners listed here will be automatically activated.
var public const array< class<Listener> > requiredListeners;
// Sets whether to enable this feature by default.
public static final function SetAutoEnable(bool doEnable)
{
default.autoEnable = doEnable;
StaticSaveConfig();
}
public static final function bool IsAutoEnabled()
{
return default.autoEnable;
}
// Whether feature is enabled is determined by
public static final function bool IsEnabled()
{
return (GetInstance() != none);
}
// Enables feature of given class.
// To disable a feature simply use 'Destroy'.
public static final function Feature EnableMe()
{
local Feature newInstance;
if (IsEnabled())
{
return Feature(GetInstance());
}
default.blockSpawning = false;
newInstance = class'Acedia'.static.GetInstance().Spawn(default.class);
default.blockSpawning = true;
return newInstance;
}
// Event functions that are called when
public function OnEnabled(){}
public function OnDisabled(){}
// Set listeners' status
private static function SetListenersActiveSatus(bool newStatus)
{
local int i;
for (i = 0; i < default.requiredListeners.length; i += 1)
{
if (default.requiredListeners[i] == none) continue;
default.requiredListeners[i].static.SetActive(newStatus);
}
}
// 'OnEnabled' and 'OnDisabled' should be called from functions that
// will be called regardless of whether 'Feature' was created
// with 'ChangeEnabledState' or in some other way.
event PreBeginPlay()
{
super.PreBeginPlay();
// '!bDeleteMe' means that we will be a singleton instance,
// meaning that we only just got enabled.
if (!bDeleteMe && IsEnabled())
{
// Block spawning this feature before calling any other events
default.blockSpawning = true;
SetListenersActiveSatus(true);
OnEnabled();
return;
}
}
event Destroyed()
{
super.Destroyed();
SetListenersActiveSatus(false);
OnDisabled();
}
defaultproperties
{
autoEnable = false
// Prevent spawning this feature by any other means than 'EnableMe()'.
blockSpawning = true
// Features are server-only actors
remoteRole = ROLE_None
}

28
sources/Core/Service.uc Normal file
View file

@ -0,0 +1,28 @@
/**
* Parent class for all services used in Acedia.
* Currently simply makes itself server-only.
* Copyright 2019 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 <https://www.gnu.org/licenses/>.
*/
class Service extends Singleton
abstract;
defaultproperties
{
remoteRole = ROLE_None
DrawType = DT_None
}

96
sources/Core/Singleton.uc Normal file
View file

@ -0,0 +1,96 @@
/**
* Singleton is an auxiliary class, meant to be used as a base for others,
* that allows for only one instance of it to exist.
* To make sure your child class properly works, either don't overload
* 'PreBeginPlay' or make sure to call it's parent's version.
* Copyright 2019 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 <https://www.gnu.org/licenses/>.
*/
class Singleton extends AcediaActor
abstract;
// Default value of this variable will store one and only existing version
// of actor of this class.
var private Singleton activeInstance;
// Setting default value of this variable to 'true' prevents creation of
// a singleton, even if no instances of it exist.
// Only a default value is ever used.
var protected bool blockSpawning;
public final static function Singleton GetInstance()
{
if (default.activeInstance != none && default.activeInstance.bPendingDelete)
return none;
return default.activeInstance;
}
public final static function bool IsSingletonCreationBlocked()
{
return default.blockSpawning;
}
protected function OnCreated(){}
protected function OnDestroyed(){}
// Make sure only one instance of 'Singleton' exists at any point in time.
// Instead of overloading this function we suggest you overload a special
// event function `OnCreated()` that is called whenever a valid `Singleton`
// instance is spawned.
// If you absolutely must overload this function in any child class -
// first call this version of the method and then check if
// you are about to be deleted 'bDeleteMe == true':
// ____________________________________________________________________________
// | super.PreBeginPlay();
// | // ^^^ If singleton wasn't already created, - only after that call
// | // will instance, returned by 'GetInstance()', be set.
// | if (bDeleteMe)
// | return;
// |___________________________________________________________________________
event PreBeginPlay()
{
super.PreBeginPlay();
if (default.blockSpawning || GetInstance() != none)
{
Destroy();
}
else
{
default.activeInstance = self;
OnCreated();
}
}
// Make sure only one instance of 'Singleton' exists at any point in time.
// Instead of overloading this function we suggest you overload a special
// event function `OnDestroyed()` that is called whenever a valid `Singleton`
// instance is destroyed.
// If you absolutely must overload this function in any child class -
// first call this version of the method.
event Destroyed()
{
super.Destroyed();
if (self == GetInstance())
{
OnDestroyed();
}
}
defaultproperties
{
blockSpawning = false
}

37
sources/Core/StartUp.uc Normal file
View file

@ -0,0 +1,37 @@
/**
* This actor's role is to add Acedia mutator on listen and dedicated servers.
* Copyright 2019 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 <https://www.gnu.org/licenses/>.
*/
class StartUp extends Actor;
function PreBeginPlay()
{
super.PreBeginPlay();
if (level != none && level.game != none)
{
level.game.AddMutator(string(class'Acedia'));
}
Destroy();
}
defaultproperties
{
// This is a server-only actor
remoteRole = ROLE_None
}

View file

@ -0,0 +1,263 @@
/**
* Base class aimed to contain sets of unit tests for various components of
* Acedia and it's features.
* Currently provides bare-bones testing functions that check boolean
* variables for true/false and objects for whether they're `none` or not.
* Tests:
* ~ can be grouped by their "context",
* describing what they are testing;
* ~ test (or several tests) can be assigned an error message,
* describing what exactly went wrong.
* Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
class TestCase extends AcediaObject
abstract;
// Name by which this set of unit tests can be referred to.
var protected const string caseName;
// Information about how well testing went for a particular context,
// i.e. subsets of tests for a particular functionality.
struct ContextSummary
{
// Text, human-readable description of the purpose of
// tests in this context.
var string description;
// `false` if at least one test failed, `true` otherwise.
var bool passed;
// How many test were performed.
var int testsPerformed;
// How many tests failed.
var int testsFailed;
// Error messages generated by failed tests.
var array<string> errors;
};
// Collection of summaries for all contexts defined by the user so far.
struct Summary
{
var bool passed;
var array<ContextSummary> contextSummaries;
};
// Has function for defining context (`Context()`) been called.
var private bool userDefinedContext;
// Were all tests performed?
var private bool finishedTests;
// Error message that will be generated if some test will fail now.
var private string currentErrorMessage;
// Store complete summary here.
var private Summary currentSummary;
// For quick access store current context's summary here and update it in
// `currentSummary` once done.
var private ContextSummary activeContextSummary;
// Call this function to define a context for subsequent test
// (until another call).
public final static function Context(string description)
{
if ( default.userDefinedContext
|| default.activeContextSummary.testsPerformed > 0)
{
UpdateContextSummary(default.activeContextSummary);
}
default.userDefinedContext = true;
default.activeContextSummary = GetContextSummary(description);
default.currentErrorMessage = "";
}
// Call this function to define an error message for tests that
// would fail after it.
// Message is reset by another call of `Issue()` or
// by changing the context via `Context()`.
public final static function Issue(string errorMessage)
{
default.currentErrorMessage = errorMessage;
}
// All tests to be performed can be placed in this function,
// along with appropriate calls to `Context()` and `Issue()`.
// For an example see class `TEST_JSON`.
protected static function TESTS(){}
// Following functions provide simple test primitives,
public final static function TEST_ExpectTrue(bool result)
{
RecordTestResult(result, default.currentErrorMessage);
}
public final static function TEST_ExpectFalse(bool result)
{
RecordTestResult(!result, default.currentErrorMessage);
}
public final static function TEST_ExpectNone(Object object)
{
RecordTestResult(object == none, default.currentErrorMessage);
}
public final static function TEST_ExpectNotNone(Object object)
{
RecordTestResult(object != none, default.currentErrorMessage);
}
// Returns the summary of how testing went.
public final static function Summary GetSummary()
{
return default.currentSummary;
}
// Name by which this set of unit tests can be referred to.
public final static function string GetName()
{
return default.caseName;
}
// Creates brand new summary for context with a given description,
// marked as "passed" and zero tests done.
private final static function ContextSummary NewContextSummary
(
string description
)
{
local ContextSummary newSummary;
newSummary.passed = true;
newSummary.description = description;
newSummary.testsPerformed = 0;
newSummary.testsFailed = 0;
newSummary.errors.length = 0;
return newSummary;
}
// Returns index of summary with given description
// in our records (`currentSummary`).
// Return `-1` if there is no context with such description.
private final static function int GetContextSummaryIndex(string description)
{
local int i;
for (i = 0; i < default.currentSummary.contextSummaries.length; i += 1)
{
if ( default.currentSummary.contextSummaries[i].description
~= description)
{
return i;
}
}
return -1;
}
// Returns index summary with given description
// in our records.
// Return new context summary if there is no context with such description.
private final static function ContextSummary GetContextSummary
(
string description
)
{
local int index;
if (default.activeContextSummary.description ~= description)
{
return default.activeContextSummary;
}
index = GetContextSummaryIndex(description);
if (index < 0)
{
return NewContextSummary(description);
}
return default.currentSummary.contextSummaries[index];
}
// Rewrites summary (with the same name as a given summary)
// in `currentSummary` records.
// If there's no such record - adds a new one.
private final static function UpdateContextSummary
(
ContextSummary relevantSummary
)
{
local int index;
index = GetContextSummaryIndex(relevantSummary.description);
if (index < 0)
{
index = default.currentSummary.contextSummaries.length;
}
default.currentSummary.contextSummaries[index] = relevantSummary;
}
// Records (in current context summary) that another test was performed and
// succeeded/failed, along with given error message.
private final static function RecordTestResult
(
bool isSuccessful,
string errorMessage
)
{
local int i;
local int errorsAmount;
if (default.finishedTests) return;
default.activeContextSummary.testsPerformed += 1;
if (isSuccessful) return;
default.currentSummary.passed = false;
default.activeContextSummary.passed = false;
default.activeContextSummary.testsFailed += 1;
errorsAmount = default.activeContextSummary.errors.length;
for (i = 0; i < errorsAmount; i += 1)
{
if (default.activeContextSummary.errors[i] ~= errorMessage)
{
return;
}
}
default.activeContextSummary.errors[errorsAmount] = errorMessage;
}
// Calling this function will perform unit tests defined in `TESTS()`
// function of this test case and will prepare the summary,
// obtainable through `GetSummary()` function.
// Returns `true` if all tests have successfully passed
// and `false` otherwise.
public final static function bool PerformTests()
{
default.finishedTests = false;
default.userDefinedContext = false;
default.currentSummary.passed = true;
default.currentSummary.contextSummaries.length = 0;
default.activeContextSummary = NewContextSummary("");
TESTS();
UpdateContextSummary(default.activeContextSummary);
default.finishedTests = true;
return default.currentSummary.passed;
}
// TODO: Support for testing in stages to avoid infinite loop crashes.
// TODO: Add support for test scening: grabbing pawns, placing them, waiting.
// TODO: Expand scening support: triggering functions on client, moving.
// TODO: Expand scening support: zed spawning, aggro setting.
// TODO: Expand scening support: function calls (i.e. for CashToss),
// testing `FixDoshSpam` feature.
// TODO: Expand scening support: lag detection.
// TODO: Expand scening support: test `FixZedTime`.
// TODO: Expand scening support: aiming shooting, detecting damage.
// TODO: Expand scening support: testing `FixFFHack`.
// TODO: Testing infinite nade (partially), ammo selling, dualies cost.
defaultproperties
{
caseName = ""
}