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.