Browse Source

Refactor `UnrealAPI`: move out `GameRulesAPI`

pull/8/head
Anton Tarasenko 4 years ago
parent
commit
5bcbc2047a
  1. 38
      sources/Unreal/Events/Unreal_OnTick_Signal.uc
  2. 40
      sources/Unreal/Events/Unreal_OnTick_Slot.uc
  3. 109
      sources/Unreal/GameRules/AcediaGameRules.uc
  4. 48
      sources/Unreal/GameRules/Events/GameRules_OnFindPlayerStart_Signal.uc
  5. 44
      sources/Unreal/GameRules/Events/GameRules_OnFindPlayerStart_Slot.uc
  6. 52
      sources/Unreal/GameRules/Events/GameRules_OnNetDamage_Signal.uc
  7. 49
      sources/Unreal/GameRules/Events/GameRules_OnNetDamage_Slot.uc
  8. 45
      sources/Unreal/GameRules/Events/GameRules_OnOverridePickupQuery_Signal.uc
  9. 46
      sources/Unreal/GameRules/Events/GameRules_OnOverridePickupQuery_Slot.uc
  10. 232
      sources/Unreal/GameRules/GameRulesAPI.uc
  11. 67
      sources/Unreal/Tests/TEST_UnrealAPI.uc
  12. 146
      sources/Unreal/UnrealAPI.uc
  13. 92
      sources/Unreal/UnrealService.uc

38
sources/Unreal/Events/Unreal_OnTick_Signal.uc

