From f18180a8f84c34c249f689f9e1b99124560bf18a Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Tue, 30 Nov 2021 00:20:20 +0700 Subject: [PATCH] Add prototype interfaces for inventory management --- .../BaseClasses/KillingFloor/KFFrontend.uc | 14 + .../BaseImplementation/EKFInventory.uc | 417 ++++++++++++++++++ .../BaseImplementation/EKFItemTemplateInfo.uc | 81 ++++ .../BaseImplementation/EKFWeapon.uc | 150 +++++++ sources/Gameplay/KF1Frontend/KF1_Frontend.uc | 10 + sources/Players/APlayer.uc | 14 + sources/Players/Inventory/EInventory.uc | 291 ++++++++++++ sources/Players/Inventory/EItem.uc | 179 ++++++++ .../Players/Inventory/EItemTemplateInfo.uc | 67 +++ sources/Players/PlayerService.uc | 19 +- 10 files changed, 1241 insertions(+), 1 deletion(-) create mode 100644 sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc create mode 100644 sources/Gameplay/KF1Frontend/BaseImplementation/EKFItemTemplateInfo.uc create mode 100644 sources/Gameplay/KF1Frontend/BaseImplementation/EKFWeapon.uc create mode 100644 sources/Players/Inventory/EInventory.uc create mode 100644 sources/Players/Inventory/EItem.uc create mode 100644 sources/Players/Inventory/EItemTemplateInfo.uc diff --git a/sources/Gameplay/BaseClasses/KillingFloor/KFFrontend.uc b/sources/Gameplay/BaseClasses/KillingFloor/KFFrontend.uc index d17d2a5..75310bd 100644 --- a/sources/Gameplay/BaseClasses/KillingFloor/KFFrontend.uc +++ b/sources/Gameplay/BaseClasses/KillingFloor/KFFrontend.uc @@ -36,6 +36,20 @@ protected function Finalizer() trading = none; } +/** + * Returns an instance of information about item template with a given name + * `templateName`. + * + * @param templateName Name of the template to return info for. + * @return Template info for item template named `templateName`. + * `none` if item template with given name does not exist or passed + * `templateName` is equal to `none`. + */ +public function EItemTemplateInfo GetItemTemplateInfo(Text templateName) +{ + return none; +} + defaultproperties { tradingClass = none diff --git a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc new file mode 100644 index 0000000..1623425 --- /dev/null +++ b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc @@ -0,0 +1,417 @@ +/** + * Player's inventory implementation for classic Killing Floor that changes + * as little as possible and only on request from another mod, otherwise not + * altering gameplay at all. + * 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 EKFInventory extends EInventory + config(AcediaSystem_KF1Frontend); + +struct DualiesPair +{ + var class single; + var class dual; +}; + +var private APlayer inventoryOwner; +var private config array dualiesClasses; + +protected function Finalizer() +{ + inventoryOwner = none; +} + +public function Initialize(APlayer player) +{ + if (inventoryOwner != none) { + return; + } + inventoryOwner = player; +} + +private function Pawn GetOwnerPawn() +{ + local PlayerService service; + service = PlayerService(class'PlayerService'.static.Require()); + if (service == none) { + return none; + } + return service.GetPawn(inventoryOwner); +} + +public function bool Add(EItem newItem, optional bool forceAddition) +{ + local Pawn pawn; + local EKFWeapon kfWeaponItem; + local KFWeapon kfWeapon; + if (!CanAdd(newItem, forceAddition)) return false; + kfWeaponItem = EKFWeapon(newItem); + if (kfWeaponItem == none) return false; + pawn = GetOwnerPawn(); + if (pawn == none) return false; + kfWeapon = kfWeaponItem.GetNativeInstance(); + if (kfWeapon == none) return false; + + kfWeapon.GiveTo(pawn); + return true; +} + +public function bool AddTemplate( + Text newItemTemplate, + optional bool forceAddition) +{ + local Pawn pawn; + local KFWeapon newWeapon; + if (newItemTemplate == none) return false; + if (!CanAddTemplate(newItemTemplate, forceAddition)) return false; + pawn = GetOwnerPawn(); + if (pawn == none) return false; + + newWeapon = KFWeapon(_.memory.AllocateByReference(newItemTemplate)); + if (newWeapon != none) + { + _.unreal.GetKFGameType().WeaponSpawned(newWeapon); + newWeapon.GiveTo(pawn); + return true; + } + return false; +} + +public function bool CanAdd(EItem itemToCheck, optional bool forceAddition) +{ + local EKFWeapon kfWeaponItem; + local KFWeapon kfWeapon; + kfWeaponItem = EKFWeapon(itemToCheck); + if (kfWeaponItem == none) return false; // can only add weapons + kfWeapon = kfWeaponItem.GetNativeInstance(); + if (kfWeapon == none) return false; // dead `EKFWeapon` object + + return CanAddWeaponClass(kfWeapon.class, forceAddition); +} + +public function bool CanAddTemplate( + Text itemTemplateToCheck, + optional bool forceAddition) +{ + local class kfWeaponClass; + // Can only add weapons for now + kfWeaponClass = class(_.memory.LoadClass(itemTemplateToCheck)); + return CanAddWeaponClass(kfWeaponClass, forceAddition); +} + +public function bool CanAddWeaponClass( + class kfWeaponClass, + optional bool forceAddition) +{ + local KFPawn kfPawn; + if (kfWeaponClass == none) return false; + kfPawn = KFPawn(GetOwnerPawn()); + if (kfPawn == none) return false; + + if (!forceAddition && !kfPawn.CanCarry(kfWeaponClass.default.weight)) { + return false; + } + if (kfPawn.FindInventoryType(kfWeaponClass) != none) { + return false; + } + if (!forceAddition && HasSameTypeWeapons(kfWeaponClass, kfPawn)) { + return false; + } + return true; +} + +private function bool HasSameTypeWeapons( + class kfWeaponClass, + Pawn pawn) +{ + local Inventory nextInventory; + local class itemRoot, nextRoot; + nextInventory = pawn.inventory; + itemRoot = GetRootPickupClass(kfWeaponClass); + while (nextInventory != none) + { + nextRoot = GetRootPickupClass(class(nextInventory.class)); + if (itemRoot == nextRoot) { + return true; + } + nextInventory = nextInventory.inventory; + } + return false; +} + +// Returns a root pickup class. +// For non-dual weapons, root class is defined as either: +// 1. the first variant (reskin), if there are variants for that weapon; +// 2. and as the class itself, if there are no variants. +// For dual weapons (all dual pistols) root class is defined as +// a root of their single version. +// This definition is useful because: +// ~ Vanilla game rules are such that player can only have two weapons +// in the inventory if they have different roots; +// ~ Root is easy to find. +private final function class GetRootPickupClass( + class weapon) +{ + local int i; + local class root; + if (weapon == none) return none; + // Start with a pickup of the given weapons + root = class(weapon.default.pickupClass); + if (root == none) return none; + + // In case it's a dual version - find corresponding single pickup class + // (it's root would be the same). + for (i = 0; i < dualiesClasses.length; i += 1) + { + if (dualiesClasses[i].dual == root) + { + root = dualiesClasses[i].single; + break; + } + } + // Take either first variant class or the class itself, - + // it's going to be root by definition. + if (root.default.variantClasses.length > 0) { + root = class(root.default.variantClasses[0]); + } + return root; +} + +public function bool Remove( + EItem itemToRemove, + optional bool keepItem, + optional bool forceRemoval) +{ + local bool removedItem; + local float passedTime; + local Pawn pawn; + local Inventory nextInventory; + local EKFWeapon kfWeaponItem; + local KFWeapon kfWeapon; + kfWeaponItem = EKFWeapon(itemToRemove); + if (kfWeaponItem == none) return false; + pawn = GetOwnerPawn(); + if (pawn == none) return false; + if (pawn.inventory == none) return false; + kfWeapon = kfWeaponItem.GetNativeInstance(); + if (kfWeapon == none) return false; + if (!forceRemoval && kfWeapon.bKFNeverThrow) return false; + + passedTime = _.unreal.GetLevel().timeSeconds - 1; + nextInventory = pawn.inventory; + while (nextInventory.inventory != none) + { + if (nextInventory.inventory == kfWeapon) + { + nextInventory.inventory = kfWeapon.inventory; + kfWeapon.inventory = none; + nextInventory.netUpdateTime = passedTime; + kfWeapon.netUpdateTime = passedTime; + kfWeapon.Destroy(); + removedItem = true; + } + else { + nextInventory = nextInventory.inventory; + } + } + return removedItem; +} + +public function bool RemoveTemplate( + Text itemTemplateToRemove, + optional bool keepItem, + optional bool forceRemoval, + optional bool removeAll) +{ + local bool canRemoveInventory; + local bool removedItem; + local float passedTime; + local Pawn pawn; + local Inventory nextInventory; + local KFWeapon nextKFWeapon; + local class kfWeaponClass; + pawn = GetOwnerPawn(); + if (pawn == none) return false; + if (pawn.inventory == none) return false; + kfWeaponClass = class(_.memory.LoadClass(itemTemplateToRemove)); + if (kfWeaponClass == none) return false; + if (!forceRemoval && kfWeaponClass.default.bKFNeverThrow) return false; + + passedTime = _.unreal.GetLevel().timeSeconds - 1; + nextInventory = pawn.inventory; + while (nextInventory.inventory != none) + { + canRemoveInventory = true; + if (!forceRemoval) + { + nextKFWeapon = KFWeapon(nextInventory.inventory); + if (nextKFWeapon != none && nextKFWeapon.bKFNeverThrow) { + canRemoveInventory = false; + } + } + if ( canRemoveInventory + && nextInventory.inventory.class == kfWeaponClass) + { + nextInventory.inventory = nextKFWeapon.inventory; + nextKFWeapon.inventory = none; + nextInventory.netUpdateTime = passedTime; + nextKFWeapon.netUpdateTime = passedTime; + nextKFWeapon.Destroy(); + removedItem = true; + if (!removeAll) { + return true; + } + } + else { + nextInventory = nextInventory.inventory; + } + } + return removedItem; +} + +public function bool RemoveAll( + optional bool keepItems, + optional bool forceRemoval) +{ + local int i; + local Pawn pawn; + local KFWeapon kfWeapon; + local Inventory nextInventory; + local class destroyedClass; + local array inventoryToRemove; + pawn = GetOwnerPawn(); + if (pawn == none) return false; + if (pawn.inventory == none) return false; + + nextInventory = pawn.inventory; + while (nextInventory != none) + { + kfWeapon = KFWeapon(nextInventory); + if (kfWeapon == none) + { + nextInventory = nextInventory.inventory; + continue; // TODO: handle non-weapons differently + } + if (forceRemoval || !kfWeapon.bKFNeverThrow) { + inventoryToRemove[inventoryToRemove.length] = nextInventory; + } + nextInventory = nextInventory.inventory; + } + for(i = 0; i < inventoryToRemove.length; i += 1) + { + if (inventoryToRemove[i] == none) { + continue; + } + destroyedClass = class(inventoryToRemove[i].class); + inventoryToRemove[i].Destroyed(); + inventoryToRemove[i].Destroy(); + _.unreal.GetKFGameType().WeaponDestroyed(destroyedClass); + } + return (inventoryToRemove.length > 0); +} + +/** + * Checks whether caller `EInventory` contains given `itemToCheck`. + * + * @param itemToCheck `EItem` we want to check for belonging to the caller + * `EInventory`. + * @result `true` if item does belong to the inventory and `false` otherwise. + */ +public function bool Contains(EItem itemToCheck) +{ + return false; +} + +/** + * Returns array with all `EItem`s contained inside the caller `EInventory`. + * + * @return Array with all `EItem`s contained inside the caller `EInventory`. + */ +public function array GetAllItems() +{ + local array emptyArray; + return emptyArray; +} + +/** + * Returns array with all `EItem`s contained inside the caller `EInventory` + * that has specified tag `tag`. + * + * @param tag Tag, which items we want to get. + * @return Array with all `EItem`s contained inside the caller `EInventory` + * that has specified tag `tag`. + */ +public function array GetTagItems(Text tag) +{ + local array emptyArray; + return emptyArray; +} + +/** + * Returns `EItem` contained inside the caller `EInventory` that has specified + * tag `tag`. + * + * If several `EItem`s inside caller `EInventory` have specified tag, + * inventory system can pick one arbitrarily (can be based on simple + * convenience of implementation). Returned value does not have to + * be stable (the same after repeated calls). + * + * @param tag Tag, which item we want to get. + * @return `EItem` contained inside the caller `EInventory` that belongs to + * the specified tag `tag`. + */ +public function EItem GetTagItem(Text tag) { return none; } + +/** + * Returns array with all `EItem`s contained inside the caller `EInventory` + * that originated from the specified template `template`. + * + * @param template Template, that items we want to get originated from. + * @return Array with all `EItem`s contained inside the caller `EInventory` + * that originated from the specified template `template`. + */ +public function array GetTemplateItems(Text template) +{ + local array emptyArray; + return emptyArray; +} + +/** + * Returns array with all `EItem`s contained inside the caller `EInventory` + * that originated from the specified template `template`. + * + * If several `EItem`s inside caller `EInventory` originated from + * that template, inventory system can pick one arbitrarily (can be based on + * simple convenience of implementation). Returned value does not have to + * be stable (the same after repeated calls). + * + * @param template Template, that item we want to get originated from. + * @return `EItem`s contained inside the caller `EInventory` that originated + * from the specified template `template`. + */ +public function EItem GetTemplateItem(Text template) { return none; } + +defaultproperties +{ + dualiesClasses(0)=(single=class'KFMod.SinglePickup',dual=class'KFMod.DualiesPickup') + dualiesClasses(1)=(single=class'KFMod.Magnum44Pickup',dual=class'KFMod.Dual44MagnumPickup') + dualiesClasses(2)=(single=class'KFMod.MK23Pickup',dual=class'KFMod.DualMK23Pickup') + dualiesClasses(3)=(single=class'KFMod.DeaglePickup',dual=class'KFMod.DualDeaglePickup') + dualiesClasses(4)=(single=class'KFMod.GoldenDeaglePickup',dual=class'KFMod.GoldenDualDeaglePickup') + dualiesClasses(5)=(single=class'KFMod.FlareRevolverPickup',dual=class'KFMod.DualFlareRevolverPickup') +} \ No newline at end of file diff --git a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFItemTemplateInfo.uc b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFItemTemplateInfo.uc new file mode 100644 index 0000000..6a24105 --- /dev/null +++ b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFItemTemplateInfo.uc @@ -0,0 +1,81 @@ +/** + * Implementation of `EKFItemTemplateInfo` for classic Killing Floor items that + * changes as little as possible and only on request from another mod, + * otherwise not altering gameplay at all. + * 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 EKFItemTemplateInfo extends EItemTemplateInfo; + +var private class classReference; + +/** + * Creates new `EKFItemTemplateInfo` that refers to the `newClass` inventory + * class. + * + * @param newClass Native inventory class that new `EKFItemTemplateInfo` + * will represent. + * @return New `EKFItemTemplateInfo` that represents given `newClass`. + */ +public final static function EKFItemTemplateInfo Wrap( + class newInventoryClass) +{ + local EKFItemTemplateInfo newTemplateReference; + if (newInventoryClass == none) { + return none; + } + newTemplateReference = + EKFItemTemplateInfo(__().memory.Allocate(class'EKFItemTemplateInfo')); + newTemplateReference.classReference = newInventoryClass; + return newTemplateReference; +} + +public function array GetTags() +{ + local array tagArray; + if (class(classReference) != none) { + tagArray[0] = P("weapon").Copy(); + } + return tagArray; +} + +public function Text GetTemplateName() +{ + if (classReference == none) { + return none; + } + return _.text.FromString(Locs(string(classReference))); +} + +public function Text GetName() +{ + local class pickupClass; + if (classReference == none) { + return none; + } + // `KFWeaponPickup` names are usually longer and an overall better fit for + // being displayed + pickupClass = class(classReference.default.pickupClass); + if (pickupClass != none && pickupClass.default.itemName != "") { + return _.text.FromString(pickupClass.default.itemName); + } + return _.text.FromString(classReference.default.itemName); +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFWeapon.uc b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFWeapon.uc new file mode 100644 index 0000000..b5d3298 --- /dev/null +++ b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFWeapon.uc @@ -0,0 +1,150 @@ +/** + * Implementation of `EItem` for classic Killing Floor weapons that changes + * as little as possible and only on request from another mod, otherwise not + * altering gameplay at all. + * 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 EKFWeapon extends EItem + abstract; + +var private NativeActorRef weaponReference; + +protected function Finalizer() +{ + _.memory.Free(weaponReference); + weaponReference = none; +} + +/** + * Creates new `EKFWeapon` that refers to the `weaponInstance` weapon. + * + * @param weaponInstance Native weapon class that new `EKFWeapon` will + * represent. + * @return New `EKFWeapon` that represents given `weaponInstance`. + */ +public final static function EKFWeapon Make(KFWeapon weaponInstance) +{ + local EKFWeapon newReference; + newReference = EKFWeapon(__().memory.Allocate(class'EKFWeapon')); + newReference.weaponReference = __().unreal.ActorRef(weaponInstance); + return newReference; +} + +/** + * Returns `KFWeapon` instance represented by the caller `EKFWeapon`. + * + * @return `KFWeapon` instance represented by the caller `EKFWeapon`. + */ +public final function KFWeapon GetNativeInstance() +{ + if (weaponReference != none) { + return KFWeapon(weaponReference.Get()); + } + return none; +} + +public function array GetTags() +{ + local array tagArray; + if (weaponReference == none) return tagArray; + if (weaponReference.Get() == none) return tagArray; + + tagArray[0] = P("weapon").Copy(); + return tagArray; +} + +public function Text GetTemplate() +{ + local Weapon weapon; + if (weaponReference == none) return none; + weapon = Weapon(weaponReference.Get()); + if (weapon == none) return none; + + return _.text.FromString(Locs(string(weapon.class))); +} + +public function Text GetName() +{ + local Weapon weapon; + if (weaponReference == none) return none; + weapon = Weapon(weaponReference.Get()); + if (weapon == none) return none; + + return _.text.FromString(Locs(weapon.itemName)); +} + +public function bool IsRemovable() +{ + local KFWeapon kfWeapon; // Check is only meaningful for `KFWeapon`s + if (weaponReference == none) return false; + kfWeapon = KFWeapon(weaponReference.Get()); + if (kfWeapon == none) return false; + + return !kfWeapon.bKFNeverThrow; +} + +public function bool IsSellable() +{ + return IsRemovable(); +} + +public function bool SetPrice(int newPrice) +{ + local KFWeapon kfWeapon; // Price is only meaningful for `KFWeapon`s + if (weaponReference == none) return false; + kfWeapon = KFWeapon(weaponReference.Get()); + if (kfWeapon == none) return false; + + kfWeapon.sellValue = newPrice; + return true; +} + +public function int GetPrice() +{ + local KFWeapon kfWeapon; // Price is only meaningful for `KFWeapon`s + if (weaponReference == none) return 0; + kfWeapon = KFWeapon(weaponReference.Get()); + if (kfWeapon == none) return 0; + + return kfWeapon.sellValue; +} + +public function bool SetWeight(int newWeight) +{ + local KFWeapon kfWeapon; // Weight is only meaningful for `KFWeapon`s + if (weaponReference == none) return false; + kfWeapon = KFWeapon(weaponReference.Get()); + if (kfWeapon == none) return false; + + kfWeapon.weight = newWeight; + return true; +} + +public function int GetWeight() +{ + local KFWeapon kfWeapon; // Weight is only meaningful for `KFWeapon`s + if (weaponReference == none) return 0; + kfWeapon = KFWeapon(weaponReference.Get()); + if (kfWeapon == none) return 0; + + return int(kfWeapon.weight); +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Gameplay/KF1Frontend/KF1_Frontend.uc b/sources/Gameplay/KF1Frontend/KF1_Frontend.uc index 4200ded..4ffbf9f 100644 --- a/sources/Gameplay/KF1Frontend/KF1_Frontend.uc +++ b/sources/Gameplay/KF1Frontend/KF1_Frontend.uc @@ -21,6 +21,16 @@ */ class KF1_Frontend extends KFFrontend; +public function EItemTemplateInfo GetItemTemplateInfo(Text templateName) +{ + local class inventoryClass; + inventoryClass = class(_.memory.LoadClass(templateName)); + if (inventoryClass == none) { + return none; + } + return class'EKFItemTemplateInfo'.static.Wrap(inventoryClass); +} + defaultproperties { tradingClass = class'KF1_TradingComponent' diff --git a/sources/Players/APlayer.uc b/sources/Players/APlayer.uc index cfb4a18..b774b0b 100644 --- a/sources/Players/APlayer.uc +++ b/sources/Players/APlayer.uc @@ -216,6 +216,20 @@ public final function SetName(Text newPlayerName) myReplicationInfo.playerName = hashedName; } +// TODO: replace this, it has no place here +// ^ works as a temporary solution before we add pawn wrappers +public final function EInventory GetInventory() +{ + local EKFInventory inventory; + if (controller != none && controller.Get() != none) + { + inventory = EKFInventory(_.memory.Allocate(class'EKFInventory')); + inventory.Initialize(self); + return inventory; + } + return none; +} + /** * Returns admin status of the caller player. * Disconnected players are never admins. diff --git a/sources/Players/Inventory/EInventory.uc b/sources/Players/Inventory/EInventory.uc new file mode 100644 index 0000000..2e101b4 --- /dev/null +++ b/sources/Players/Inventory/EInventory.uc @@ -0,0 +1,291 @@ +/** + * Abstract interface that represents inventory system. Inventory system is + * supposed to represent a way to handle items in players inventory - + * in Killing Floor it is a simple set of items with total weight limitations. + * But any other kind of inventory can be implemented as long as it follows + * limitations of this interface. + * 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 EInventory extends AcediaObject + abstract; + +/** + * Initializes `EInventory` for a given `player`. + * + * This method should not be called manually, unless you implement your own + * game interface. + * + * Cannot fail for any connected player and can assume it will not be called + * for not connected ones. + * + * @param player `APlayer` for which to initialize this inventory. + */ +public function Initialize(APlayer player) {} + +/** + * Adds passed `EItem` to the caller inventory system. + * + * If adding `newItem` is not currently possible for the caller + * inventory system - it can refuse it. + * + * @param newItem New item to add to the caller inventory system. + * @param forceAddition This parameter is only relevant when `newItem` + * cannot be added in the caller inventory system. If it cannot be added + * because of the conflict with other items - setting this flag to `true` + * allows caller inventory system to get rid of such items to make room for + * `newItem`. Removing items is only allowed if it will actually let us add + * `newItem`. How removal will be done is up to the implementation. + * @return `true` if `newItem` was added and `false` otherwise. + */ +public function bool Add(EItem newItem, optional bool forceAddition) +{ + return false; +} + +/** + * Adds new `EItem` of template `newItemTemplate` to the caller + * inventory system. + * + * If adding new item is not currently possible for the caller + * inventory system - it can refuse it. + * + * @param newItemTemplate Template of the new item to add to + * the caller inventory system. + * @param forceAddition This parameter is only relevant when new item + * cannot be added in the caller inventory system. If it cannot be added + * because of the conflict with other items - setting this flag to `true` + * allows caller inventory system to get rid of such items to make room for + * new item. Removing items is only allowed if it will actually let us add + * new item. How removal will be done is up to the implementation. + * @return `true` if new item was added and `false` otherwise. + */ +public function bool AddTemplate( + Text newItemTemplate, + optional bool forceAddition) +{ + return false; +} + +/** + * Checks whether given item `itemToCheck` can be added to the caller + * inventory system. + * + * @param itemToCheck Item to check for whether we can add it to + * the caller `EInventory`. + * @param forceAddition New items can be added with or without + * `forceAddition` flag. This parameter allows you to check whether we + * test for addition with or without it. + * @return `true` if given `itemToCheck` can be added to the caller + * inventory system with given flag `forceAddition` and `false` otherwise. + */ +public function bool CanAdd(EItem itemToCheck, optional bool forceAddition) +{ + return false; +} + +/** + * Checks whether item with given template `itemToCheck` can be added to + * the caller inventory system. + * + * @param itemTemplateToCheck Template of the item to check for whether we can + * add it to the caller `EInventory`. + * @param forceAddition New items can be added with or without + * `forceAddition` flag. This parameter allows you to check whether we + * test for addition with or without it. + * @return `true` if item with given template `itemTemplateToCheck` can be + * added to the caller inventory system with given flag `forceAddition` and + * `false` otherwise. + */ +public function bool CanAddTemplate( + Text itemTemplateToCheck, + optional bool forceAddition) +{ + return false; +} + +/** + * Removes given item `itemToRemove` from the caller `EInventory`. + * + * Based on gameplay considerations, inventory system can refuse removing + * `EItem`s for which `IsRemovable()` returns `false`. But removal of any item + * can be enforced with optional third parameter. + * + * @param itemToRemove Item that needs to be removed. + * @param keepItem By default removed item is destroyed. + * Setting this flag to `true` will make caller `EInventory` try to + * preserve it in some way. For Killing Floor it means dropping the item. + * @param forceRemoval Set this to `true` if item must be removed + * no matter what. Otherwise inventory system can refuse removal of items, + * whose `IsRemovable()` returns `false`. + * @return `true` if `EItem` was removed and `false` otherwise + * (including the case where `EItem` was not kept in the caller + * `EInventory` in the first place). + */ +public function bool Remove( + EItem itemToRemove, + optional bool keepItem, + optional bool forceRemoval) +{ + return false; +} + +/** + * Removes item of type `itemTemplateToRemove` from the caller `EInventory`. + * + * By default removes one arbitrary (can be based on simple convenience of + * implementation) item, but optional parameter can make it remove all items + * of that type. + * + * Based on gameplay considerations, inventory system can refuse removing any + * `EItem` if `IsRemovable()` returns `false` for all stored items of + * given type `itemTemplateToRemove`. But removal of any item can be enforced + * with optional third parameter. + * + * @param itemTemplateToRemove Type of item that needs to be removed. + * @param keepItem By default removed item is destroyed. + * Setting this flag to `true` will make caller `EInventory` try to + * preserve it in some way. For Killing Floor it means dropping the item. + * @param forceRemoval Set this to `true` if item must be removed + * no matter what. Otherwise inventory system can refuse removal if + * `IsRemovable()` returns `false` for all items of given type. + * @param removeAll Set this to `true` if all items of the given type + * must be removed from the caller `EInventory` and keep `false` to remove + * only one. With `forceRemoval == false` it is possible that only items + * that return `false` for `IsRemovable()` will be removed, while others + * will be retained. + * @return `true` if any `EItem`s was removed and `false` otherwise + * (including the case where `EItem` of given type were not kept in the + * caller `EInventory` in the first place). + */ +public function bool RemoveTemplate( + Text itemTemplateToRemove, + optional bool keepItem, + optional bool forceRemoval, + optional bool removeAll) +{ + return false; +} + +/** + * Removes all items from the caller `EInventory`. + * + * Based on gameplay considerations, inventory system can refuse removing + * `EItem`s for which `IsRemovable()` returns `false`. But removal of any item + * can be enforced with optional second parameter. + * + * @param keepItem By default removed item is destroyed. + * Setting this flag to `true` will make caller `EInventory` try to + * preserve it in some way. For Killing Floor it means dropping the item. + * @param forceRemoval Set this to `true` if item must be removed + * no matter what. Otherwise inventory system can refuse removal of items, + * whose `IsRemovable()` returns `false`. + * @return `true` if any `EItem` was removed and `false` otherwise + * (including the case where no `EItem`s were kept in the caller + * `EInventory` in the first place). + */ +public function bool RemoveAll( + optional bool keepItems, + optional bool forceRemoval) +{ + return false; +} + +/** + * Checks whether caller `EInventory` contains given `itemToCheck`. + * + * @param itemToCheck `EItem` we want to check for belonging to the caller + * `EInventory`. + * @result `true` if item does belong to the inventory and `false` otherwise. + */ +public function bool Contains(EItem itemToCheck) +{ + return false; +} + +/** + * Returns array with all `EItem`s contained inside the caller `EInventory`. + * + * @return Array with all `EItem`s contained inside the caller `EInventory`. + */ +public function array GetAllItems() +{ + local array emptyArray; + return emptyArray; +} + +/** + * Returns array with all `EItem`s contained inside the caller `EInventory` + * that has specified tag `tag`. + * + * @param tag Tag, which items we want to get. + * @return Array with all `EItem`s contained inside the caller `EInventory` + * that has specified tag `tag`. + */ +public function array GetTagItems(Text tag) +{ + local array emptyArray; + return emptyArray; +} + +/** + * Returns `EItem` contained inside the caller `EInventory` that has specified + * tag `tag`. + * + * If several `EItem`s inside caller `EInventory` have specified tag, + * inventory system can pick one arbitrarily (can be based on simple + * convenience of implementation). Returned value does not have to + * be stable (the same after repeated calls). + * + * @param tag Tag, which item we want to get. + * @return `EItem` contained inside the caller `EInventory` that belongs to + * the specified tag `tag`. + */ +public function EItem GetTagItem(Text tag) { return none; } + +/** + * Returns array with all `EItem`s contained inside the caller `EInventory` + * that originated from the specified template `template`. + * + * @param template Template, that items we want to get originated from. + * @return Array with all `EItem`s contained inside the caller `EInventory` + * that originated from the specified template `template`. + */ +public function array GetTemplateItems(Text template) +{ + local array emptyArray; + return emptyArray; +} + +/** + * Returns array with all `EItem`s contained inside the caller `EInventory` + * that originated from the specified template `template`. + * + * If several `EItem`s inside caller `EInventory` originated from + * that template, inventory system can pick one arbitrarily (can be based on + * simple convenience of implementation). Returned value does not have to + * be stable (the same after repeated calls). + * + * @param template Template, that item we want to get originated from. + * @return `EItem`s contained inside the caller `EInventory` that originated + * from the specified template `template`. + */ +public function EItem GetTemplateItem(Text template) { return none; } + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Players/Inventory/EItem.uc b/sources/Players/Inventory/EItem.uc new file mode 100644 index 0000000..e95b6ff --- /dev/null +++ b/sources/Players/Inventory/EItem.uc @@ -0,0 +1,179 @@ +/** + * Abstract interface that represents some kind of item inside player's + * inventory. + * At most basic, abstract item has "template", "group" and "name": + * 1. "Template" refers to some sort of preset from which new instances of + * `EItem` can be created. "Template" might be implemented in any way, + * but the requirement is that "templates" can be referred to by + * case-insensitive, human-readable text value. In Killing Floor + * "templates" correspond to classes: for example, + * `KFMod.M79GrenadeLauncher` for M79 or `KFMod.M14EBRBattleRifle` + * for EBR. + * 2. "Tag" refers to the one of the groups inventory belongs to. + * For example all weapons would belong to group "weapons", while ammo + * or objective items can have their own group. But weapons can have + * several different tags further separating them, like "primary", + * "secondary" or "ultimate". + * 3. "Name" is simply a human-readable name of an item that is also fit + * to be output in UI. Like "M79 Grenade Launcher" instead of just + * "KFMod.M79GrenadeLauncher". + * We can also specify whether a certain item is allowed to be removed from + * inventory by player's own volition for any item. `IsRemovable()` method must + * return `true` if it can be. + * + * However, while `EItem` is meant to be abstract generalization for any + * kind of item, to help simplify access to common item data we have also + * added several additional groups of parameters: + * 1. Shop-related parameters. Price (accessed by `SetPrice()` and + * `GetPrice()`) and whether it is sellable (`IsSellable()`). Price + * should only be used if an item is sellable. + * 2. Weight. Accessed by `SetWeight()` / `GetWeight()`. + * All of these parameters can be ignored if they are not applicable to + * a certain type of item. + * 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 EItem extends AcediaObject + abstract; + +/** + * Returns arrays of tags for caller `EItem`. + * + * @return Tags for the caller `EItem`. Returned `Text` values are not allowed + * to be empty or `none`. There can be no duplicates (in case-insensitive + * sense). But returned array can be empty. + */ +public function array GetTags() +{ + local array emptyArray; + return emptyArray; +} + +/** + * Returns template caller `EItem` was created from. + * + * @return Template caller `EItem` belongs to, even if it was modified to be + * something else entirely. `none` for dead `EItem`s. + */ +public function Text GetTemplate() +{ + return none; +} + +/** + * Returns UI-usable name of the caller `EItem`. + * + * @return UI-usable name of the caller `EItem`. Allowed to be empty, + * not allowed to be `none`. `none` for dead `EItem`s. + */ +public function Text GetName() +{ + return none; +} + +/** + * Checks whether this item can be removed as a result of player's action. + * + * We will not enforce items to be completely unremovable through the API, so + * this only marks an item as one unintended to be removed from inventory. + * Enforcing of this rule is up to the implementation. + * 9mm pistol is a good example for Killing Floor. + * + * Note that item being removable does not mean game must always (or ever) + * provide players with a way to remove that item, just that it can. + * + * @return `true` if caller `EItem` is removable by player and + * `false` otherwise. + */ +public function bool IsRemovable() +{ + return false; +} + +/** + * Can caller `EItem` be sold? + * + * @return `true` if caller `EItem` can be sold and `false` otherwise. + */ +public function bool IsSellable() +{ + return false; +} + +/** + * Changes price of the caller `EItem`. + * Only applicable it item is sellable (`IsSellable() == true`). + * + * Price is allowed to have any integer value. + * + * Caller `EItem` is allowed to refuse this call and keep the old price. + * Setting new price, different from both old value and `newPrice` is + * forbidden. + * + * @return `true` if price was successfully changed and `false` otherwise. + */ +public function bool SetPrice(int newPrice) +{ + return false; +} + +/** + * Returns current price of the caller `EItem`. + * Only applicable it item is sellable (`IsSellable() == true`). + * + * Price is allowed to have any integer value. + * + * @return Current price of the caller `EItem`. + */ +public function int GetPrice() { return 0; } + +/** + * Returns "weight" of the caller `EItem`. + * A parameter widely used in Killing Floor. + * + * Weight is allowed to have any integer value. + * + * Caller `EItem` is allowed to refuse this call and keep the old weight. + * Setting new weight, different from both old value and `newWeight` is + * forbidden. + * + * @return `true` if weight was successfully changed and `false` otherwise. + */ +public function bool SetWeight(int newWeight) +{ + return false; +} + +/** + * Returns current weight of the caller `EItem`. + * + * Weight is allowed to have any integer value. + * + * If concept of weight is not applicable, this method should return `0`. + * However inverse does not hold - returning `0` does not mean that weight + * is not applicable for the caller `EItem`. + * + * @return Current weight of the caller `EItem`. + */ +public function int GetWeight() +{ + return 0; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Players/Inventory/EItemTemplateInfo.uc b/sources/Players/Inventory/EItemTemplateInfo.uc new file mode 100644 index 0000000..9615939 --- /dev/null +++ b/sources/Players/Inventory/EItemTemplateInfo.uc @@ -0,0 +1,67 @@ +/** + * Abstract interface that represents information about some kind of item. + * It refers to some sort of preset from which new instances of `EItem` + * can be created. "Template" might be implemented in any way, but + * the requirement is that "templates" can be referred to by case-insensitive, + * human-readable text value. In Killing Floor "templates" correspond to + * classes: for example, `KFMod.M79GrenadeLauncher` for M79 or + * `KFMod.M14EBRBattleRifle` for EBR. However Acedia adds its own parameters + * (such as tags) on top. + * 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 EItemTemplateInfo extends AcediaObject + abstract; + +/** + * Returns arrays of tags for caller `EItem`. + * + * @return Tags for the caller `EItem`. Returned `Text` values are not allowed + * to be empty or `none`. There can be no duplicates (in case-insensitive + * sense). But returned array can be empty. + */ +public function array GetTags() +{ + local array emptyArray; + return emptyArray; +} + +/** + * Returns template caller `EItem` was created from. + * + * @return Template caller `EItem` belongs to, even if it was modified to be + * something else entirely. `none` for dead `EItem`s. + */ +public function Text GetTemplateName() +{ + return none; +} + +/** + * Returns UI-usable name of the caller `EItem`. + * + * @return UI-usable name of the caller `EItem`. Allowed to be empty, + * not allowed to be `none`. `none` for dead `EItem`s. + */ +public function Text GetName() +{ + return none; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Players/PlayerService.uc b/sources/Players/PlayerService.uc index 7bd9049..5ddc42b 100644 --- a/sources/Players/PlayerService.uc +++ b/sources/Players/PlayerService.uc @@ -121,7 +121,7 @@ public final function APlayer GetPlayer(Controller controller) /** * Returns `PlayerController` associated with a given `APlayer`. * - * @param player Player for which we want to find associated controller. + * @param player Player for which we want to find associated `Controller`. * @return Controller that is associated with a given player. * Can return `none` if controller has already "expired". */ @@ -140,6 +140,23 @@ public final function PlayerController GetController(APlayer player) return none; } +/** + * Returns `Pawn` associated with a given `APlayer`. + * + * @param player Player for which we want to find associated `Pawn`. + * @return `Pawn` that is associated with a given player. + * Can return `none` if controller has already "expired". + */ +public final function Pawn GetPawn(APlayer player) +{ + local Controller controller; + controller = GetController(player); + if (controller != none) { + return controller.pawn; + } + return none; +} + /** * IMPORTANT: this is a helper function that is not supposed to be * called manually.