Change file structure of Acedia
Improves grouping of some files in project's directories.
This commit is contained in:
parent
54e87437c2
commit
97569f9568
21 changed files with 0 additions and 0 deletions
141
sources/Core/Acedia.uc
Normal file
141
sources/Core/Acedia.uc
Normal 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"
|
||||
}
|
||||
40
sources/Core/AcediaActor.uc
Normal file
40
sources/Core/AcediaActor.uc
Normal 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
|
||||
{
|
||||
}
|
||||
35
sources/Core/AcediaObject.uc
Normal file
35
sources/Core/AcediaObject.uc
Normal 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
|
||||
{
|
||||
}
|
||||
32
sources/Core/AcediaReplicationInfo.uc
Normal file
32
sources/Core/AcediaReplicationInfo.uc
Normal 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
|
||||
{
|
||||
}
|
||||
351
sources/Core/Data/JSON/JArray.uc
Normal file
351
sources/Core/Data/JSON/JArray.uc
Normal 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
|
||||
{
|
||||
}
|
||||
265
sources/Core/Data/JSON/JObject.uc
Normal file
265
sources/Core/Data/JSON/JObject.uc
Normal 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
|
||||
{
|
||||
}
|
||||
84
sources/Core/Data/JSON/JSON.uc
Normal file
84
sources/Core/Data/JSON/JSON.uc
Normal 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 name–value 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
|
||||
{
|
||||
}
|
||||
41
sources/Core/Data/JSON/JSONAPI.uc
Normal file
41
sources/Core/Data/JSON/JSONAPI.uc
Normal 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
|
||||
{
|
||||
}
|
||||
711
sources/Core/Data/JSON/Tests/TEST_JSON.uc
Normal file
711
sources/Core/Data/JSON/Tests/TEST_JSON.uc
Normal 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"
|
||||
}
|
||||
142
sources/Core/Events/Broadcast/BroadcastEvents.uc
Normal file
142
sources/Core/Events/Broadcast/BroadcastEvents.uc
Normal 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'
|
||||
}
|
||||
197
sources/Core/Events/Broadcast/BroadcastHandler.uc
Normal file
197
sources/Core/Events/Broadcast/BroadcastHandler.uc
Normal 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
|
||||
}
|
||||
120
sources/Core/Events/Broadcast/BroadcastListenerBase.uc
Normal file
120
sources/Core/Events/Broadcast/BroadcastListenerBase.uc
Normal 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'.
|
||||
133
sources/Core/Events/Events.uc
Normal file
133
sources/Core/Events/Events.uc
Normal 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'
|
||||
}
|
||||
59
sources/Core/Events/Listener.uc
Normal file
59
sources/Core/Events/Listener.uc
Normal 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'
|
||||
}
|
||||
56
sources/Core/Events/Mutator/MutatorEvents.uc
Normal file
56
sources/Core/Events/Mutator/MutatorEvents.uc
Normal 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'
|
||||
}
|
||||
47
sources/Core/Events/Mutator/MutatorListenerBase.uc
Normal file
47
sources/Core/Events/Mutator/MutatorListenerBase.uc
Normal 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
117
sources/Core/Feature.uc
Normal 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
28
sources/Core/Service.uc
Normal 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
96
sources/Core/Singleton.uc
Normal 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
37
sources/Core/StartUp.uc
Normal 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
|
||||
}
|
||||
263
sources/Core/Testing/TestCase.uc
Normal file
263
sources/Core/Testing/TestCase.uc
Normal 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 = ""
|
||||
}
|
||||
Reference in a new issue