@ -0,0 +1,38 @@
/**
* Signal class implementation for `UnrealAPI`'s `OnTick` signal.
* Copyright 2021 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 Unreal_OnTick_Signal extends Signal;
public final function Emit(float delta, float dilationCoefficient)
{
local Slot nextSlot;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
Unreal_OnTick_Slot(nextSlot).connect(delta, dilationCoefficient);
nextSlot = GetNextSlot();
}
CleanEmptySlots();
}
defaultproperties
{
relatedSlotClass = class'Unreal_OnTick_Slot'
}

40
sources/Unreal/Events/Unreal_OnTick_Slot.uc

@ -0,0 +1,40 @@
/**
* Slot class implementation for `UnrealAPI`'s `OnTick` signal.
* Copyright 2021 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 Unreal_OnTick_Slot extends Slot;
delegate connect(float delta, float dilationCoefficient)
{
DummyCall();
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
defaultproperties
{
}

109
sources/Unreal/GameRules/AcediaGameRules.uc

@ -0,0 +1,109 @@
/**
* Acedia's `GameRules` class that provides `GameRules`'s events through
* the signal/slot functionality.
* Copyright 2021 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 AcediaGameRules extends GameRules;
var private GameRules_OnFindPlayerStart_Signal onFindPlayerStartSignal;
var private GameRules_OnOverridePickupQuery_Signal onOverridePickupQuery;
var private GameRules_OnNetDamage_Signal onNetDamage;
public final function Initialize(unrealService service)
{
if (service == none) {
return;
}
onFindPlayerStartSignal = GameRules_OnFindPlayerStart_Signal(
service.GetSignal(class'GameRules_OnFindPlayerStart_Signal'));
onOverridePickupQuery = GameRules_OnOverridePickupQuery_Signal(
service.GetSignal(class'GameRules_OnOverridePickupQuery_Signal'));
onNetDamage = GameRules_OnNetDamage_Signal(
service.GetSignal(class'GameRules_OnNetDamage_Signal'));
}
function string GetRules()
{
local string resultSet;
resultSet = "acedia";
if (nextGameRules != none) {
resultSet = resultSet $ nextGameRules.GetRules();
}
return resultSet;
}
function NavigationPoint FindPlayerStart(
Controller player,
optional byte inTeam,
optional string incomingName)
{
local NavigationPoint result;
if (onFindPlayerStartSignal != none) {
result = onFindPlayerStartSignal.Emit(player, inTeam, incomingName);
}
if (result == none && nextGameRules != none) {
return nextGameRules.FindPlayerStart(player, inTeam, incomingName);
}
return result;
}
function bool OverridePickupQuery(
Pawn other,
Pickup item,
out byte allowPickup)
{
local bool shouldOverride;
if (onOverridePickupQuery != none) {
shouldOverride = onOverridePickupQuery.Emit(other, item, allowPickup);
}
if (shouldOverride) {
return true;
}
if (nextGameRules != none) {
return nextGameRules.OverridePickupQuery(other, item, allowPickup);
}
return false;
}
function int NetDamage(
int originalDamage,
int damage,
Pawn injured,
Pawn instigatedBy,
Vector hitLocation,
out Vector momentum,
class<DamageType> damageType)
{
if (onNetDamage != none)
{
damage = onNetDamage.Emit( originalDamage, damage, injured,
instigatedBy, hitLocation, momentum,
damageType);
}
if (nextGameRules != none)
{
return nextGameRules.NetDamage( originalDamage, damage, injured,
instigatedBy, hitLocation, momentum,
damageType);
}
return damage;
}
defaultproperties
{
}

48
sources/Unreal/GameRules/Events/GameRules_OnFindPlayerStart_Signal.uc

@ -0,0 +1,48 @@
/**
* Signal class implementation for `GameRulesAPI`'s `OnFindPlayerStart` signal.
* Copyright 2021 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 GameRules_OnFindPlayerStart_Signal extends Signal;
public final function NavigationPoint Emit(
Controller player,
optional byte inTeam,
optional string incomingName
)
{
local Slot nextSlot;
local NavigationPoint nextPoint;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
nextPoint = GameRules_OnFindPlayerStart_Slot(nextSlot)
.connect(player, inTeam, incomingName);
if (nextPoint != none && !nextSlot.IsEmpty()) {
return nextPoint;
}
nextSlot = GetNextSlot();
}
CleanEmptySlots();
return none;
}
defaultproperties
{
relatedSlotClass = class'GameRules_OnFindPlayerStart_Slot'
}

44
sources/Unreal/GameRules/Events/GameRules_OnFindPlayerStart_Slot.uc

@ -0,0 +1,44 @@
/**
* Slot class implementation for `GameRulesAPI`'s `OnFindPlayerStart` signal.
* Copyright 2021 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 GameRules_OnFindPlayerStart_Slot extends Slot;
delegate NavigationPoint connect(
Controller player,
optional byte inTeam,
optional string incomingName)
{
DummyCall();
return none;
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
defaultproperties
{
}

52
sources/Unreal/GameRules/Events/GameRules_OnNetDamage_Signal.uc

@ -0,0 +1,52 @@
/**
* Signal class implementation for `GameRulesAPI`'s `OnNetDamage` signal.
* Copyright 2021 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 GameRules_OnNetDamage_Signal extends Signal;
public final function int Emit(
int originalDamage,
int damage,
Pawn injured,
Pawn instigatedBy,
Vector hitLocation,
out Vector momentum,
class<DamageType> damageType)
{
local Slot nextSlot;
local int newDamage;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
newDamage = GameRules_OnNetDamage_Slot(nextSlot)
.connect( originalDamage, damage, injured, instigatedBy,
hitLocation, momentum, damageType);
if (!nextSlot.IsEmpty()) {
damage = newDamage;
}
nextSlot = GetNextSlot();
}
CleanEmptySlots();
return damage;
}
defaultproperties
{
relatedSlotClass = class'GameRules_OnNetDamage_Slot'
}

49
sources/Unreal/GameRules/Events/GameRules_OnNetDamage_Slot.uc

@ -0,0 +1,49 @@
/**
* Slot class implementation for `GameRulesAPI`'s `OnNetDamage` signal.
* Copyright 2021 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 GameRules_OnNetDamage_Slot extends Slot;
delegate int connect(
int originalDamage,
int damage,
Pawn injured,
Pawn instigatedBy,
Vector hitLocation,
out Vector momentum,
class<DamageType> damageType)
{
DummyCall();
// Do not alter any already made modifications
return damage;
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
defaultproperties
{
}

45
sources/Unreal/GameRules/Events/GameRules_OnOverridePickupQuery_Signal.uc

@ -0,0 +1,45 @@
/**
* Signal class implementation for `GameRulesAPI`'s
* `OnOverridePickupQuerySignal` signal.
* Copyright 2021 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 GameRules_OnOverridePickupQuery_Signal extends Signal;
public final function bool Emit(Pawn other, Pickup item, out byte allowPickup)
{
local Slot nextSlot;
local bool shouldOverride;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
shouldOverride = GameRules_OnOverridePickupQuery_Slot(nextSlot)
.connect(other, item, allowPickup);
if (shouldOverride && !nextSlot.IsEmpty()) {
return shouldOverride;
}
nextSlot = GetNextSlot();
}
CleanEmptySlots();
return false;
}
defaultproperties
{
relatedSlotClass = class'GameRules_OnOverridePickupQuery_Slot'
}

46
sources/Unreal/GameRules/Events/GameRules_OnOverridePickupQuery_Slot.uc

@ -0,0 +1,46 @@
/**
* Slot class implementation for `GameRulesAPI`'s
* `OnOverridePickupQuerySignal` signal.
* Copyright 2021 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 GameRules_OnOverridePickupQuery_Slot extends Slot;
delegate bool connect(
Pawn other,
Pickup item,
out byte allowPickup)
{
DummyCall();
// Do not override pickup queue by default
return false;
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
defaultproperties
{
}

232
sources/Unreal/GameRules/GameRulesAPI.uc

@ -0,0 +1,232 @@
/**
* Low-level API that provides set of utility methods for working with
* `GameRule`s.
* Copyright 2021 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 GameRulesAPI extends AcediaObject;
var private LoggerAPI.Definition errNoService;
/**
* Called when game decides on a player's spawn point. If a `NavigationPoint`
* is returned, signal propagation will be interrupted and returned value will
* be used as the player start.
*
* [Signature]
* NavigationPoint <slot>(
* Controller player,
* optional byte inTeam,
* optional string incomingName)
*
* @param player Player for whom we are picking a spawn point.
* @param inTeam Player's team number.
* @param incomingName `Portal` parameter from `GameInfo.Login()` event.
* @return `NavigationPoint` that will player must be spawned at.
* `none` means that slot does not want to modify it.
*/
/* SIGNAL */
public final function GameRules_OnFindPlayerStart_Slot OnFindPlayerStart(
AcediaObject receiver)
{
local Signal signal;
local UnrealService service;
service = UnrealService(class'UnrealService'.static.Require());
if (service == none)
{
_.logger.Auto(errNoService);
return none;
}
signal = service.GetSignal(class'GameRules_OnFindPlayerStart_Signal');
return GameRules_OnFindPlayerStart_Slot(signal.NewSlot(receiver));
}
/**
* When pawn wants to pickup something, `GameRule`s are given a chance to
* modify it. If one of the `Slot`s returns `true`, `allowPickup` will
* determine if the object can be picked up.
* Overriding via this method allows to completely bypass check against
* `Pawn`'s inventory's `HandlePickupQuery()` method.
*
* [Signature]
* bool <slot>(Pawn other, Pickup item, out byte allowPickup)
*
* @param other Pawn which will potentially pickup `item`.
* @param item Pickup which `other` might potentially pickup.
* @param allowPickup `true` if you want to force `other` to pickup an item
* and `false` otherwise. This parameter is ignored if returned value of
* your slot call is `false`.
* @return `true` if you wish to override decision about pickup with
* `allowPickup` and `false` if you do not want to make that decision.
* If you do decide to override decision by returning `true` - this signal
* will not be propagated to the rest of the slots.
*/
/* SIGNAL */
public final function GameRules_OnOverridePickupQuery_Slot
OnOverridePickupQuery(AcediaObject receiver)
{
local Signal signal;
local UnrealService service;
service = UnrealService(class'UnrealService'.static.Require());
if (service == none)
{
_.logger.Auto(errNoService);
return none;
}
signal = service.GetSignal(class'GameRules_OnOverridePickupQuery_Signal');
return GameRules_OnOverridePickupQuery_Slot(signal.NewSlot(receiver));
}
// TODO: rewrite
/**
* When pawn wants to pickup something, `GameRule`s are given a chance to
* modify it. If one of the `Slot`s returns `true`, `allowPickup` will
* determine if the object can be picked up.
* Overriding via this method allows to completely bypass check against
* `Pawn`'s inventory's `HandlePickupQuery()` method.
*
* [Signature]
* bool <slot>(Pawn other, Pickup item, out byte allowPickup)
*
* @param other Pawn which will potentially pickup `item`.
* @param item Pickup which `other` might potentially pickup.
* @param allowPickup `true` if you want to force `other` to pickup an item
* and `false` otherwise. This parameter is ignored if returned value of
* your slot call is `false`.
* @return `true` if you wish to override decision about pickup with
* `allowPickup` and `false` if you do not want to make that decision.
* If you do decide to override decision by returning `true` - this signal
* will not be propagated to the rest of the slots.
*/
/* SIGNAL */
public final function GameRules_OnNetDamage_Slot OnNetDamage(
AcediaObject receiver)
{
local Signal signal;
local UnrealService service;
service = UnrealService(class'UnrealService'.static.Require());
if (service == none)
{
_.logger.Auto(errNoService);
return none;
}
signal = service.GetSignal(class'GameRules_OnNetDamage_Signal');
return GameRules_OnNetDamage_Slot(signal.NewSlot(receiver));
}
/**
* Adds new `GameRules` class to the current `GameInfo`.
* Does nothing if give `GameRules` class was already added before.
*
* @param newRulesClass Class of rules to add.
* @return `true` if `GameRules` were added and `false` otherwise
* (because they were already active.)
*/
public final function bool Add(class<GameRules> newRulesClass)
{
if (AreAdded(newRulesClass)) {
return false;
}
_.unreal.GetGameType()
.AddGameModifier(GameRules(_.memory.Allocate(newRulesClass)));
return true;
}
/**
* Removes given `GameRules` class from the current `GameInfo`,
* if they are active. Does nothing otherwise.
*
* @param rulesClassToRemove Class of rules to try and remove.
* @return `true` if `GameRules` were removed and `false` otherwise
* (if they were not active in the first place).
*/
public final function bool Remove(class<GameRules> rulesClassToRemove)
{
local GameInfo game;
local GameRules rulesIter;
local GameRules rulesToDestroy;
if (rulesClassToRemove == none) return false;
game = _.unreal.GetGameType();
if (game.gameRulesModifiers == none) return false;
// Check root rules
rulesToDestroy = game.gameRulesModifiers;
if (rulesToDestroy.class == rulesClassToRemove)
{
game.gameRulesModifiers = rulesToDestroy.nextGameRules;
rulesToDestroy.Destroy();
return true;
}
// Check rest of the rules
rulesIter = game.gameRulesModifiers;
while (rulesIter != none)
{
rulesToDestroy = rulesIter.nextGameRules;
if ( rulesToDestroy != none
&& rulesToDestroy.class == rulesClassToRemove)
{
rulesIter.nextGameRules = rulesToDestroy.nextGameRules;
rulesToDestroy.Destroy();
return true;
}
rulesIter = rulesIter.nextGameRules;
}
return false;
}
/**
* Finds given class of `GameRules` if it's currently active in `GameInfo`.
* Returns `none` otherwise.
*
* @param rulesClassToFind Class of rules to find.
* @return `GameRules` of given class `rulesClassToFind` instance added to
* `GameInfo`'s records and `none` if no such rules are currently added.
*/
public final function GameRules FindInstance(
class<GameRules> rulesClassToFind)
{
local GameRules rulesIter;
if (rulesClassToFind == none) {
return none;
}
rulesIter = _.unreal.GetGameType().gameRulesModifiers;
while (rulesIter != none)
{
if (rulesIter.class == rulesClassToFind) {
return rulesIter;
}
rulesIter = rulesIter.nextGameRules;
}
return none;
}
/**
* Checks if given class of `GameRules` is currently active in `GameInfo`.
*
* @param rulesClassToCheck Class of rules to check for.
* @return `true` if `GameRules` are active and `false` otherwise.
*/
public final function bool AreAdded(
class<GameRules> rulesClassToCheck)
{
return (FindInstance(rulesClassToCheck) != none);
}
defaultproperties
{
errNoService = (l=LOG_Error,m="`UnrealService` could not be reached.")
}

