From ce3e06041627501b58d51be8d9e4e3c19b528b66 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Sun, 4 Apr 2021 03:01:03 +0700 Subject: [PATCH] Add `UnrealAPI` with low-level auxiliary methods --- sources/Global.uc | 3 +- sources/Manifest.uc | 31 +-- sources/Unreal/Tests/MockGameRulesA.uc | 25 ++ sources/Unreal/Tests/MockGameRulesB.uc | 25 ++ sources/Unreal/Tests/MockInventoryA.uc | 25 ++ sources/Unreal/Tests/MockInventoryAChild.uc | 25 ++ sources/Unreal/Tests/MockInventoryB.uc | 25 ++ sources/Unreal/Tests/TEST_UnrealAPI.uc | 230 ++++++++++++++++++ sources/Unreal/UnrealAPI.uc | 246 ++++++++++++++++++++ 9 files changed, 619 insertions(+), 16 deletions(-) create mode 100644 sources/Unreal/Tests/MockGameRulesA.uc create mode 100644 sources/Unreal/Tests/MockGameRulesB.uc create mode 100644 sources/Unreal/Tests/MockInventoryA.uc create mode 100644 sources/Unreal/Tests/MockInventoryAChild.uc create mode 100644 sources/Unreal/Tests/MockInventoryB.uc create mode 100644 sources/Unreal/Tests/TEST_UnrealAPI.uc create mode 100644 sources/Unreal/UnrealAPI.uc diff --git a/sources/Global.uc b/sources/Global.uc index da1c9e1..c7b6767 100644 --- a/sources/Global.uc +++ b/sources/Global.uc @@ -29,7 +29,7 @@ var public RefAPI ref; var public BoxAPI box; var public LoggerAPI logger; var public CollectionsAPI collections; -//var public JSONAPI json; +var public UnrealAPI unreal; var public AliasesAPI alias; var public TextAPI text; var public MemoryAPI memory; @@ -59,6 +59,7 @@ protected function Initialize() box = BoxAPI(memory.Allocate(class'BoxAPI')); text = TextAPI(memory.Allocate(class'TextAPI')); collections = CollectionsAPI(memory.Allocate(class'CollectionsAPI')); + unreal = UnrealAPI(memory.Allocate(class'UnrealAPI')); logger = LoggerAPI(memory.Allocate(class'LoggerAPI')); alias = AliasesAPI(memory.Allocate(class'AliasesAPI')); console = ConsoleAPI(memory.Allocate(class'ConsoleAPI')); diff --git a/sources/Manifest.uc b/sources/Manifest.uc index 921527a..d0fdf86 100644 --- a/sources/Manifest.uc +++ b/sources/Manifest.uc @@ -34,19 +34,20 @@ defaultproperties testCases(0) = class'TEST_Base' testCases(1) = class'TEST_Boxes' testCases(2) = class'TEST_Refs' - testCases(3) = class'TEST_Aliases' - testCases(4) = class'TEST_ColorAPI' - testCases(5) = class'TEST_Text' - testCases(6) = class'TEST_TextAPI' - testCases(7) = class'TEST_Parser' - testCases(8) = class'TEST_JSON' - testCases(9) = class'TEST_TextCache' - testCases(10) = class'TEST_User' - testCases(11) = class'TEST_Memory' - testCases(12) = class'TEST_DynamicArray' - testCases(13) = class'TEST_AssociativeArray' - testCases(14) = class'TEST_Iterator' - testCases(15) = class'TEST_Command' - testCases(16) = class'TEST_CommandDataBuilder' - testCases(17) = class'TEST_LogMessage' + testCases(3) = class'TEST_UnrealAPI' + testCases(4) = class'TEST_Aliases' + testCases(5) = class'TEST_ColorAPI' + testCases(6) = class'TEST_Text' + testCases(7) = class'TEST_TextAPI' + testCases(8) = class'TEST_Parser' + testCases(9) = class'TEST_JSON' + testCases(10) = class'TEST_TextCache' + testCases(11) = class'TEST_User' + testCases(12) = class'TEST_Memory' + testCases(13) = class'TEST_DynamicArray' + testCases(14) = class'TEST_AssociativeArray' + testCases(15) = class'TEST_Iterator' + testCases(16) = class'TEST_Command' + testCases(17) = class'TEST_CommandDataBuilder' + testCases(18) = class'TEST_LogMessage' } \ No newline at end of file diff --git a/sources/Unreal/Tests/MockGameRulesA.uc b/sources/Unreal/Tests/MockGameRulesA.uc new file mode 100644 index 0000000..8ce9444 --- /dev/null +++ b/sources/Unreal/Tests/MockGameRulesA.uc @@ -0,0 +1,25 @@ +/** + * Mock `GameRules` class for testing `UnrealAPI` and it' methods for + * adding / removing `GameRules`. + * 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 MockGameRulesA extends GameRules; + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Unreal/Tests/MockGameRulesB.uc b/sources/Unreal/Tests/MockGameRulesB.uc new file mode 100644 index 0000000..b90c33f --- /dev/null +++ b/sources/Unreal/Tests/MockGameRulesB.uc @@ -0,0 +1,25 @@ +/** + * Mock `GameRules` class for testing `UnrealAPI` and it's methods for + * adding / removing `GameRules`. + * 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 MockGameRulesB extends GameRules; + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Unreal/Tests/MockInventoryA.uc b/sources/Unreal/Tests/MockInventoryA.uc new file mode 100644 index 0000000..1f0c5f2 --- /dev/null +++ b/sources/Unreal/Tests/MockInventoryA.uc @@ -0,0 +1,25 @@ +/** + * Mock inventory class for testing `UnrealAPI` and it's methods for + * adding / removing `GameRules`. + * 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 MockInventoryA extends Inventory; + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Unreal/Tests/MockInventoryAChild.uc b/sources/Unreal/Tests/MockInventoryAChild.uc new file mode 100644 index 0000000..9ae493b --- /dev/null +++ b/sources/Unreal/Tests/MockInventoryAChild.uc @@ -0,0 +1,25 @@ +/** + * Mock inventory class for testing `UnrealAPI` and it's methods for + * adding / removing `GameRules`. + * 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 MockInventoryAChild extends MockInventoryA; + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Unreal/Tests/MockInventoryB.uc b/sources/Unreal/Tests/MockInventoryB.uc new file mode 100644 index 0000000..49ba579 --- /dev/null +++ b/sources/Unreal/Tests/MockInventoryB.uc @@ -0,0 +1,25 @@ +/** + * Mock inventory class for testing `UnrealAPI` and it's methods for + * adding / removing `GameRules`. + * 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 MockInventoryB extends Inventory; + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Unreal/Tests/TEST_UnrealAPI.uc b/sources/Unreal/Tests/TEST_UnrealAPI.uc new file mode 100644 index 0000000..a3fa8b5 --- /dev/null +++ b/sources/Unreal/Tests/TEST_UnrealAPI.uc @@ -0,0 +1,230 @@ +/** + * Set of tests for `UnrealAPI` class. + * 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 TEST_UnrealAPI extends TestCase; + +protected static function int CountRulesAmount(class gameRulesClass) +{ + local int counter; + local GameRules rulesIter; + if (gameRulesClass == none) { + return 0; + } + rulesIter = __().unreal.GetGameType().gameRulesModifiers; + while (rulesIter != none) + { + if (rulesIter.class == gameRulesClass) { + counter += 1; + } + rulesIter = rulesIter.nextGameRules; + } + return counter; +} + +protected static function TESTS() +{ + Test_GameType(); + Test_GameRules(); + Test_InventoryChainFetching(); +} + +protected static function Test_GameType() +{ + Context("Testing methods for returning `GameType` class."); + Issue("`GetGameType()` returns `none`."); + TEST_ExpectNotNone(__().unreal.GetGameType()); + Issue("`GetKFGameType()` returns `none`."); + TEST_ExpectNotNone(__().unreal.GetKFGameType()); + Issue("`GetGameType()` and `GetKFGameType()` return different values."); + TEST_ExpectTrue(__().unreal.GetGameType() == __().unreal.GetKFGameType()); +} + +protected static function Test_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'); + TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesA') == 1); + + __().unreal.AddGameRules(class'MockGameRulesA'); + Issue("Calling `AddGameRules()` twice leads to rule duplication."); + TEST_ExpectFalse(CountRulesAmount(class'MockGameRulesA') > 1); + Issue("Calling `AddGameRules()` 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'); + TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesA') == 1); + TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesB') == 1); + + Issue("Adding/removing rules with `RemoveGameRules()` leads to" @ + "unexpected results."); + __().unreal.RemoveGameRules(class'MockGameRulesB'); + TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesA') == 1); + TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesB') == 0); + __().unreal.AddGameRules(class'MockGameRulesB'); + __().unreal.RemoveGameRules(class'MockGameRulesA'); + TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesA') == 0); + TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesB') == 1); + __().unreal.RemoveGameRules(class'MockGameRulesB'); + TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesA') == 0); + TEST_ExpectTrue(CountRulesAmount(class'MockGameRulesB') == 0); +} + +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" @ + "are not currently added."; + __().unreal.RemoveGameRules(class'MockGameRulesA'); + __().unreal.RemoveGameRules(class'MockGameRulesB'); + Issue(issueForNotAdded); + TEST_ExpectFalse(__().unreal.AreGameRulesAdded(class'MockGameRulesA')); + TEST_ExpectFalse(__().unreal.AreGameRulesAdded(class'MockGameRulesB')); + + __().unreal.AddGameRules(class'MockGameRulesB'); + Issue(issueForNotAdded); + TEST_ExpectFalse(__().unreal.AreGameRulesAdded(class'MockGameRulesA')); + Issue(issueForAdded); + TEST_ExpectTrue(__().unreal.AreGameRulesAdded(class'MockGameRulesB')); + + __().unreal.AddGameRules(class'MockGameRulesA'); + Issue(issueForAdded); + TEST_ExpectTrue(__().unreal.AreGameRulesAdded(class'MockGameRulesA')); + TEST_ExpectTrue(__().unreal.AreGameRulesAdded(class'MockGameRulesB')); + + __().unreal.RemoveGameRules(class'MockGameRulesB'); + Issue(issueForAdded); + TEST_ExpectTrue(__().unreal.AreGameRulesAdded(class'MockGameRulesA')); + Issue(issueForNotAdded); + TEST_ExpectFalse(__().unreal.AreGameRulesAdded(class'MockGameRulesB')); +} + +protected static function Test_InventoryChainFetching() +{ + local Inventory chainStart, chainEnd; + // a - B - A - a - B - a - A, + // where A = `MockInventoryA` + // a = `MockInventoryAChild` + // B = `MockInventoryB` + chainStart = Inventory(__().memory.Allocate(class'MockInventoryAChild')); + chainEnd = chainStart; + chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryB')); + chainEnd = chainEnd.inventory; + chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryA')); + chainEnd = chainEnd.inventory; + chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryAChild')); + chainEnd = chainEnd.inventory; + chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryB')); + chainEnd = chainEnd.inventory; + chainEnd.inventory = + Inventory(__().memory.Allocate(class'MockInventoryAChild')); + chainEnd = chainEnd.inventory; + chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryA')); + chainEnd = chainEnd.inventory; + Context("Testing auxiliary methods for working with inventory chains."); + SubTest_InventoryChainFetchingSingle(chainStart); + SubTest_InventoryChainFetchingMany(chainStart); +} + +protected static function SubTest_InventoryChainFetchingSingle(Inventory chain) +{ + Issue("Does not find correct first entry inside the inventory chain."); + TEST_ExpectTrue( + __().unreal.GetInventoryFrom(class'MockInventoryA', chain) + == chain.inventory.inventory); + TEST_ExpectTrue( + __().unreal.GetInventoryFrom(class'MockInventoryB', chain) + == chain.inventory); + TEST_ExpectTrue( + __().unreal.GetInventoryFrom(class'MockInventoryAChild', chain) + == chain); + + Issue("Incorrectly finds missing inventory entries."); + TEST_ExpectNone(__().unreal.GetInventoryFrom(none, chain)); + TEST_ExpectNone(__().unreal.GetInventoryFrom(class'Winchester', chain)); + + Issue("Does not find correct first entry inside the inventory chain when" @ + "allowing for child classes."); + TEST_ExpectTrue( + __().unreal.GetInventoryFrom(class'MockInventoryA', chain, true) + == chain); + TEST_ExpectTrue( + __().unreal.GetInventoryFrom(class'MockInventoryB', chain, true) + == chain.inventory); + TEST_ExpectTrue( + __().unreal.GetInventoryFrom(class'MockInventoryAChild', chain, true) + == chain); + + Issue("Incorrectly finds missing inventory entries when allowing for" @ + "child classes."); + TEST_ExpectNone(__().unreal.GetInventoryFrom(none, chain, true)); + TEST_ExpectNone(__().unreal.GetInventoryFrom( class'Winchester', chain, + true)); +} + +protected static function SubTest_InventoryChainFetchingMany(Inventory chain) +{ + local array result; + Issue("Does not find correct entries inside the inventory chain."); + result = __().unreal.GetAllInventoryFrom(class'MockInventoryB', chain); + TEST_ExpectTrue(result.length == 2); + TEST_ExpectTrue(result[0] == chain.inventory); + TEST_ExpectTrue(result[1] == chain.inventory.inventory.inventory.inventory); + + Issue("Does not find correct entries inside the inventory chain when" @ + "allowing for child classes."); + result = + __().unreal.GetAllInventoryFrom(class'MockInventoryB', chain, true); + TEST_ExpectTrue(result.length == 2); + TEST_ExpectTrue(result[0] == chain.inventory); + TEST_ExpectTrue(result[1] == chain.inventory.inventory.inventory.inventory); + result = + __().unreal.GetAllInventoryFrom(class'MockInventoryA', chain, true); + TEST_ExpectTrue(result.length == 5); + TEST_ExpectTrue(result[0] == chain); + TEST_ExpectTrue(result[1] == chain.inventory.inventory); + TEST_ExpectTrue(result[2] == chain.inventory.inventory.inventory); + TEST_ExpectTrue( + result[3] + == chain.inventory.inventory.inventory.inventory.inventory); + TEST_ExpectTrue( + result[4] + == chain.inventory.inventory.inventory.inventory.inventory.inventory); + + Issue("Does not return empty array for non-existing inventory class."); + result = __().unreal.GetAllInventoryFrom(class'Winchester', chain); + TEST_ExpectTrue(result.length == 0); + result = __().unreal.GetAllInventoryFrom(class'Winchester', chain, true); + TEST_ExpectTrue(result.length == 0); +} + +defaultproperties +{ + caseName = "UnrealAPI" + caseGroup = "Unreal" +} \ No newline at end of file diff --git a/sources/Unreal/UnrealAPI.uc b/sources/Unreal/UnrealAPI.uc new file mode 100644 index 0000000..63f75dc --- /dev/null +++ b/sources/Unreal/UnrealAPI.uc @@ -0,0 +1,246 @@ +/** + * Low-level API that provides set of utility methods for working with + * unreal script classes. + * 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 UnrealAPI extends AcediaObject; + +/** + * Returns current game's `LevelInfo`. Useful because `level` variable + * is not defined inside objects. + * + * @return `LevelInfo` instance for the current game. Guaranteed to + * not be `none`. + */ +public final function LevelInfo GetLevel() +{ + return class'CoreService'.static.GetInstance().level; +} + +/** + * Returns current game's `GameInfo`. Useful because `level.game` is not + * accessible inside objects. + * + * @return `GameInfo` instance for the current game. Guaranteed to + * not be `none`. + */ +public final function GameInfo GetGameType() +{ + return class'CoreService'.static.GetInstance().level.game; +} + +/** + * Returns current game's `GameInfo` as `KFGameType`. Useful because + * `level.game` is not accessible inside objects and because it auto converts + * game type to `KFGameType`, which virtually all mods for killing floor use + * (by itself or as a base class). + * + * @return `KFGameType` instance for the current game. Can be `none` only if + * game was modded to run a `GameInfo` not derived from `KFGameType`. + */ +public final function KFGameType GetKFGameType() +{ + return KFGameType(GetGameType()); +} + +/** + * Returns current local player's `Controller`. Useful because `level` + * is not accessible inside objects. + * + * @return `PlayerController` instance for the local player. `none` iff run on + * dedicated servers. + */ +public final function PlayerController GetLocalPlayer() +{ + return class'CoreService'.static.GetInstance().level + .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`. + * + * Inventory is stored as a linked list, where next inventory item is available + * through the `inventory` reference. This method follows this list, starting + * from `inventoryChain` until it finds `Inventory` of the appropriate class + * or reaches the end of the list. + * + * @param inventoryClass Class of the inventory we are interested in. + * @param inventoryChain Inventory chain in which we should search for + * the given class. + * @param acceptChildClass `true` if method should also return any + * `Inventory` of class derived from `inventoryClass` and `false` if + * we want given class specifically (default). + * @return First inventory from `inventoryChain` that matches given + * `inventoryClass` class (whether exactly or as a child class, + * in case `acceptChildClass == true`). + */ +public final function Inventory GetInventoryFrom( + class inventoryClass, + Inventory inventoryChain, + optional bool acceptChildClass) +{ + if (inventoryClass == none) { + return none; + } + while (inventoryChain != none) + { + if (inventoryChain.class == inventoryClass) { + return inventoryChain; + } + if ( acceptChildClass + && ClassIsChildOf(inventoryChain.class, inventoryClass)) + { + return inventoryChain; + } + inventoryChain = inventoryChain.inventory; + } + return none; +} + +/** + * Convenience method for finding a all inventory entries of the given + * class `inventoryClass` in the given inventory chain `inventoryChain`. + * + * Inventory is stored as a linked list, where next inventory item is available + * through the `inventory` reference. This method follows this list, starting + * from `inventoryChain` until the end of the list. + * + * @param inventoryClass Class of the inventory we are interested in. + * @param inventoryChain Inventory chain in which we should search for + * the given class. + * @param acceptChildClass `true` if method should also return any + * `Inventory` of class derived from `inventoryClass` and `false` if + * we want given class specifically (default). + * @return Array of inventory items from `inventoryChain` that match given + * `inventoryClass` class (whether exactly or as a child class, + * in case `acceptChildClass == true`). + */ +public final function array GetAllInventoryFrom( + class inventoryClass, + Inventory inventoryChain, + optional bool acceptChildClass) +{ + local bool shouldAdd; + local array result; + if (inventoryClass == none) { + return result; + } + while (inventoryChain != none) + { + shouldAdd = false; + if (inventoryChain.class == inventoryClass) { + shouldAdd = true; + } + else if (acceptChildClass) { + shouldAdd = ClassIsChildOf(inventoryChain.class, inventoryClass); + } + if (shouldAdd) { + result[result.length] = inventoryChain; + } + inventoryChain = inventoryChain.inventory; + } + return result; +} + +defaultproperties +{ +} \ No newline at end of file