From 5bcbc2047a8231dcf1b9562a0fd5398ed08b33d7 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Sat, 17 Apr 2021 17:51:02 +0700 Subject: [PATCH] Refactor `UnrealAPI`: move out `GameRulesAPI` --- sources/Unreal/Events/Unreal_OnTick_Signal.uc | 38 +++ sources/Unreal/Events/Unreal_OnTick_Slot.uc | 40 +++ sources/Unreal/GameRules/AcediaGameRules.uc | 109 ++++++++ .../GameRules_OnFindPlayerStart_Signal.uc | 48 ++++ .../GameRules_OnFindPlayerStart_Slot.uc | 44 ++++ .../Events/GameRules_OnNetDamage_Signal.uc | 52 ++++ .../Events/GameRules_OnNetDamage_Slot.uc | 49 ++++ .../GameRules_OnOverridePickupQuery_Signal.uc | 45 ++++ .../GameRules_OnOverridePickupQuery_Slot.uc | 46 ++++ sources/Unreal/GameRules/GameRulesAPI.uc | 232 ++++++++++++++++++ sources/Unreal/Tests/TEST_UnrealAPI.uc | 67 ++--- sources/Unreal/UnrealAPI.uc | 146 +++++------ sources/Unreal/UnrealService.uc | 92 +++++++ 13 files changed, 892 insertions(+), 116 deletions(-) create mode 100644 sources/Unreal/Events/Unreal_OnTick_Signal.uc create mode 100644 sources/Unreal/Events/Unreal_OnTick_Slot.uc create mode 100644 sources/Unreal/GameRules/AcediaGameRules.uc create mode 100644 sources/Unreal/GameRules/Events/GameRules_OnFindPlayerStart_Signal.uc create mode 100644 sources/Unreal/GameRules/Events/GameRules_OnFindPlayerStart_Slot.uc create mode 100644 sources/Unreal/GameRules/Events/GameRules_OnNetDamage_Signal.uc create mode 100644 sources/Unreal/GameRules/Events/GameRules_OnNetDamage_Slot.uc create mode 100644 sources/Unreal/GameRules/Events/GameRules_OnOverridePickupQuery_Signal.uc create mode 100644 sources/Unreal/GameRules/Events/GameRules_OnOverridePickupQuery_Slot.uc create mode 100644 sources/Unreal/GameRules/GameRulesAPI.uc create mode 100644 sources/Unreal/UnrealService.uc diff --git a/sources/Unreal/Events/Unreal_OnTick_Signal.uc b/sources/Unreal/Events/Unreal_OnTick_Signal.uc new file mode 100644 index 0000000..62cdbbe --- /dev/null +++ b/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 . + */ +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' +} \ No newline at end of file diff --git a/sources/Unreal/Events/Unreal_OnTick_Slot.uc b/sources/Unreal/Events/Unreal_OnTick_Slot.uc new file mode 100644 index 0000000..3a4ad96 --- /dev/null +++ b/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 . + */ +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 +{ +} \ No newline at end of file diff --git a/sources/Unreal/GameRules/AcediaGameRules.uc b/sources/Unreal/GameRules/AcediaGameRules.uc new file mode 100644 index 0000000..8552e9f --- /dev/null +++ b/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 . + */ +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) +{ + 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 +{ +} \ No newline at end of file diff --git a/sources/Unreal/GameRules/Events/GameRules_OnFindPlayerStart_Signal.uc b/sources/Unreal/GameRules/Events/GameRules_OnFindPlayerStart_Signal.uc new file mode 100644 index 0000000..61cba42 --- /dev/null +++ b/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 . + */ +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' +} \ No newline at end of file diff --git a/sources/Unreal/GameRules/Events/GameRules_OnFindPlayerStart_Slot.uc b/sources/Unreal/GameRules/Events/GameRules_OnFindPlayerStart_Slot.uc new file mode 100644 index 0000000..2f13983 --- /dev/null +++ b/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 . + */ +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 +{ +} \ No newline at end of file diff --git a/sources/Unreal/GameRules/Events/GameRules_OnNetDamage_Signal.uc b/sources/Unreal/GameRules/Events/GameRules_OnNetDamage_Signal.uc new file mode 100644 index 0000000..d921073 --- /dev/null +++ b/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 . + */ +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) +{ + 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' +} \ No newline at end of file diff --git a/sources/Unreal/GameRules/Events/GameRules_OnNetDamage_Slot.uc b/sources/Unreal/GameRules/Events/GameRules_OnNetDamage_Slot.uc new file mode 100644 index 0000000..97d86aa --- /dev/null +++ b/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 . + */ +class GameRules_OnNetDamage_Slot extends Slot; + +delegate int connect( + int originalDamage, + int damage, + Pawn injured, + Pawn instigatedBy, + Vector hitLocation, + out Vector momentum, + class damageType) +{ + DummyCall(); + // Do not alter any already made modifications + return damage; +} + +protected function Constructor() +{ + connect = none; +} + +protected function Finalizer() +{ + super.Finalizer(); + connect = none; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Unreal/GameRules/Events/GameRules_OnOverridePickupQuery_Signal.uc b/sources/Unreal/GameRules/Events/GameRules_OnOverridePickupQuery_Signal.uc new file mode 100644 index 0000000..ff54155 --- /dev/null +++ b/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 . + */ +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' +} \ No newline at end of file diff --git a/sources/Unreal/GameRules/Events/GameRules_OnOverridePickupQuery_Slot.uc b/sources/Unreal/GameRules/Events/GameRules_OnOverridePickupQuery_Slot.uc new file mode 100644 index 0000000..af1a12e --- /dev/null +++ b/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 . + */ +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 +{ +} \ No newline at end of file diff --git a/sources/Unreal/GameRules/GameRulesAPI.uc b/sources/Unreal/GameRules/GameRulesAPI.uc new file mode 100644 index 0000000..6b19cfb --- /dev/null +++ b/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 . + */ +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 ( + * 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 (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 (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 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 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 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 rulesClassToCheck) +{ + return (FindInstance(rulesClassToCheck) != none); +} + +defaultproperties +{ + errNoService = (l=LOG_Error,m="`UnrealService` could not be reached.") +} \ No newline at end of file diff --git a/sources/Unreal/Tests/TEST_UnrealAPI.uc b/sources/Unreal/Tests/TEST_UnrealAPI.uc index a3fa8b5..d4db865 100644 --- a/sources/Unreal/Tests/TEST_UnrealAPI.uc +++ b/sources/Unreal/Tests/TEST_UnrealAPI.uc @@ -19,10 +19,10 @@ */ class TEST_UnrealAPI extends TestCase; -protected static function int CountRulesAmount(class gameRulesClass) +protected static function int CountRulesAmount(class 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 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; diff --git a/sources/Unreal/UnrealAPI.uc b/sources/Unreal/UnrealAPI.uc index c487216..38d2d79 100644 --- a/sources/Unreal/UnrealAPI.uc +++ b/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 (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 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 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 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.") } \ No newline at end of file diff --git a/sources/Unreal/UnrealService.uc b/sources/Unreal/UnrealService.uc new file mode 100644 index 0000000..f57bcb0 --- /dev/null +++ b/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 . + */ +class UnrealService extends Service; + +struct SignalRecord +{ + var class signalClass; + var Signal instance; +}; +var private array 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 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') +} \ No newline at end of file