67
sources/Unreal/Tests/TEST_UnrealAPI.uc

@ -19,10 +19,10 @@
*/
class TEST_UnrealAPI extends TestCase;
protected static function int CountRulesAmount(class<GameRules> gameRulesClass)
protected static function int CountRulesAmount(class<gameRules> gameRulesClass)
{
local int counter;
local GameRules rulesIter;
local gameRules rulesIter;
if (gameRulesClass == none) {
return 0;
}
@ -32,7 +32,7 @@ protected static function int CountRulesAmount(class<GameRules> gameRulesClass)
if (rulesIter.class == gameRulesClass) {
counter += 1;
}
rulesIter = rulesIter.nextGameRules;
rulesIter = rulesIter.nextgameRules;
}
return counter;
}
@ -57,38 +57,38 @@ protected static function Test_GameType()
protected static function Test_GameRules()
{
Context("Testing methods for working with `GameRules`.");
Context("Testing methods for working with `gameRules`.");
SubTest_AddRemoveGameRules();
SubTest_CheckGameRules();
}
protected static function SubTest_AddRemoveGameRules()
{
Issue("`AddGameRules()` does not add game rules.");
__().unreal.AddGameRules(class'MockGameRulesA');
Issue("`gameRules.Add()` does not add game rules.");
__().unreal.gameRules.Add(class'MockGameRulesA');
TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesA') == 1);
__().unreal.AddGameRules(class'MockGameRulesA');
Issue("Calling `AddGameRules()` twice leads to rule duplication.");
__().unreal.gameRules.Add(class'MockGameRulesA');
Issue("Calling `gameRules.Add()` twice leads to rule duplication.");
TEST_ExpectFalse(CountRulesAmount(class'MockGameRulesA') > 1);
Issue("Calling `AddGameRules()` leads to rule not being added.");
Issue("Calling `gameRules.Add()` leads to rule not being added.");
TEST_ExpectFalse(CountRulesAmount(class'MockGameRulesA') == 0);
Issue("Adding new rules with `AddGameRules()` does not work properly.");
__().unreal.AddGameRules(class'MockGameRulesB');
Issue("Adding new rules with `gameRules.Add()` does not work properly.");
__().unreal.gameRules.Add(class'MockGameRulesB');
TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesA') == 1);
TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesB') == 1);
Issue("Adding/removing rules with `RemoveGameRules()` leads to" @
Issue("Adding/removing rules with `gameRules.Remove()` leads to" @
"unexpected results.");
__().unreal.RemoveGameRules(class'MockGameRulesB');
__().unreal.gameRules.Remove(class'MockGameRulesB');
TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesA') == 1);
TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesB') == 0);
__().unreal.AddGameRules(class'MockGameRulesB');
__().unreal.RemoveGameRules(class'MockGameRulesA');
__().unreal.gameRules.Add(class'MockGameRulesB');
__().unreal.gameRules.Remove(class'MockGameRulesA');
TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesA') == 0);
TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesB') == 1);
__().unreal.RemoveGameRules(class'MockGameRulesB');
__().unreal.gameRules.Remove(class'MockGameRulesB');
TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesA') == 0);
TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesB') == 0);
}
@ -96,32 +96,32 @@ protected static function SubTest_AddRemoveGameRules()
protected static function SubTest_CheckGameRules()
{
local string issueForAdded, issueForNotAdded;
issueForAdded = "`AreGameRulesAdded()` returns `false` for rules that are" @
"currently added.";
issueForNotAdded = "`AreGameRulesAdded()` returns `true` for rules that" @
issueForAdded = "`gameRules.AreAdded()` returns `false` for rules that"
@ "are currently added.";
issueForNotAdded = "`gameRules.AreAdded()` returns `true` for rules that" @
"are not currently added.";
__().unreal.RemoveGameRules(class'MockGameRulesA');
__().unreal.RemoveGameRules(class'MockGameRulesB');
__().unreal.gameRules.Remove(class'MockGameRulesA');
__().unreal.gameRules.Remove(class'MockGameRulesB');
Issue(issueForNotAdded);
TEST_ExpectFalse(__().unreal.AreGameRulesAdded(class'MockGameRulesA'));
TEST_ExpectFalse(__().unreal.AreGameRulesAdded(class'MockGameRulesB'));
TEST_ExpectFalse(__().unreal.gameRules.AreAdded(class'MockGameRulesA'));
TEST_ExpectFalse(__().unreal.gameRules.AreAdded(class'MockGameRulesB'));
__().unreal.AddGameRules(class'MockGameRulesB');
__().unreal.gameRules.Add(class'MockGameRulesB');
Issue(issueForNotAdded);
TEST_ExpectFalse(__().unreal.AreGameRulesAdded(class'MockGameRulesA'));
TEST_ExpectFalse(__().unreal.gameRules.AreAdded(class'MockGameRulesA'));
Issue(issueForAdded);
TEST_ExpectTrue(__().unreal.AreGameRulesAdded(class'MockGameRulesB'));
TEST_ExpectTrue(__().unreal.gameRules.AreAdded(class'MockGameRulesB'));
__().unreal.AddGameRules(class'MockGameRulesA');
__().unreal.gameRules.Add(class'MockGameRulesA');
Issue(issueForAdded);
TEST_ExpectTrue(__().unreal.AreGameRulesAdded(class'MockGameRulesA'));
TEST_ExpectTrue(__().unreal.AreGameRulesAdded(class'MockGameRulesB'));
TEST_ExpectTrue(__().unreal.gameRules.AreAdded(class'MockGameRulesA'));
TEST_ExpectTrue(__().unreal.gameRules.AreAdded(class'MockGameRulesB'));
__().unreal.RemoveGameRules(class'MockGameRulesB');
__().unreal.gameRules.Remove(class'MockGameRulesB');
Issue(issueForAdded);
TEST_ExpectTrue(__().unreal.AreGameRulesAdded(class'MockGameRulesA'));
TEST_ExpectTrue(__().unreal.gameRules.AreAdded(class'MockGameRulesA'));
Issue(issueForNotAdded);
TEST_ExpectFalse(__().unreal.AreGameRulesAdded(class'MockGameRulesB'));
TEST_ExpectFalse(__().unreal.gameRules.AreAdded(class'MockGameRulesB'));
}
protected static function Test_InventoryChainFetching()
@ -137,7 +137,8 @@ protected static function Test_InventoryChainFetching()
chainEnd = chainEnd.inventory;
chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryA'));
chainEnd = chainEnd.inventory;
chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryAChild'));
chainEnd.inventory =
Inventory(__().memory.Allocate(class'MockInventoryAChild'));
chainEnd = chainEnd.inventory;
chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryB'));
chainEnd = chainEnd.inventory;

146
sources/Unreal/UnrealAPI.uc

@ -20,6 +20,41 @@
*/
class UnrealAPI extends AcediaObject;
var public GameRulesAPI gameRules;
var private LoggerAPI.Definition errNoService;
protected function Constructor()
{
gameRules = GameRulesAPI(_.memory.Allocate(class'GameRulesAPI'));
}
/**
* Signal that will be emitted every tick.
*
* [Signature]
* void <slot>(float delta)
*
* @param delta In-game time in seconds that has passed since the last tick.
* To obtain real time passed from the last tick multiply `delta` by
* `1.1 / level.timeDilation`.
*/
/* SIGNAL */
public final function Unreal_OnTick_Slot OnTick(
AcediaObject receiver)
{
local Signal signal;
local UnrealService service;
service = UnrealService(class'UnrealService'.static.Require());
if (service == none)
{
_.logger.Auto(errNoService);
return none;
}
signal = service.GetSignal(class'Unreal_OnTick_Signal');
return Unreal_OnTick_Slot(signal.NewSlot(receiver));
}
/**
* Returns current game's `LevelInfo`. Useful because `level` variable
* is not defined inside objects.
@ -32,6 +67,33 @@ public final function LevelInfo GetLevel()
return class'CoreService'.static.GetInstance().level;
}
/**
* Returns current game's `GameReplicationInfo`. Useful because `level.game`
* is not accessible inside objects.
*
* @return `GameReplicationInfo` instance for the current game. Guaranteed to
* not be `none`.
*/
public final function GameReplicationInfo GetGameRI()
{
return class'CoreService'.static.GetInstance().level.GRI;
}
/**
* Returns current game's `GameReplicationInfo` as `KFGameReplicationInfo`.
* Useful because `level.game` is not accessible inside objects and because it
* auto converts game replication info type to `KFGameReplicationInfo`, which
* virtually all mods for killing floor use (by itself or as a base class).
*
* @return `KFGameReplicationInfo` instance for the current game.
* Can be `none` only if game was modded to run a `KFGameReplicationInfo`
* not derived from `KFGameType`.
*/
public final function KFGameReplicationInfo GetKFGameRI()
{
return KFGameReplicationInfo(GetGameRI());
}
/**
* Returns current game's `GameInfo`. Useful because `level.game` is not
* accessible inside objects.
@ -71,89 +133,6 @@ public final function PlayerController GetLocalPlayer()
.GetLocalPlayerController();
}
/**
* Checks if given class of `GameRules` is currently active in `GameInfo`.
*
* @param rulesClassToCheck Class of rules to check for.
* @return `true` if `GameRules` are active and `false` otherwise.
*/
public final function bool AreGameRulesAdded(
class<GameRules> rulesClassToCheck)
{
local GameRules rulesIter;
if (rulesClassToCheck == none) {
return false;
}
rulesIter = GetGameType().gameRulesModifiers;
while (rulesIter != none)
{
if (rulesIter.class == rulesClassToCheck) {
return true;
}
rulesIter = rulesIter.nextGameRules;
}
return false;
}
/**
* Adds new `GameRules` class to the current `GameInfo`.
* Does nothing if give `GameRules` class was already added before.
*
* @param newRulesClass Class of rules to add.
* @return `true` if `GameRules` were added and `false` otherwise
* (because they were already active.)
*/
public final function bool AddGameRules(class<GameRules> newRulesClass)
{
if (AreGameRulesAdded(newRulesClass)) {
return false;
}
GetGameType().AddGameModifier(GameRules(_.memory.Allocate(newRulesClass)));
return true;
}
/**
* Removes given `GameRules` class from the current `GameInfo`,
* if they are active. Does nothing otherwise.
*
* @param rulesClassToRemove Class of rules to try and remove.
* @return `true` if `GameRules` were removed and `false` otherwise
* (if they were not active in the first place).
*/
public final function bool RemoveGameRules(class<GameRules> rulesClassToRemove)
{
local GameInfo game;
local GameRules rulesIter;
local GameRules rulesToDestroy;
if (rulesClassToRemove == none) return false;
game = GetGameType();
if (game.gameRulesModifiers == none) return false;
// Check root rules
rulesToDestroy = game.gameRulesModifiers;
if (rulesToDestroy.class == rulesClassToRemove)
{
game.gameRulesModifiers = rulesToDestroy.nextGameRules;
rulesToDestroy.Destroy();
return true;
}
// Check rest of the rules
rulesIter = game.gameRulesModifiers;
while (rulesIter != none)
{
rulesToDestroy = rulesIter.nextGameRules;
if ( rulesToDestroy != none
&& rulesToDestroy.class == rulesClassToRemove)
{
rulesIter.nextGameRules = rulesToDestroy.nextGameRules;
rulesToDestroy.Destroy();
return true;
}
rulesIter = rulesIter.nextGameRules;
}
return false;
}
/**
* Convenience method for finding a first inventory entry of the given
* class `inventoryClass` in the given inventory chain `inventoryChain`.
@ -257,4 +236,5 @@ public final function NativeActorRef ActorRef(optional Actor value)
defaultproperties
{
errNoService = (l=LOG_Error,m="`UnrealService` could not be reached.")
}

92
sources/Unreal/UnrealService.uc

@ -0,0 +1,92 @@
/**
* Service for the needs of `UnrealAPI`. Mainly tasked with creating API's
* `Signal`s.
* Copyright 2021 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 UnrealService extends Service;
struct SignalRecord
{
var class<Signal> signalClass;
var Signal instance;
};
var private array<SignalRecord> serviceSignals;
var private Unreal_OnTick_Signal onTickSignal;
protected function OnLaunch()
{
local AcediaGameRules gameRules;
CreateSignals();
_.unreal.gameRules.Add(class'AcediaGameRules');
gameRules = AcediaGameRules(
_.unreal.gameRules.FindInstance(class'AcediaGameRules'));
gameRules.Initialize(self);
}
protected function OnShutdown()
{
_.unreal.gameRules.Remove(class'AcediaGameRules');
}
private final function CreateSignals()
{
local int i;
onTickSignal = Unreal_OnTick_Signal(
_.memory.Allocate(class'Unreal_OnTick_Signal'));
for (i = 0; i < serviceSignals.length; i += 1)
{
if (serviceSignals[i].instance != none) continue;
if (serviceSignals[i].signalClass == none) continue;
serviceSignals[i].instance =
Signal(_.memory.Allocate(serviceSignals[i].signalClass));
}
}
public final function Signal GetSignal(class<Signal> requiredClass)
{
local int i;
if (requiredClass == class'Unreal_OnTick_Signal') {
return onTickSignal;
}
for (i = 0; i < serviceSignals.length; i += 1)
{
if (serviceSignals[i].signalClass == requiredClass) {
return serviceSignals[i].instance;
}
}
return none;
}
public event Tick(float delta)
{
local float dilationCoefficient;
if (onTickSignal != none)
{
dilationCoefficient = level.timeDilation / 1.1;
onTickSignal.Emit(delta, dilationCoefficient);
}
}
defaultproperties
{
serviceSignals(0) = (signalClass=class'GameRules_OnFindPlayerStart_Signal')
serviceSignals(1) = (signalClass=class'GameRules_OnOverridePickupQuery_Signal')
serviceSignals(2) = (signalClass=class'GameRules_OnNetDamage_Signal')
}
Loading…
Cancel
Save