Browse Source

Add inventory frontend support for Killing Floor

pull/8/head
Anton Tarasenko 2 years ago
parent
commit
61d1c0b878
  1. 118
      System
  2. 12
      config/AcediaSystem_KF1Frontend.ini
  3. 17
      sources/Gameplay/BaseClasses/Frontend/BaseFrontend.uc
  4. 16
      sources/Gameplay/BaseClasses/Frontend/EInterface.uc
  5. 82
      sources/Gameplay/BaseClasses/Frontend/Templates/ATemplatesComponent.uc
  6. 1
      sources/Gameplay/BaseClasses/KillingFloor/Frontend/KFFrontend.uc
  7. 464
      sources/Gameplay/KF1Frontend/BaseImplementation/EKFAmmo.uc
  8. 298
      sources/Gameplay/KF1Frontend/BaseImplementation/EKFFlashlightAmmo.uc
  9. 1355
      sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc
  10. 2
      sources/Gameplay/KF1Frontend/BaseImplementation/EKFItemTemplateInfo.uc
  11. 285
      sources/Gameplay/KF1Frontend/BaseImplementation/EKFMedicAmmo.uc
  12. 288
      sources/Gameplay/KF1Frontend/BaseImplementation/EKFSyringeAmmo.uc
  13. 168
      sources/Gameplay/KF1Frontend/BaseImplementation/EKFUnknownItem.uc
  14. 158
      sources/Gameplay/KF1Frontend/BaseImplementation/EKFWeapon.uc
  15. 1
      sources/Gameplay/KF1Frontend/KF1_Frontend.uc
  16. 215
      sources/Gameplay/KF1Frontend/Trading/KF1_TemplatesComponent.uc
  17. 2
      sources/Players/EPlayer.uc
  18. 259
      sources/Players/Inventory/EAmmo.uc
  19. 155
      sources/Players/Inventory/EInventory.uc
  20. 10
      sources/Players/Inventory/EItem.uc
  21. 57
      sources/Players/Inventory/EWeapon.uc
  22. 11
      sources/Text/MutableText.uc
  23. 5
      sources/Types/AcediaActor.uc
  24. 530
      sources/Unreal/InventoryAPI/InventoryAPI.uc
  25. 128
      sources/Unreal/InventoryAPI/InventoryService.uc
  26. 30
      sources/Unreal/Tests/TEST_UnrealAPI.uc
  27. 92
      sources/Unreal/UnrealAPI.uc

118
System

@ -0,0 +1,118 @@
[Editor.EditorEngine]
EditPackages=Core
EditPackages=Engine
EditPackages=Fire
EditPackages=Editor
EditPackages=UnrealEd
EditPackages=IpDrv
EditPackages=UWeb
EditPackages=GamePlay
EditPackages=UnrealGame
EditPackages=XGame
EditPackages=XInterface
EditPackages=XAdmin
EditPackages=XWebAdmin
EditPackages=GUI2K4
EditPackages=xVoting
EditPackages=UTV2004c
EditPackages=UTV2004s
EditPackages=ROEffects
EditPackages=ROEngine
EditPackages=ROInterface
EditPackages=Old2k4
EditPackages=KFMod
EditPackages=KFChar
EditPackages=KFGui
EditPackages=GoodKarma
EditPackages=KFMutators
EditPackages=KFStoryGame
EditPackages=KFStoryUI
EditPackages=SideShowScript
EditPackages=FrightScript
CutdownPackages=Core
CutdownPackages=Editor
CutdownPackages=Engine
CutdownPackages=Fire
CutdownPackages=GamePlay
CutdownPackages=GUI2K4
CutdownPackages=IpDrv
CutdownPackages=Onslaught
CutdownPackages=UnrealEd
CutdownPackages=UnrealGame
CutdownPackages=UWeb
CutdownPackages=XAdmin
CutdownPackages=XEffects
CutdownPackages=XInterface
CutdownPackages=XPickups
CutdownPackages=XWebAdmin
CutdownPackages=XVoting
[Engine.Engine]
RenderDevice=D3D9Drv.D3D9RenderDevice
AudioDevice=ALAudio.ALAudioSubsystem
NetworkDevice=IpDrv.TcpNetDriver
DemoRecordingDevice=Engine.DemoRecDriver
Console=KFMod.KFConsole
GUIController=KFGUI.KFGUIController
StreamPlayer=Engine.StreamInteraction
Language=int
Product=KillingFloor
GameEngine=Engine.GameEngine
EditorEngine=Editor.EditorEngine
DefaultGame=KFMod.KFGameType
DefaultServerGame=KFMod.KFGameType
ViewportManager=WinDrv.WindowsClient
Render=Render.Render
Input=Engine.Input
Canvas=Engine.Canvas
DetectedVideoMemory=0
ServerReadsStdin=False
[Core.System]
PurgeCacheDays=30
SavePath=..\Save
CachePath=../Cache
CacheExt=.uxx
CacheRecordPath=../System/*.ucl
MusicPath=../Music
SpeechPath=../Speech
Paths=../System/*.u
Paths=../Maps/*.rom
Paths=../TestMaps/*.rom
Paths=../Textures/*.utx
Paths=../Sounds/*.uax
Paths=../Music/*.umx
Paths=../StaticMeshes/*.usx
Paths=../Animations/*.ukx
Paths=../Saves/*.uvx
Paths=../Textures/Old2k4/*.utx
Paths=../Sounds/Old2k4/*.uax../Music/Old2k4/*.umx
Paths=../StaticMeshes/Old2k4/*.usx
Paths=../Animations/Old2k4/*.ukx
Paths=../KarmaData/Old2k4/*.ka
Suppress=DevLoad
Suppress=DevSave
Suppress=DevNetTraffic
Suppress=DevGarbage
Suppress=DevKill
Suppress=DevReplace
Suppress=DevCompile
Suppress=DevBind
Suppress=DevBsp
Suppress=DevNet
Suppress=DevLIPSinc
Suppress=DevKarma
Suppress=RecordCache
Suppress=MapVoteDebug
Suppress=Init
Suppress=MapVote
Suppress=VoiceChat
Suppress=ChatManager
Suppress=Time
[ROFirstRun]
ROFirstRun=1094

12
config/AcediaSystem_KF1Frontend.ini

@ -0,0 +1,12 @@
; Every single option in this config should be considered [ADVANCED]
[AcediaCore.KFDualiesTool]
; This array defines what weapons Acedia's Killing Floor frontend considers as
; pairs of single - dual versions.
; If you are adding some custom dual weapons to your server, then they should
; also be added here until a better way is implemented.
dualiesClasses=(single=class'KFMod.SinglePickup',dual=class'KFMod.DualiesPickup')
dualiesClasses=(single=class'KFMod.Magnum44Pickup',dual=class'KFMod.Dual44MagnumPickup')
dualiesClasses=(single=class'KFMod.MK23Pickup',dual=class'KFMod.DualMK23Pickup')
dualiesClasses=(single=class'KFMod.DeaglePickup',dual=class'KFMod.DualDeaglePickup')
dualiesClasses=(single=class'KFMod.GoldenDeaglePickup',dual=class'KFMod.GoldenDualDeaglePickup')
dualiesClasses=(single=class'KFMod.FlareRevolverPickup',dual=class'KFMod.DualFlareRevolverPickup')

17
sources/Gameplay/BaseClasses/Frontend/BaseFrontend.uc

@ -21,6 +21,23 @@
class BaseFrontend extends AcediaObject class BaseFrontend extends AcediaObject
abstract; abstract;
var private config class<ATemplatesComponent> templatesClass;
var public ATemplatesComponent templates;
protected function Constructor()
{
if (templatesClass != none) {
templates = ATemplatesComponent(_.memory.Allocate(templatesClass));
}
}
protected function Finalizer()
{
_.memory.Free(templates);
templates = none;
}
defaultproperties defaultproperties
{ {
templatesClass = none
} }

16
sources/Gameplay/BaseClasses/Frontend/EInterface.uc

@ -8,7 +8,7 @@
* once (including those of the same type). Deallocating one such reference * once (including those of the same type). Deallocating one such reference
* should not affect referred entity in any way and should be treated as simply * should not affect referred entity in any way and should be treated as simply
* getting rid of one of the references. * getting rid of one of the references.
* Copyright 2021 Anton Tarasenko * Copyright 2021 - 2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -42,6 +42,20 @@ public function EInterface Copy()
return none; return none;
} }
/**
* Checks if entity, referred to by the caller `EInterface` supports
* `newInterfaceClass` interface class.
*
* @param newInterfaceClass Class of the `EInterface`, for which method
* should check support by entity, referred to by the caller `EInterface`.
* @return `true` if referred entity supports `newInterfaceClass` and
* `false` otherwise.
*/
public function bool Supports(class<EInterface> newInterfaceClass)
{
return false;
}
/** /**
* Provides `EInterface` reference of given class `newInterfaceClass` to * Provides `EInterface` reference of given class `newInterfaceClass` to
* the entity, referred to by the caller `EInterface` (if supported). * the entity, referred to by the caller `EInterface` (if supported).

82
sources/Gameplay/BaseClasses/Frontend/Templates/ATemplatesComponent.uc

@ -0,0 +1,82 @@
/**
* Subset of functionality for dealing with everything related to templates.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class ATemplatesComponent extends AcediaObject
abstract;
/**
* Returns `true` if list of items named `listName` exists and
* `false` otherwise.
*
* This method is necessary, since `GetItemList()` does not allow to
* distinguish between empty and non-existing item list.
*
* @param listName Name of the list to check for whether it exists.
* @return `true` if list named `listName` exists and `false` otherwise.
* Always returns `false` if `listName` equals `none`.
*/
public function bool ItemListExists(Text listName)
{
return false;
}
/**
* Returns array with templates of items belonging to the `listName` list.
*
* All implementations must support:
* 1. "all weapons" / "weapons" (both names
* should refer to the same list) list with templates of all weapons in
* the game;
* 2. "trading weapons" list with names of all weapons available for trade
* in one way or another (even if they are not all tradable in
* all shops / for all players).
*
* @param listName Name of the list to return templates for.
* In case a name of inexistent list is specified - method does nothing.
* @return Array of templates in the list, specified with `listName`.
* All of the `Text`s in the returned array are guaranteed to be `none`.
* When incorrect `listName` is specified - empty array is returned
* (which can also happen if specified list is empty).
*/
public function array<Text> GetItemList(Text listName)
{
local array<Text> emptyArray;
return emptyArray;
}
/**
* Returns array that is listing all available lists of item templates.
*
* All implementations must include "all weapons" and "trading weapons" lists.
*
* @return Array with names of all available lists.
* All of the `Text`s in the returned array are guaranteed to be `none`.
* If a certain list has several names (like "all weapons" / "weapons"),
* only one of these names (guaranteed to always be the same between calls)
* will be included.
*/
public function array<Text> GetAvailableLists()
{
local array<Text> emptyArray;
return emptyArray;
}
defaultproperties
{
}

1
sources/Gameplay/BaseClasses/KillingFloor/Frontend/KFFrontend.uc

@ -25,6 +25,7 @@ var public ATradingComponent trading;
protected function Constructor() protected function Constructor()
{ {
super.Constructor();
if (tradingClass != none) { if (tradingClass != none) {
trading = ATradingComponent(_.memory.Allocate(tradingClass)); trading = ATradingComponent(_.memory.Allocate(tradingClass));
} }

464
sources/Gameplay/KF1Frontend/BaseImplementation/EKFAmmo.uc

@ -0,0 +1,464 @@
/**
* Implementation of `EAmmo` 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 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class EKFAmmo extends EAmmo;
var private NativeActorRef ammunitionReference;
protected function Finalizer()
{
_.memory.Free(ammunitionReference);
ammunitionReference = none;
}
/**
* Creates new `EKFAmmo` that refers to the `ammunitionInstance` ammunition.
*
* @param ammunitionInstance Native ammunition instance that new `EKFAmmo`
* will represent.
* @return New `EKFAmmo` that represents given `ammunitionInstance`.
* `none` iff `ammunitionInstance` is either `none` or
* is an unused flash light ammunition
* (has `class'KFMod.FlashlightAmmo'` class).
*/
public final static /*unreal*/ function EKFAmmo Wrap(
Ammunition ammunitionInstance)
{
local EKFAmmo newReference;
if (ammunitionInstance == none) return none;
// This one is not actually used for anything, so it is not real
if (ammunitionInstance.class == class'KFMod.FlashlightAmmo') return none;
newReference = EKFAmmo(__().memory.Allocate(class'EKFAmmo'));
newReference.ammunitionReference = __().unreal.ActorRef(ammunitionInstance);
return newReference;
}
public function EInterface Copy()
{
local Ammunition ammunitionInstance;
ammunitionInstance = GetNativeInstance();
return Wrap(ammunitionInstance);
}
public function bool Supports(class<EInterface> newInterfaceClass)
{
if (newInterfaceClass == none) return false;
if (newInterfaceClass == class'EItem') return true;
if (newInterfaceClass == class'EAmmo') return true;
if (newInterfaceClass == class'EKFAmmo') return true;
return false;
}
public function EInterface As(class<EInterface> newInterfaceClass)
{
if (!IsExistent()) {
return none;
}
if ( newInterfaceClass == class'EItem'
|| newInterfaceClass == class'EAmmo'
|| newInterfaceClass == class'EKFAmmo')
{
return Copy();
}
return none;
}
public function bool IsExistent()
{
return (GetNativeInstance() != none);
}
public function bool SameAs(EInterface other)
{
local EKFAmmo otherAmmo;
otherAmmo = EKFAmmo(other);
if (otherAmmo == none) {
return false;
}
return (GetNativeInstance() == otherAmmo.GetNativeInstance());
}
/**
* Returns `Ammunition` instance represented by the caller `EKFAmmo`.
*
* @return `Ammunition` instance represented by the caller `EKFAmmo`.
*/
public final /*unreal*/ function Ammunition GetNativeInstance()
{
if (ammunitionReference != none) {
return Ammunition(ammunitionReference.Get());
}
return none;
}
public function array<Text> GetTags()
{
local array<Text> tagArray;
if (ammunitionReference == none) return tagArray;
if (ammunitionReference.Get() == none) return tagArray;
tagArray[0] = P("ammo").Copy();
return tagArray;
}
public function bool HasTag(Text tagToCheck)
{
if (tagToCheck == none) return false;
if (tagToCheck.Compare(P("ammo"))) return true;
return false;
}
public function Text GetTemplate()
{
local Ammunition ammunition;
ammunition = GetNativeInstance();
if (ammunition == none) {
return none;
}
return _.text.FromString(string(ammunition.class));
}
public function Text GetName()
{
local Ammunition ammunition;
ammunition = GetNativeInstance();
if (ammunition == none) {
return none;
}
return _.text.FromString(ammunition.GetHumanReadableName());
}
public function bool IsRemovable()
{
return false;
}
public function bool IsSellable()
{
return false;
}
private function class<KFWeaponPickup> GetOwnerWeaponPickupClass()
{
local KFWeapon ownerWeapon;
local Ammunition ammunition;
ammunition = GetNativeInstance();
if (ammunition == none) {
return none;
}
ownerWeapon = GetOwnerWeapon();
if (ownerWeapon != none) {
return class<KFWeaponPickup>(ownerWeapon.pickupClass);
}
return none;
}
// Finds a weapons that is corresponding to our ammo.
// We can limit ourselves to returning a single instance, since one weapon
// per ammo type is how Killing Floor does things.
private function KFWeapon GetOwnerWeapon()
{
local Pawn myOwner;
local KFWeapon nextWeapon;
local Inventory nextInventory;
local Ammunition ammunition;
ammunition = GetNativeInstance();
if (ammunition == none) return none;
myOwner = Pawn(ammunition.owner);
if (myOwner == none) return none;
nextInventory = myOwner.inventory;
while (nextInventory != none)
{
nextWeapon = KFWeapon(nextInventory);
nextInventory = nextInventory.inventory;
if (_.unreal.inventory.GetAmmoClass(nextWeapon, 0) == ammunition.class)
{
return nextWeapon;
}
else if ( _.unreal.inventory.GetAmmoClass(nextWeapon, 1)
== ammunition.class) {
return nextWeapon;
}
}
return none;
}
/**
* In Killing Floor ammo object itself does not actually have a price,
* instead it is defined inside weapon's `Pickup` class and, therefore,
* cannot be changed for an individual item. Only calculated.
*/
public function bool SetPrice(int newPrice)
{
return false;
}
public function int GetPrice()
{
return GetPriceOf(GetAmount());
}
public function int GetTotalPrice()
{
return GetPriceOf(GetTotalAmount());
}
public function int GetPriceOf(int ammoAmount)
{
local Pawn myOwner;
local int clipSize;
local float clipPrice;
local KFWeapon ownerWeapon;
local KFPlayerReplicationInfo ownerKFPRI;
local class<KFWeaponPickup> ownerWeaponPickupClass;
local Ammunition ammunition;
ammunition = GetNativeInstance();
if (ammunition == none) return 0;
ownerWeapon = GetOwnerWeapon();
if (ownerWeapon == none) return 0;
ownerWeaponPickupClass = class<KFWeaponPickup>(ownerWeapon.pickupClass);
if (ownerWeaponPickupClass == none) return 0;
// Calculate clip price
if ( ownerWeapon.bHasSecondaryAmmo
&& ammunition.class != ownerWeapon.fireModeClass[0].default.ammoClass)
{
// Amon Killing Floor's weapons, only M4 203 has a real secondary ammo
clipSize = 1;
}
else {
clipSize = ownerWeapon.default.magCapacity;
}
if( ownerWeapon.PickupClass == class'HuskGunPickup' ) {
clipSize = ownerWeaponPickupClass.default.buyClipSize;
}
clipPrice = ownerWeaponPickupClass.default.ammoCost;
// Calculate clip size
myOwner = Pawn(ammunition.owner);
if (myOwner != none) {
ownerKFPRI = KFPlayerReplicationInfo(myOwner.playerReplicationInfo);
}
if (ownerKFPRI != none)
{
clipPrice *= ownerKFPRI.clientVeteranSkill.static
.GetAmmoCostScaling(ownerKFPRI, ownerWeaponPickupClass);
}
// Calculate price of total ammo
return int(ammoAmount * clipPrice / clipSize);
}
public function bool SetWeight(int newWeight)
{
return false;
}
public function int GetWeight()
{
return 0;
}
// Killing Floor weapons do not reduce ammunition when it is loaded it into
// the weapons. This is because each ammo type is only ever used by one weapon,
// so the can simply treat it as part of the weapon and only record how much
// of it is currently in the weapon's magazine.
// This method goes through inventory weapons to find how much ammo was
// already loaded into the weapons.
private function int GetLoadedAmmo()
{
local KFWeapon ownerWeapon;
local Ammunition ammunition;
ammunition = GetNativeInstance();
if (ammunition == none) return 0;
ownerWeapon = GetOwnerWeapon();
if (ownerWeapon == none) return 0;
// Husk gun does not load ammo at all
if (ownerWeapon.class == class'KFMod.HuskGun') return 0;
// Most of the Killing Floor weapons do not have a proper separate
// secondary ammo: they either reuse primary ammo (like zed guns or
// hunting shotgun), or they use some pseudo-ammo (like medic guns).
// They only exception is M4 203 that loads itself as soon as
// it fires. Some modded weapons might also be exceptions and/or use
// secondary ammo differently, but we have no way of knowing how
// exactly they are doing it and cannot implement this interface
// for them.
// That is why we only bother with the first fire mode and count
// one loaded ammo for the secondary, just assuming it is M4 203.
// We can also quit as soon as we have found a single weapon that
// uses our ammo, since one weapon per ammo type is how Killing Floor
// does things.
if (_.unreal.inventory.GetAmmoClass(ownerWeapon, 0) == ammunition.class) {
return ownerWeapon.magAmmoRemaining;
}
else if ( _.unreal.inventory.GetAmmoClass(ownerWeapon, 1)
== ammunition.class)
{
return 1; // M4 203
}
return 0;
}
public function Add(int amount, optional bool forceAddition)
{
local Ammunition ammunition;
ammunition = GetNativeInstance();
if (ammunition == none) {
return;
}
if (forceAddition) {
ammunition.ammoAmount += amount;
}
else
{
ammunition.ammoAmount =
Min(ammunition.maxAmmo, ammunition.ammoAmount + amount);
}
// Correct possible negative values
if (ammunition.ammoAmount < 0) {
ammunition.ammoAmount = 0;
}
ammunition.netUpdateTime = ammunition.level.timeSeconds - 1;
}
public function int GetAmount()
{
local Ammunition ammunition;
ammunition = GetNativeInstance();
if (ammunition == none) {
return 0;
}
return Max(0, ammunition.ammoAmount - GetLoadedAmmo());
}
public function int GetTotalAmount()
{
local Ammunition ammunition;
ammunition = GetNativeInstance();
if (ammunition == none) {
return 0;
}
return Max(0, ammunition.ammoAmount);
}
public function SetAmount(int amount, optional bool forceAddition)
{
local Ammunition ammunition;
ammunition = GetNativeInstance();
if (ammunition == none) {
return;
}
if (forceAddition) {
ammunition.ammoAmount = amount;
}
else {
ammunition.ammoAmount = Min(ammunition.maxAmmo, amount);
}
// Correct possible negative values
if (ammunition.ammoAmount < 0) {
ammunition.ammoAmount = 0;
}
ammunition.netUpdateTime = ammunition.level.timeSeconds - 1;
}
public function int GetMaxAmount()
{
local Ammunition ammunition;
ammunition = GetNativeInstance();
if (ammunition == none) {
return 0;
}
// `Ammunition` does not really support infinite ammo, so return `0` if
// the value is messed up.
return Max(0, ammunition.maxAmmo - GetLoadedAmmo());
}
public function int GetMaxTotalAmount()
{
local Ammunition ammunition;
ammunition = GetNativeInstance();
if (ammunition == none) {
return 0;
}
// `Ammunition` does not really support infinite ammo, so return `0` if
// the value is messed up.
return Max(0, ammunition.maxAmmo);
}
/**
* Supports any non-negative ammo value.
*/
public function bool SetMaxAmount(
int newMaxAmmo,
optional bool leaveCurrentAmmo)
{
local Ammunition ammunition;
// We do not support unlimited ammo values
if (newMaxAmmo < 0) return false;
ammunition = GetNativeInstance();
if (ammunition == none) return false;
ammunition.maxAmmo = newMaxAmmo + GetLoadedAmmo();
if (!leaveCurrentAmmo) {
ammunition.ammoAmount = Min(ammunition.maxAmmo, ammunition.ammoAmount);
}
ammunition.netUpdateTime = ammunition.level.timeSeconds - 1;
return true;
}
public function bool SetMaxTotalAmount(
int newTotalMaxAmmo,
optional bool leaveCurrentAmmo)
{
local Ammunition ammunition;
// We do not support unlimited ammo values
if (newTotalMaxAmmo < 0) return false;
ammunition = GetNativeInstance();
if (ammunition == none) return false;
ammunition.maxAmmo = newTotalMaxAmmo;
if (!leaveCurrentAmmo) {
ammunition.ammoAmount = Min(ammunition.maxAmmo, ammunition.ammoAmount);
}
ammunition.netUpdateTime = ammunition.level.timeSeconds - 1;
return true;
}
public function bool HasWeapon()
{
return (GetOwnerWeapon() != none);
}
// Killing Floor's ammo should also count ammo already loaded into the magazine
public function Fill()
{
if (GetMaxTotalAmount() < 0) return;
if (GetAmount() >= GetMaxTotalAmount()) return;
SetAmount(GetMaxTotalAmount());
}
defaultproperties
{
}

298
sources/Gameplay/KF1Frontend/BaseImplementation/EKFFlashlightAmmo.uc

@ -0,0 +1,298 @@
/**
* Implementation of `EAmmo` for Killing Floor's flashlight charge that changes
* as little as possible and only on request from another mod, otherwise not
* altering gameplay at all.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class EKFFlashlightAmmo extends EAmmo;
var private NativeActorRef pawnReference;
protected function Finalizer()
{
_.memory.Free(pawnReference);
pawnReference = none;
}
/**
* Creates new `EKFFlashlightAmmo` that refers to the `medicWeaponInstance`'s
* medic ammunition.
*
* @param kfHumanPawn Pawn class with flashlight ammo.
* In Killing Floor, "flashlight ammo" is basically just a variable
* inside `KFHumanPawn` instance.
* @return New `EKFFlashlightAmmo` that represents medic ammunition of given
* `kfHumanPawn`. `none` iff `kfHumanPawn` is `none`.
*/
public final static /*unreal*/ function EKFFlashlightAmmo Wrap(
KFHumanPawn kfHumanPawn)
{
local EKFFlashlightAmmo newReference;
if (kfHumanPawn == none) {
return none;
}
newReference =
EKFFlashlightAmmo(__().memory.Allocate(class'EKFFlashlightAmmo'));
newReference.pawnReference = __().unreal.ActorRef(kfHumanPawn);
return newReference;
}
public function EInterface Copy()
{
local KFHumanPawn pawnInstance;
pawnInstance = GetNativeInstance();
return Wrap(pawnInstance);
}
public function bool Supports(class<EInterface> newInterfaceClass)
{
if (newInterfaceClass == none) return false;
if (newInterfaceClass == class'EItem') return true;
if (newInterfaceClass == class'EAmmo') return true;
if (newInterfaceClass == class'EKFFlashlightAmmo') return true;
return false;
}
public function EInterface As(class<EInterface> newInterfaceClass)
{
if (!IsExistent()) {
return none;
}
if ( newInterfaceClass == class'EItem'
|| newInterfaceClass == class'EAmmo'
|| newInterfaceClass == class'EKFFlashlightAmmo')
{
return Copy();
}
return none;
}
public function bool IsExistent()
{
return (GetNativeInstance() != none);
}
public function bool SameAs(EInterface other)
{
local EKFFlashlightAmmo otherAmmo;
otherAmmo = EKFFlashlightAmmo(other);
if (otherAmmo == none) {
return false;
}
return (GetNativeInstance() == otherAmmo.GetNativeInstance());
}
/**
* Returns `KFAmmunition` instance represented by the caller `EKFAmmo`.
*
* @return `KFAmmunition` instance represented by the caller `EKFAmmo`.
*/
public final /*unreal*/ function KFHumanPawn GetNativeInstance()
{
if (pawnReference != none) {
return KFHumanPawn(pawnReference.Get());
}
return none;
}
public function array<Text> GetTags()
{
local array<Text> tagArray;
if (pawnReference == none) return tagArray;
if (pawnReference.Get() == none) return tagArray;
tagArray[0] = P("ammo").Copy();
return tagArray;
}
public function bool HasTag(Text tagToCheck)
{
if (tagToCheck == none) return false;
if (tagToCheck.Compare(P("ammo"))) return true;
return false;
}
public function Text GetTemplate()
{
if (IsExistent()) {
return P("flashlight:ammo").Copy();
}
return none;
}
public function Text GetName()
{
if (IsExistent()) {
return P("Flashlight's ammo").Copy();
}
return none;
}
public function bool IsRemovable()
{
return false;
}
public function bool IsSellable()
{
return false;
}
/**
* Medic ammo is free and does not have a price in Killing Floor.
*/
public function bool SetPrice(int newPrice)
{
return false;
}
public function int GetPrice()
{
return 0;
}
public function int GetTotalPrice()
{
return 0;
}
public function int GetPriceOf(int ammoAmount)
{
return 0;
}
public function bool SetWeight(int newWeight)
{
return false;
}
public function int GetWeight()
{
return 0;
}
public function Add(int amount, optional bool forceAddition)
{
local KFHumanPawn kfHumanPawn;
kfHumanPawn = GetNativeInstance();
if (kfHumanPawn == none) {
return;
}
if (forceAddition) {
kfHumanPawn.torchBatteryLife += amount;
}
else
{
kfHumanPawn.torchBatteryLife =
Min( kfHumanPawn.default.torchBatteryLife,
kfHumanPawn.torchBatteryLife + amount);
}
// Correct possible negative values
if (kfHumanPawn.torchBatteryLife < 0) {
kfHumanPawn.torchBatteryLife = 0;
}
}
public function int GetAmount()
{
local KFHumanPawn kfHumanPawn;
kfHumanPawn = GetNativeInstance();
if (kfHumanPawn == none) {
return 0;
}
return Max(0, kfHumanPawn.torchBatteryLife);
}
public function int GetTotalAmount()
{
return GetAmount();
}
public function SetAmount(int amount, optional bool forceAddition)
{
local KFHumanPawn kfHumanPawn;
kfHumanPawn = GetNativeInstance();
if (kfHumanPawn == none) {
return;
}
if (forceAddition) {
kfHumanPawn.torchBatteryLife = amount;
}
else
{
kfHumanPawn.torchBatteryLife =
Min(kfHumanPawn.default.torchBatteryLife, amount);
}
// Correct possible negative values
if (kfHumanPawn.torchBatteryLife < 0) {
kfHumanPawn.torchBatteryLife = 0;
}
}
public function int GetMaxAmount()
{
local KFHumanPawn kfHumanPawn;
kfHumanPawn = GetNativeInstance();
if (kfHumanPawn == none) {
return 0;
}
return Max(0, kfHumanPawn.default.torchBatteryLife);
}
public function int GetMaxTotalAmount()
{
return GetMaxAmount();
}
public function bool SetMaxAmount(
int newMaxAmmo,
optional bool leaveCurrentAmmo)
{
local KFHumanPawn kfHumanPawn;
// We do not support unlimited ammo values
if (newMaxAmmo < 0) return false;
kfHumanPawn = GetNativeInstance();
if (kfHumanPawn == none) return false;
kfHumanPawn.default.torchBatteryLife = newMaxAmmo;
if (!leaveCurrentAmmo)
{
kfHumanPawn.torchBatteryLife =
Min( kfHumanPawn.default.torchBatteryLife,
kfHumanPawn.torchBatteryLife);
}
return true;
}
public function bool SetMaxTotalAmount(
int newTotalMaxAmmo,
optional bool leaveCurrentAmmo)
{
return SetMaxAmount(newTotalMaxAmmo, leaveCurrentAmmo);
}
public function bool HasWeapon()
{
return (GetNativeInstance() != none);
}
defaultproperties
{
}

1355
sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc

File diff suppressed because it is too large Load Diff

2
sources/Gameplay/KF1Frontend/BaseImplementation/EKFItemTemplateInfo.uc

@ -1,5 +1,5 @@
/** /**
* Implementation of `EKFItemTemplateInfo` for classic Killing Floor items that * Implementation of `EItemTemplateInfo` for classic Killing Floor items that
* changes as little as possible and only on request from another mod, * changes as little as possible and only on request from another mod,
* otherwise not altering gameplay at all. * otherwise not altering gameplay at all.
* Copyright 2021 - 2022 Anton Tarasenko * Copyright 2021 - 2022 Anton Tarasenko

285
sources/Gameplay/KF1Frontend/BaseImplementation/EKFMedicAmmo.uc

@ -0,0 +1,285 @@
/**
* Implementation of `EAmmo` for Killing Floor medic weapons that changes
* as little as possible and only on request from another mod, otherwise not
* altering gameplay at all.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class EKFMedicAmmo extends EAmmo;
var private NativeActorRef medicWeaponReference;
protected function Finalizer()
{
_.memory.Free(medicWeaponReference);
medicWeaponReference = none;
}
/**
* Creates new `EKFMedicAmmo` that refers to the `medicWeaponInstance`'s
* medic ammunition.
*
* @param medicWeaponInstance Native medic gun, whose medic ammunition
* new `EKFMedicAmmo` will represent.
* @return New `EKFMedicAmmo` that represents medic ammunition of given
* `medicWeaponInstance`. `none` iff `medicWeaponInstance` is `none`.
*/
public final static /*unreal*/ function EKFMedicAmmo Wrap(
KFMedicGun medicWeaponInstance)
{
local EKFMedicAmmo newReference;
if (medicWeaponInstance == none) {
return none;
}
newReference = EKFMedicAmmo(__().memory.Allocate(class'EKFMedicAmmo'));
newReference.medicWeaponReference =
__().unreal.ActorRef(medicWeaponInstance);
return newReference;
}
public function EInterface Copy()
{
local KFMedicGun medicWeaponInstance;
medicWeaponInstance = GetNativeInstance();
return Wrap(medicWeaponInstance);
}
public function bool Supports(class<EInterface> newInterfaceClass)
{
if (newInterfaceClass == none) return false;
if (newInterfaceClass == class'EItem') return true;
if (newInterfaceClass == class'EAmmo') return true;
if (newInterfaceClass == class'EKFMedicAmmo') return true;
return false;
}
public function EInterface As(class<EInterface> newInterfaceClass)
{
if (!IsExistent()) {
return none;
}
if ( newInterfaceClass == class'EItem'
|| newInterfaceClass == class'EAmmo'
|| newInterfaceClass == class'EKFMedicAmmo')
{
return Copy();
}
return none;
}
public function bool IsExistent()
{
return (GetNativeInstance() != none);
}
public function bool SameAs(EInterface other)
{
local EKFMedicAmmo otherAmmo;
otherAmmo = EKFMedicAmmo(other);
if (otherAmmo == none) {
return false;
}
return (GetNativeInstance() == otherAmmo.GetNativeInstance());
}
/**
* Returns `KFAmmunition` instance represented by the caller `EKFAmmo`.
*
* @return `KFAmmunition` instance represented by the caller `EKFAmmo`.
*/
public final /*unreal*/ function KFMedicGun GetNativeInstance()
{
if (medicWeaponReference != none) {
return KFMedicGun(medicWeaponReference.Get());
}
return none;
}
public function array<Text> GetTags()
{
local array<Text> tagArray;
if (medicWeaponReference == none) return tagArray;
if (medicWeaponReference.Get() == none) return tagArray;
tagArray[0] = P("ammo").Copy();
return tagArray;
}
public function bool HasTag(Text tagToCheck)
{
if (tagToCheck == none) return false;
if (tagToCheck.Compare(P("ammo"))) return true;
return false;
}
public function Text GetTemplate()
{
local KFMedicGun medicWeapon;
medicWeapon = GetNativeInstance();
if (medicWeapon == none) {
return none;
}
return _.text.FromString(string(medicWeapon.class) $ ":ammo");
}
public function Text GetName()
{
local KFMedicGun medicWeapon;
medicWeapon = GetNativeInstance();
if (medicWeapon == none) {
return none;
}
return _.text.FromString(medicWeapon.GetHumanReadableName() $ "'s ammo");
}
public function bool IsRemovable()
{
return false;
}
public function bool IsSellable()
{
return false;
}
/**
* Medic ammo is free and does not have a price in Killing Floor.
*/
public function bool SetPrice(int newPrice)
{
return false;
}
public function int GetPrice()
{
return 0;
}
public function int GetTotalPrice()
{
return 0;
}
public function int GetPriceOf(int ammoAmount)
{
return 0;
}
public function bool SetWeight(int newWeight)
{
return false;
}
public function int GetWeight()
{
return 0;
}
public function Add(int amount, optional bool forceAddition)
{
local KFMedicGun medicWeapon;
medicWeapon = GetNativeInstance();
if (medicWeapon == none) {
return;
}
if (forceAddition) {
medicWeapon.healAmmoCharge += amount;
}
else
{
medicWeapon.healAmmoCharge =
Min(medicWeapon.maxAmmoCount, medicWeapon.healAmmoCharge + amount);
}
// Correct possible negative values
if (medicWeapon.healAmmoCharge < 0) {
medicWeapon.healAmmoCharge = 0;
}
}
public function int GetAmount()
{
local KFMedicGun medicWeapon;
medicWeapon = GetNativeInstance();
if (medicWeapon == none) {
return 0;
}
return Max(0, medicWeapon.healAmmoCharge);
}
public function int GetTotalAmount()
{
return GetAmount();
}
public function SetAmount(int amount, optional bool forceAddition)
{
local KFMedicGun medicWeapon;
medicWeapon = GetNativeInstance();
if (medicWeapon == none) {
return;
}
if (forceAddition) {
medicWeapon.healAmmoCharge = amount;
}
else {
medicWeapon.healAmmoCharge = Min(medicWeapon.maxAmmoCount, amount);
}
// Correct possible negative values
if (medicWeapon.healAmmoCharge < 0) {
medicWeapon.healAmmoCharge = 0;
}
}
public function int GetMaxAmount()
{
local KFMedicGun medicWeapon;
medicWeapon = GetNativeInstance();
if (medicWeapon == none) {
return 0;
}
return Max(0, medicWeapon.maxAmmoCount);
}
public function int GetMaxTotalAmount()
{
return GetMaxAmount();
}
public function bool SetMaxAmount(
int newMaxAmmo,
optional bool leaveCurrentAmmo)
{
return false;
}
public function bool SetMaxTotalAmount(
int newTotalMaxAmmo,
optional bool leaveCurrentAmmo)
{
return false;
}
public function bool HasWeapon()
{
return (GetNativeInstance() != none);
}
defaultproperties
{
}

288
sources/Gameplay/KF1Frontend/BaseImplementation/EKFSyringeAmmo.uc

@ -0,0 +1,288 @@
/**
* Implementation of `EAmmo` for Killing Floor medical syringe that changes
* as little as possible and only on request from another mod, otherwise not
* altering gameplay at all.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class EKFSyringeAmmo extends EAmmo;
var private NativeActorRef syringeReference;
protected function Finalizer()
{
_.memory.Free(syringeReference);
syringeReference = none;
}
/**
* Creates new `EKFSyringeAmmo` that refers to the `syringeInstance`'s
* ammunition.
*
* @param syringeInstance Native syringe instance, whose ammunition
* new `EKFSyringeAmmo` will represent.
* @return New `EKFSyringeAmmo` that represents ammunition of given
* `syringeInstance`. `none` iff `syringeInstance` is `none`.
*/
public final static /*unreal*/ function EKFSyringeAmmo Wrap(
Syringe syringeInstance)
{
local EKFSyringeAmmo newReference;
if (syringeInstance == none) {
return none;
}
newReference = EKFSyringeAmmo(__().memory.Allocate(class'EKFSyringeAmmo'));
newReference.syringeReference =
__().unreal.ActorRef(syringeInstance);
return newReference;
}
public function EInterface Copy()
{
local Syringe syringeInstance;
syringeInstance = GetNativeInstance();
return Wrap(syringeInstance);
}
public function bool Supports(class<EInterface> newInterfaceClass)
{
if (newInterfaceClass == none) return false;
if (newInterfaceClass == class'EItem') return true;
if (newInterfaceClass == class'EAmmo') return true;
if (newInterfaceClass == class'EKFSyringeAmmo') return true;
return false;
}
public function EInterface As(class<EInterface> newInterfaceClass)
{
if (!IsExistent()) {
return none;
}
if ( newInterfaceClass == class'EItem'
|| newInterfaceClass == class'EAmmo'
|| newInterfaceClass == class'EKFSyringeAmmo')
{
return Copy();
}
return none;
}
public function bool IsExistent()
{
return (GetNativeInstance() != none);
}
public function bool SameAs(EInterface other)
{
local EKFSyringeAmmo otherAmmo;
otherAmmo = EKFSyringeAmmo(other);
if (otherAmmo == none) {
return false;
}
return (GetNativeInstance() == otherAmmo.GetNativeInstance());
}
/**
* Returns `KFAmmunition` instance represented by the caller `EKFAmmo`.
*
* @return `KFAmmunition` instance represented by the caller `EKFAmmo`.
*/
public final /*unreal*/ function Syringe GetNativeInstance()
{
if (syringeReference != none) {
return Syringe(syringeReference.Get());
}
return none;
}
public function array<Text> GetTags()
{
local array<Text> tagArray;
if (syringeReference == none) return tagArray;
if (syringeReference.Get() == none) return tagArray;
tagArray[0] = P("ammo").Copy();
return tagArray;
}
public function bool HasTag(Text tagToCheck)
{
if (tagToCheck == none) return false;
if (tagToCheck.Compare(P("ammo"))) return true;
return false;
}
public function Text GetTemplate()
{
local Syringe syringeInstance;
syringeInstance = GetNativeInstance();
if (syringeInstance == none) {
return none;
}
return _.text.FromString("kfmod.syringe:ammo");
}
public function Text GetName()
{
local Syringe syringeInstance;
syringeInstance = GetNativeInstance();
if (syringeInstance == none) {
return none;
}
return _.text.FromString("Syringe's ammo");
}
public function bool IsRemovable()
{
return false;
}
public function bool IsSellable()
{
return false;
}
/**
* Medic ammo is free and does not have a price in Killing Floor.
*/
public function bool SetPrice(int newPrice)
{
return false;
}
public function int GetPrice()
{
return 0;
}
public function int GetTotalPrice()
{
return 0;
}
public function int GetPriceOf(int ammoAmount)
{
return 0;
}
public function bool SetWeight(int newWeight)
{
return false;
}
public function int GetWeight()
{
return 0;
}
public function Add(int amount, optional bool forceAddition)
{
local Syringe syringeInstance;
syringeInstance = GetNativeInstance();
if (syringeInstance == none) {
return;
}
if (forceAddition) {
syringeInstance.ammoCharge[0] += amount;
}
else
{
syringeInstance.ammoCharge[0] =
Min(syringeInstance.maxAmmoCount,
syringeInstance.ammoCharge[0] + amount);
}
// Correct possible negative values
if (syringeInstance.ammoCharge[0] < 0) {
syringeInstance.ammoCharge[0] = 0;
}
}
public function int GetAmount()
{
local Syringe syringeInstance;
syringeInstance = GetNativeInstance();
if (syringeInstance == none) {
return 0;
}
return Max(0, syringeInstance.ammoCharge[0]);
}
public function int GetTotalAmount()
{
return GetAmount();
}
public function SetAmount(int amount, optional bool forceAddition)
{
local Syringe syringeInstance;
syringeInstance = GetNativeInstance();
if (syringeInstance == none) {
return;
}
if (forceAddition) {
syringeInstance.ammoCharge[0] = amount;
}
else
{
syringeInstance.ammoCharge[0] =
Min(syringeInstance.maxAmmoCount, amount);
}
// Correct possible negative values
if (syringeInstance.ammoCharge[0] < 0) {
syringeInstance.ammoCharge[0] = 0;
}
}
public function int GetMaxAmount()
{
local Syringe syringeInstance;
syringeInstance = GetNativeInstance();
if (syringeInstance == none) {
return 0;
}
return Max(0, syringeInstance.maxAmmoCount);
}
public function int GetMaxTotalAmount()
{
return GetMaxAmount();
}
public function bool SetMaxAmount(
int newMaxAmmo,
optional bool leaveCurrentAmmo)
{
return false;
}
public function bool SetMaxTotalAmount(
int newTotalMaxAmmo,
optional bool leaveCurrentAmmo)
{
return false;
}
public function bool HasWeapon()
{
return (GetNativeInstance() != none);
}
defaultproperties
{
}

168
sources/Gameplay/KF1Frontend/BaseImplementation/EKFUnknownItem.uc

@ -0,0 +1,168 @@
/**
* Dummy implementation for `EItem` interface that can wrap around `Inventory`
* instances that Acedia does not know about - including any non-weapons and
* non-ammo items added by any other mods.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class EKFUnknownItem extends EItem;
var private NativeActorRef inventoryReference;
protected function Finalizer()
{
_.memory.Free(inventoryReference);
inventoryReference = none;
}
/**
* Creates new `EKFUnknownItem` that refers to the `inventoryInstance`.
*
* @param inventoryInstance Native inventory instance that new
* `EKFUnknownItem` will represent.
* @return New `EKFUnknownItem` that represents given `inventoryInstance`.
* `none` iff `inventoryInstance` is either `none`.
*/
public final static /*unreal*/ function EKFUnknownItem Wrap(
Inventory inventoryInstance)
{
local EKFUnknownItem newReference;
if (inventoryInstance == none) return none;
if (Ammunition(inventoryInstance) != none) return none;
// This one is not actually used for anything, so it is not real
if (inventoryInstance.class == class'KFMod.FlashlightAmmo') return none;
newReference = EKFUnknownItem(__().memory.Allocate(class'EKFUnknownItem'));
newReference.inventoryReference = __().unreal.ActorRef(inventoryInstance);
return newReference;
}
/**
* Returns `Inventory` instance represented by the caller `EKFUnknownItem`.
*
* @return `Inventory` instance represented by the caller `EKFUnknownItem`.
*/
public final /*unreal*/ function Inventory GetNativeInstance()
{
if (inventoryReference != none) {
return Inventory(inventoryReference.Get());
}
return none;
}
public function EInterface Copy()
{
local Inventory inventoryInstance;
inventoryInstance = GetNativeInstance();
return Wrap(inventoryInstance);
}
public function bool Supports(class<EInterface> newInterfaceClass)
{
if (newInterfaceClass == none) return false;
if (newInterfaceClass == class'EItem') return true;
return false;
}
public function EInterface As(class<EInterface> newInterfaceClass)
{
if (!IsExistent()) {
return none;
}
if (newInterfaceClass == class'EItem') {
return Copy();
}
return none;
}
public function bool IsExistent()
{
return (GetNativeInstance() != none);
}
public function bool SameAs(EInterface other)
{
local EKFUnknownItem otherItem;
otherItem = EKFUnknownItem(other);
if (otherItem == none) {
return false;
}
return (GetNativeInstance() == otherItem.GetNativeInstance());
}
public function array<Text> GetTags()
{
local array<Text> emptyArray;
return emptyArray;
}
public function bool HasTag(Text tagToCheck)
{
return false;
}
public function Text GetTemplate()
{
local Inventory inventory;
inventory = GetNativeInstance();
if (inventory == none) {
return none;
}
return _.text.FromString(string(inventory.class));
}
public function Text GetName()
{
local Inventory inventory;
inventory = GetNativeInstance();
if (inventory == none) {
return none;
}
return _.text.FromString(inventory.GetHumanReadableName());
}
public function bool IsRemovable()
{
return true;
}
public function bool IsSellable()
{
return false;
}
public function bool SetPrice(int newPrice)
{
return false;
}
public function int GetPrice() { return 0; }
public function bool SetWeight(int newWeight)
{
return false;
}
public function int GetWeight()
{
return 0;
}
defaultproperties
{
}

158
sources/Gameplay/KF1Frontend/BaseImplementation/EKFWeapon.uc

@ -1,5 +1,5 @@
/** /**
* Implementation of `EItem` for classic Killing Floor weapons that changes * Implementation of `EWeapon` for classic Killing Floor weapons that changes
* as little as possible and only on request from another mod, otherwise not * as little as possible and only on request from another mod, otherwise not
* altering gameplay at all. * altering gameplay at all.
* Copyright 2021 - 2022 Anton Tarasenko * Copyright 2021 - 2022 Anton Tarasenko
@ -19,11 +19,12 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. * along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/ */
class EKFWeapon extends EItem class EKFWeapon extends EWeapon;
abstract;
var private NativeActorRef weaponReference; var private NativeActorRef weaponReference;
var private config array< class<KFWeapon> > weaponsWithFlashlight;
protected function Finalizer() protected function Finalizer()
{ {
_.memory.Free(weaponReference); _.memory.Free(weaponReference);
@ -40,11 +41,59 @@ protected function Finalizer()
public final static /*unreal*/ function EKFWeapon Wrap(KFWeapon weaponInstance) public final static /*unreal*/ function EKFWeapon Wrap(KFWeapon weaponInstance)
{ {
local EKFWeapon newReference; local EKFWeapon newReference;
if (weaponInstance == none) {
return none;
}
newReference = EKFWeapon(__().memory.Allocate(class'EKFWeapon')); newReference = EKFWeapon(__().memory.Allocate(class'EKFWeapon'));
newReference.weaponReference = __().unreal.ActorRef(weaponInstance); newReference.weaponReference = __().unreal.ActorRef(weaponInstance);
return newReference; return newReference;
} }
public function EInterface Copy()
{
local KFWeapon weaponInstance;
weaponInstance = GetNativeInstance();
return Wrap(weaponInstance);
}
public function bool Supports(class<EInterface> newInterfaceClass)
{
if (newInterfaceClass == none) return false;
if (newInterfaceClass == class'EWeapon') return true;
if (newInterfaceClass == class'EKFWeapon') return true;
return false;
}
public function EInterface As(class<EInterface> newInterfaceClass)
{
if (!IsExistent()) {
return none;
}
if ( newInterfaceClass == class'EItem'
|| newInterfaceClass == class'EWeapon'
|| newInterfaceClass == class'EKFWeapon')
{
return Copy();
}
return none;
}
public function bool IsExistent()
{
return (GetNativeInstance() != none);
}
public function bool SameAs(EInterface other)
{
local EKFWeapon otherWeapon;
otherWeapon = EKFWeapon(other);
if (otherWeapon == none) {
return false;
}
return (GetNativeInstance() == otherWeapon.GetNativeInstance());
}
/** /**
* Returns `KFWeapon` instance represented by the caller `EKFWeapon`. * Returns `KFWeapon` instance represented by the caller `EKFWeapon`.
* *
@ -65,9 +114,19 @@ public function array<Text> GetTags()
if (weaponReference.Get() == none) return tagArray; if (weaponReference.Get() == none) return tagArray;
tagArray[0] = P("weapon").Copy(); tagArray[0] = P("weapon").Copy();
tagArray[1] = P("visible").Copy();
return tagArray; return tagArray;
} }
public function bool HasTag(Text tagToCheck)
{
if (tagToCheck == none) return false;
if (tagToCheck.Compare(P("weapon"))) return true;
if (tagToCheck.Compare(P("visible"))) return true;
return false;
}
public function Text GetTemplate() public function Text GetTemplate()
{ {
local Weapon weapon; local Weapon weapon;
@ -85,7 +144,7 @@ public function Text GetName()
weapon = Weapon(weaponReference.Get()); weapon = Weapon(weaponReference.Get());
if (weapon == none) return none; if (weapon == none) return none;
return _.text.FromString(Locs(weapon.itemName)); return _.text.FromString(weapon.GetHumanReadableName());
} }
public function bool IsRemovable() public function bool IsRemovable()
@ -145,6 +204,97 @@ public function int GetWeight()
return int(kfWeapon.weight); return int(kfWeapon.weight);
} }
public function array<EAmmo> GetAvailableAmmo()
{
local EAmmo nextAmmo;
local KFWeapon kfWeapon;
local Inventory nextInventory;
local array<EAmmo> result;
local class<Ammunition> ammoClass1, ammoClass2;
if (weaponReference == none) return result;
kfWeapon = KFWeapon(weaponReference.Get());
if (kfWeapon == none) return result;
if (kfWeapon.owner == none) return result;
ammoClass1 = _.unreal.inventory.GetAmmoClass(kfWeapon, 0);
ammoClass2 = _.unreal.inventory.GetAmmoClass(kfWeapon, 1);
nextInventory = kfWeapon.owner.inventory;
while (nextInventory != none)
{
if ( nextInventory.class == ammoClass1
|| nextInventory.class == ammoClass2)
{
nextAmmo = class'EKFAmmo'.static.Wrap(Ammunition(nextInventory));
if (nextAmmo != none) {
result[result.length] = nextAmmo;
}
// Reset temporary variable to avoid adding same `EKFAmmo` twice
nextAmmo = none;
}
nextInventory = nextInventory.inventory;
}
result = AddSpecialAmmo(kfWeapon, result);
return result;
}
private function array<EAmmo> AddSpecialAmmo(
KFWeapon kfWeapon,
array<EAmmo> ammoCollection)
{
local EAmmo nextAmmo;
local KFMedicGun kfMedicWeapon;
if (kfWeapon == none) {
return ammoCollection;
}
kfMedicWeapon = KFMedicGun(kfWeapon);
if (kfMedicWeapon != none)
{
nextAmmo = class'EKFMedicAmmo'.static.Wrap(kfMedicWeapon);
if (nextAmmo != none) {
ammoCollection[ammoCollection.length] = nextAmmo;
}
}
if (HasFlashlight(kfWeapon))
{
nextAmmo =
class'EKFFlashlightAmmo'.static.Wrap(KFHumanPawn(kfWeapon.owner));
if (nextAmmo != none) {
ammoCollection[ammoCollection.length] = nextAmmo;
}
}
if (kfWeapon.class == class'KFMod.Syringe')
{
nextAmmo =
class'EKFSyringeAmmo'.static.Wrap(Syringe(kfWeapon));
if (nextAmmo != none) {
ammoCollection[ammoCollection.length] = nextAmmo;
}
}
return ammoCollection;
}
private function bool HasFlashlight(KFWeapon weapon)
{
local int i;
if (weapon == none) {
return false;
}
for (i = 0; i < weaponsWithFlashlight.length; i += 1)
{
if (weapon.class == weaponsWithFlashlight[i]) {
return true;
}
}
return false;
}
defaultproperties defaultproperties
{ {
weaponsWithFlashlight(0) = class'Single'
weaponsWithFlashlight(1) = class'Dualies'
weaponsWithFlashlight(2) = class'Shotgun'
weaponsWithFlashlight(3) = class'CamoShotgun'
weaponsWithFlashlight(4) = class'NailGun'
weaponsWithFlashlight(5) = class'BenelliShotgun'
weaponsWithFlashlight(6) = class'GoldenBenelliShotgun'
} }

1
sources/Gameplay/KF1Frontend/KF1_Frontend.uc

@ -33,5 +33,6 @@ public function EItemTemplateInfo GetItemTemplateInfo(Text templateName)
defaultproperties defaultproperties
{ {
templatesClass = class'KF1_TemplatesComponent'
tradingClass = class'KF1_TradingComponent' tradingClass = class'KF1_TradingComponent'
} }

215
sources/Gameplay/KF1Frontend/Trading/KF1_TemplatesComponent.uc

@ -0,0 +1,215 @@
/**
* `ATemplatesComponent`'s implementation for `KF1_Frontend`.
* Lists weapons available at the trader, provides support for per-perk lists,
* derived from the `KFLevelRules`.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class KF1_TemplatesComponent extends ATemplatesComponent;
var private bool listsAreReady;
// TODO: add tools
var private array<Text> availableWeaponLists;
var private array<Text> allWeaponsList;
var private array<Text> medicWeaponsList;
var private array<Text> supportWeaponsList;
var private array<Text> sharpshooterWeaponsList;
var private array<Text> commandoWeaponsList;
var private array<Text> berserkerWeaponsList;
var private array<Text> firebugWeaponsList;
var private array<Text> demolitionWeaponsList;
var private array<Text> neutralWeaponsList;
protected function Finalizer()
{
_.memory.FreeMany(allWeaponsList);
_.memory.FreeMany(medicWeaponsList);
_.memory.FreeMany(supportWeaponsList);
_.memory.FreeMany(sharpshooterWeaponsList);
_.memory.FreeMany(commandoWeaponsList);
_.memory.FreeMany(berserkerWeaponsList);
_.memory.FreeMany(firebugWeaponsList);
_.memory.FreeMany(demolitionWeaponsList);
_.memory.FreeMany(neutralWeaponsList);
_.memory.FreeMany(availableWeaponLists);
if (allWeaponsList.length > 0) {
allWeaponsList.length = 0;
}
if (medicWeaponsList.length > 0) {
medicWeaponsList.length = 0;
}
if (supportWeaponsList.length > 0) {
supportWeaponsList.length = 0;
}
if (sharpshooterWeaponsList.length > 0) {
sharpshooterWeaponsList.length = 0;
}
if (commandoWeaponsList.length > 0) {
commandoWeaponsList.length = 0;
}
if (berserkerWeaponsList.length > 0) {
berserkerWeaponsList.length = 0;
}
if (firebugWeaponsList.length > 0) {
firebugWeaponsList.length = 0;
}
if (demolitionWeaponsList.length > 0) {
demolitionWeaponsList.length = 0;
}
if (neutralWeaponsList.length > 0) {
neutralWeaponsList.length = 0;
}
if (availableWeaponLists.length > 0) {
availableWeaponLists.length = 0;
}
listsAreReady = false;
}
private function BuildKFWeaponLists()
{
local LevelInfo level;
local KFLevelRules kfLevelRules;
if (listsAreReady) return;
level = _.unreal.GetLevel();
if (level == none) return;
foreach level.DynamicActors(class'KFMod.KFLevelRules', kfLevelRules) break;
if (kfLevelRules == none) return;
medicWeaponsList = MakeWeaponList(kfLevelRules.mediItemForSale);
supportWeaponsList = MakeWeaponList(kfLevelRules.suppItemForSale);
sharpshooterWeaponsList = MakeWeaponList(kfLevelRules.shrpItemForSale);
commandoWeaponsList = MakeWeaponList(kfLevelRules.commItemForSale);
berserkerWeaponsList = MakeWeaponList(kfLevelRules.bersItemForSale);
firebugWeaponsList = MakeWeaponList(kfLevelRules.fireItemForSale);
demolitionWeaponsList = MakeWeaponList(kfLevelRules.demoItemForSale);
neutralWeaponsList = MakeWeaponList(kfLevelRules.neutItemForSale);
availableWeaponLists[0] = _.text.FromString("all weapons");
availableWeaponLists[1] = _.text.FromString("trading weapons");
availableWeaponLists[2] = _.text.FromString("medic weapons");
availableWeaponLists[3] = _.text.FromString("support weapons");
availableWeaponLists[4] = _.text.FromString("sharpshooter weapons");
availableWeaponLists[5] = _.text.FromString("commando weapons");
availableWeaponLists[6] = _.text.FromString("firebug weapons");
availableWeaponLists[7] = _.text.FromString("demolition weapons");
availableWeaponLists[8] = _.text.FromString("neutral weapons");
listsAreReady = true;
}
private function array<Text> MakeWeaponList(array< class<Pickup> > shopList)
{
local int i;
local Text nextTemplate;
local class<Weapon> nextWeaponClass;
local array<Text> resultArray;
if (listsAreReady) {
return resultArray;
}
for (i = 0; i < shopList.length; i += 1)
{
if (shopList[i] == none) continue;
nextWeaponClass = class<Weapon>(shopList[i].default.inventoryType);
if (nextWeaponClass == none) continue;
nextTemplate = _.text.FromString(string(nextWeaponClass));
resultArray[resultArray.length] = nextTemplate.Copy();
allWeaponsList[allWeaponsList.length] = nextTemplate;
}
return resultArray;
}
private function array<Text> CopyList(array<Text> inputList)
{
local int i;
local array<Text> outputList;
// `inputList` is guaranteed to not contain invalid `Text` objects
for (i = 0; i < inputList.length; i += 1) {
outputList[outputList.length] = inputList[i].Copy();
}
return outputList;
}
public function bool ItemListExists(Text listName)
{
local string listNameAsString;
if (listName == none) return false;
listNameAsString = listName.ToString();
if (listNameAsString == "weapons") return true;
if (listNameAsString == "all weapons") return true;
if (listNameAsString == "trading weapons") return true;
if (listNameAsString == "medic weapons") return true;
if (listNameAsString == "support weapons") return true;
if (listNameAsString == "sharpshooter weapons") return true;
if (listNameAsString == "commando weapons") return true;
if (listNameAsString == "berserker weapons") return true;
if (listNameAsString == "firebug weapons") return true;
if (listNameAsString == "demolition weapons") return true;
if (listNameAsString == "neutral weapons") return true;
return false;
}
public function array<Text> GetItemList(Text listName)
{
local string listNameAsString;
local array<Text> emptyArray;
if (listName == none) {
return emptyArray;
}
listNameAsString = listName.ToString();
BuildKFWeaponLists();
if ( listNameAsString == "weapons"
|| listNameAsString == "all weapons"
|| listNameAsString == "trading weapons")
{
return CopyList(allWeaponsList);
}
if (listNameAsString == "medic weapons") {
return CopyList(medicWeaponsList);
}
if (listNameAsString == "support weapons") {
return CopyList(supportWeaponsList);
}
if (listNameAsString == "sharpshooter weapons") {
return CopyList(sharpshooterWeaponsList);
}
if (listNameAsString == "commando weapons") {
return CopyList(commandoWeaponsList);
}
if (listNameAsString == "berserker weapons") {
return CopyList(berserkerWeaponsList);
}
if (listNameAsString == "firebug weapons") {
return CopyList(firebugWeaponsList);
}
if (listNameAsString == "demolition weapons") {
return CopyList(demolitionWeaponsList);
}
if (listNameAsString == "neutral weapons") {
return CopyList(neutralWeaponsList);
}
return emptyArray;
}
public function array<Text> GetAvailableLists()
{
BuildKFWeaponLists();
return CopyList(availableWeaponLists);
}
defaultproperties
{
}

2
sources/Players/EPlayer.uc

@ -134,7 +134,7 @@ public function bool SameAs(EInterface other)
if (other == none) return false; if (other == none) return false;
if (controller == none) return false; if (controller == none) return false;
asPlayer = EPlayer(other); asPlayer = EPlayer(other);
if (asPlayer != none) return false; if (asPlayer == none) return false;
otherController = asPlayer.controller; otherController = asPlayer.controller;
if (otherController == none) return false; if (otherController == none) return false;

259
sources/Players/Inventory/EAmmo.uc

@ -0,0 +1,259 @@
/**
* Abstract interface that represents ammunition of a certain type.
* Ammunition methods make distinction between "amount" and "total amount":
* * "Amount" is how much of this ammo is stored in this
* inventory item, "max amount" is how much it can store at once.
* These values can be affected by other items in the inventory.
* * "Total amount" is how much ammo of this type player has in his
* inventory in total.
* Neither amounts can ever be negative.
* For Killing Floor "total ammo" corresponds to the amount of ammunition
* associated with a weapon, while "ammo" would correspond to the amount of
* ammo still unleaded into the weapon. This means that "max ammo" becomes
* quite a bizarre value that depends on how full your magazine is.
* For example, if you bought lever action rifle, filled it
* with ammo (80 bullets) and then shot out 6, you will have:
* * "Ammo" == 70 - since you will have that much still unloaded;
* * "Max ammo" == 76 - since with 4 loaded bullets you can only
* have 76 unloaded ones;
* * "Total ammo" == 74 - amount of bullets you can still shoot;
* * "Max total ammo" = 80 - since that is the limit of LAR bullets you
* can carry in total.
* When one loads ammo into the weapon, its "amount" decreases, but its
* "total amount" stays the same. Unless specified otherwise, all the methods
* deal with a regular "amount".
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class EAmmo extends EItem
abstract;
/**
* Changes amount of ammo inside referred ammunition item by given `amount`.
*
* Negative argument values will decrease it ("adding" a negative amount).
*
* New value cannot go below zero and can only exceed maximum amount
* (@see `GetMaxAmount()` and @see `GetMaxTotalAmount()`) if `forceAddition`
* is also set to `true`.
* If resulting value is to go over the limits - it will be clamped inside
* allowed range.
*
* @param amount How much ammo to add. `0` does nothing, negative
* values decrease the total ammo count. Cannot force total ammo to go into
* negative values.
* @param forceAddition This parameter is only relevant when changing ammo
* amount by `amount` will go over maximum (total) amount that referred
* ammo item can store. Setting this parameter to `true` will allow you to
* add more ammo than caller `EAmmo` normally supports.
* Cannot force total amount below zero.
*/
public function Add(int amount, optional bool forceAddition) {}
/**
* Returns current price of total ammo inside inventory of the owner of
* referred ammunition item.
*
* In comparison, `EItem`'s method `GetPrice()` returns the price of only
* the ammo inside the referred item.
* @return Current price of total ammo inside inventory of the owner of
* referred ammunition item.
*/
public function int GetTotalPrice()
{
return 0;
}
/**
* Returns how much would `ammoAmount` amount of referred ammo item would cost.
*
* @return Price of `ammoAmount` amount of referred ammo item.
*/
public function int GetPriceOf(int ammoAmount)
{
return 0;
}
/**
* Returns current amount of ammo inside referred ammunition item.
*
* Guaranteed to not be negative, but can exceed maximum value
* (@see `GetMaxAmount()`).
*
* @return Current amount of ammo inside referred ammunition item.
*/
public function int GetAmount()
{
return 0;
}
/**
* Returns current total amount of ammo inside inventory of the owner of
* referred ammunition item.
*
* Guaranteed to not be negative, but can exceed maximum value
* (@see `GetMaxTotalAmount()`).
*
* @return Current amount of ammo inside referred ammunition item.
*/
public function int GetTotalAmount()
{
return 0;
}
/**
* Changes amount of ammo inside referred ammunition item.
*
* Negative values will be treated as `0`. Values that exceed
* maximum (total) amount will be automatically reduced to said maximum amount
* (@see `GetMaxAmount()` and @see `GetMaxTotalAmount()`), unless
* `forceAddition` is also set to `true`.
* If resulting value is to go over the limits - it will be clamped inside
* allowed range.
*
* @param amount How much ammo should referred ammunition item have.
* Negative values are treated like `0`.
* @param forceAddition This parameter is only relevant when `amount` is
* higher than maximum (total) amount that referred ammo item can store.
* Setting this parameter to `true` will allow you to add more ammo than
* caller `EAmmo` normally supports. Cannot force total amount below zero.
*/
public function SetAmount(int amount, optional bool forceAddition) {}
/**
* Returns maximum amount of ammo referred ammunition item supports.
*
* This is not a hard limit and can be bypassed by `SetAmount()` and
* `Add()` methods, meaning that it is possible that
* `GetAmount() > GetMaxAmount()`.
* Treat this value like a limit obtainable through "normal means", that
* can only be exceeded through cheats or special powerups of some kind.
*
* @return Current "soft" max ammo limit of the referred ammunition item.
* Returning negative value means that there is no upper limit.
* Zero is considered a valid value.
*/
public function int GetMaxAmount()
{
return 0;
}
/**
* Returns maximum total amount of ammo owner of the referred ammunition item
* can hold.
*
* This is not a hard limit and can be bypassed by `SetAmount()` and
* `Add()` methods, meaning that it is possible that
* `GetTotalAmount() > GetMaxTotalAmount()`.
* Treat this value like a limit obtainable through "normal means", that
* can only be exceeded through cheats or special powerups of some kind.
*
* @return Current "soft" max total ammo limit of the referred ammunition item.
* Returning negative value means that there is no upper limit.
* Zero is considered a valid value.
*/
public function int GetMaxTotalAmount()
{
return 0;
}
/**
* Changes maximum amount of ammo referred ammunition item supports.
*
* This is not a hard limit and can be bypassed by `SetAmount()` and
* `Add()` methods, meaning that it is possible that
* `GetAmount() > GetMaxAmount()`.
* Treat this value like a limit obtainable through "normal means", that
* can only be exceeded through cheats or special powerups of some kind.
*
* Referred ammunition item does not have to support this method and is
* allowed to refuse changing maximum ammo value. It can also only support
* certain ranges of values.
*
* @param newMaxAmmo New maximum ammo referred ammunition
* should support. Negative values mean unlimited maximum value.
* @param leaveCurrentAmmo Default value of `false` will result in current
* ammo being updated to not exceed `newMaxAmmo`, while setting this to
* `true` will leave it unchanged.
*
* @return `true` if maximum value was changed and `false` otherwise.
*/
public function bool SetMaxAmount(
int newMaxAmmo,
optional bool leaveCurrentAmmo)
{
return false;
}
/**
* Changes maximum total amount of ammo that owner of the referred
* ammunition item supports.
*
* This is not a hard limit and can be bypassed by `SetAmount()` and
* `Add()` methods, meaning that it is possible that
* `GetTotalAmount() > GetMaxTotalAmount()`.
* Treat this value like a limit obtainable through "normal means", that
* can only be exceeded through cheats or special powerups of some kind.
*
* Referred ammunition item does not have to support this method is allowed to
* refuse changing maximum ammo value. It can also only support certain ranges
* of values.
*
* @param newTotalMaxAmmo New maximum total ammo owner of the referred
* ammunition can have. Negative values mean unlimited maximum value.
* @param leaveCurrentAmmo Default value of `false` will result in current
* total ammo being updated to not exceed `newTotalMaxAmmo`, while setting
* this to `true` will leave it unchanged.
*
* @return `true` if maximum value was changed and `false` otherwise.
*/
public function bool SetMaxTotalAmount(
int newTotalMaxAmmo,
optional bool leaveCurrentAmmo)
{
return false;
}
/**
* Checks whether the owner of the referred ammo item also has a weapon that
* can be loaded with that ammo.
*
* @return `true` if owner of the referred ammo has a weapon that can be
* loaded with that ammo and `false` otherwise.
*/
public function bool HasWeapon()
{
return false;
}
/**
* Maxes out amount of ammo of the referred ammunition item.
*
* Does nothing if current ammo is already at (or higher) than maximum value
* (@see `GetMaxAmount()`).
*/
public function Fill()
{
if (GetMaxAmount() < 0) return;
if (GetAmount() >= GetMaxAmount()) return;
SetAmount(GetMaxAmount());
}
defaultproperties
{
}

155
sources/Players/Inventory/EInventory.uc

@ -21,7 +21,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. * along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/ */
class EInventory extends AcediaObject class EInventory extends EInterface
abstract; abstract;
/** /**
@ -30,7 +30,7 @@ class EInventory extends AcediaObject
* This method should not be called manually, unless you implement your own * This method should not be called manually, unless you implement your own
* game interface. * game interface.
* *
* Cannot fail for any connected player and can assume it will not be called * Cannot fail for any connected player and will assume it will not be called
* for not connected ones. * for not connected ones.
* *
* @param player `EPlayer` for which to initialize this inventory. * @param player `EPlayer` for which to initialize this inventory.
@ -44,17 +44,23 @@ public function Initialize(EPlayer player) {}
* inventory system - it can refuse it. * inventory system - it can refuse it.
* *
* @param newItem New item to add to the caller inventory system. * @param newItem New item to add to the caller inventory system.
* Can be destroyed as a result of this call, if it gets merged with
* another weapon inside the inventory.
* @param forceAddition This parameter is only relevant when `newItem` * @param forceAddition This parameter is only relevant when `newItem`
* cannot be added in the caller inventory system. If it cannot be added * 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` * 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 * 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`. Removing items is only allowed if it will actually let us add
* `newItem`. How removal will be done is up to the implementation. * `newItem`. How removal will be done is up to the implementation.
* @return `true` if `newItem` was added and `false` otherwise. * @return `EItem` added as a result. Can be different from `newItem` in case
* inventory made it "merge" with another weapon. This can happen,
* for example, if we add a single pistol when inventory already contains
* pistol of the same type.
* `none` if we have failed to add `newItem` to the inventory.
*/ */
public function bool Add(EItem newItem, optional bool forceAddition) public function EItem Add(EItem newItem, optional bool forceAddition)
{ {
return false; return none;
} }
/** /**
@ -72,19 +78,22 @@ public function bool Add(EItem newItem, optional bool forceAddition)
* allows caller inventory system to get rid of such items to make room for * 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. Removing items is only allowed if it will actually let us add
* new item. How removal will be done is up to the implementation. * new item. How removal will be done is up to the implementation.
* @return `true` if new item was added and `false` otherwise. * @return Reference to `EItem` interface to the added item entity,
* `none` iff adding item has failed.
*/ */
public function bool AddTemplate( public function EItem AddTemplate(
Text newItemTemplate, Text newItemTemplate,
optional bool forceAddition) optional bool forceAddition)
{ {
return false; return none;
} }
/** /**
* Checks whether given item `itemToCheck` can be added to the caller * Checks whether given item `itemToCheck` can be added to the caller
* inventory system. * inventory system.
* *
* See also `CanAddExplain()`.
*
* @param itemToCheck Item to check for whether we can add it to * @param itemToCheck Item to check for whether we can add it to
* the caller `EInventory`. * the caller `EInventory`.
* @param forceAddition New items can be added with or without * @param forceAddition New items can be added with or without
@ -93,15 +102,24 @@ public function bool AddTemplate(
* @return `true` if given `itemToCheck` can be added to the caller * @return `true` if given `itemToCheck` can be added to the caller
* inventory system with given flag `forceAddition` and `false` otherwise. * inventory system with given flag `forceAddition` and `false` otherwise.
*/ */
public function bool CanAdd(EItem itemToCheck, optional bool forceAddition) public final function bool CanAdd(
EItem itemToCheck,
optional bool forceAddition)
{ {
return false; local bool success;
local Text explanation;
explanation = CanAddExplain(itemToCheck, forceAddition);
success = (explanation == none);
_.memory.Free(explanation);
return success;
} }
/** /**
* Checks whether item with given template `itemToCheck` can be added to * Checks whether item with given template `itemToCheck` can be added to
* the caller inventory system. * the caller inventory system.
* *
* See also `CanAddTemplateExplain()`.
*
* @param itemTemplateToCheck Template of the item to check for whether we can * @param itemTemplateToCheck Template of the item to check for whether we can
* add it to the caller `EInventory`. * add it to the caller `EInventory`.
* @param forceAddition New items can be added with or without * @param forceAddition New items can be added with or without
@ -115,7 +133,58 @@ public function bool CanAddTemplate(
Text itemTemplateToCheck, Text itemTemplateToCheck,
optional bool forceAddition) optional bool forceAddition)
{ {
return false; local bool success;
local Text explanation;
explanation = CanAddTemplateExplain(itemTemplateToCheck, forceAddition);
success = (explanation == none);
_.memory.Free(explanation);
return success;
}
/**
* Checks whether given item `itemToCheck` can be added to the caller
* inventory system and provides short explanation (dependent on
* implementation) if item cannot be added.
*
* See also `CanAdd()`.
*
* @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 `none` if given `itemToCheck` can be added to the caller
* inventory system with given flag `forceAddition` and `Text` with
* description of reason why not otherwise.
*/
public function Text CanAddExplain(
EItem itemToCheck,
optional bool forceAddition)
{
return none;
}
/**
* Checks whether item with given template `itemToCheck` can be added to
* the caller inventory system and provides short explanation (dependent on
* implementation) if item cannot be added.
*
* See also `CanAddTemplate()`.
*
* @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 `none` if given `itemToCheck` can be added to the caller
* inventory system with given flag `forceAddition` and `Text` with
* description of reason why not otherwise.
*/
public function Text CanAddTemplateExplain(
Text itemTemplateToCheck,
optional bool forceAddition)
{
return none;
} }
/** /**
@ -145,7 +214,8 @@ public function bool Remove(
} }
/** /**
* Removes item of type `itemTemplateToRemove` from the caller `EInventory`. * Removes item with given template `itemTemplateToRemove` from the caller
* `EInventory`.
* *
* By default removes one arbitrary (can be based on simple convenience of * By default removes one arbitrary (can be based on simple convenience of
* implementation) item, but optional parameter can make it remove all items * implementation) item, but optional parameter can make it remove all items
@ -194,13 +264,18 @@ public function bool RemoveTemplate(
* @param forceRemoval Set this to `true` if item must be removed * @param forceRemoval Set this to `true` if item must be removed
* no matter what. Otherwise inventory system can refuse removal of items, * no matter what. Otherwise inventory system can refuse removal of items,
* whose `IsRemovable()` returns `false`. * whose `IsRemovable()` returns `false`.
* @param forceRemoval Set this to `true` if even invisible to the player
* items have to be removed. In Killing Floor only weapons are visible to
* the player (ammunition items are considered to be just
* their parameters).
* @return `true` if any `EItem` was removed and `false` otherwise * @return `true` if any `EItem` was removed and `false` otherwise
* (including the case where no `EItem`s were kept in the caller * (including the case where no `EItem`s were kept in the caller
* `EInventory` in the first place). * `EInventory` in the first place).
*/ */
public function bool RemoveAll( public function bool RemoveAll(
optional bool keepItems, optional bool keepItems,
optional bool forceRemoval) optional bool forceRemoval,
optional bool includeHidden)
{ {
return false; return false;
} }
@ -217,6 +292,20 @@ public function bool Contains(EItem itemToCheck)
return false; return false;
} }
/**
* Checks whether caller `EInventory` contains item with given template
* `itemTemplateToCheck`.
*
* @param itemTemplateToCheck Template we want to check for belonging to
* the caller `EInventory`.
* @result `true` if item with a given template does belong to the inventory
* and `false` otherwise.
*/
public function bool ContainsTemplate(Text itemTemplateToCheck)
{
return false;
}
/** /**
* Returns array with all `EItem`s contained inside the caller `EInventory`. * Returns array with all `EItem`s contained inside the caller `EInventory`.
* *
@ -228,6 +317,21 @@ public function array<EItem> GetAllItems()
return emptyArray; return emptyArray;
} }
/**
* Returns array with all `EItem`s contained inside the caller `EInventory`
* that support interface of class `interfaceClass`.
*
* @return Array with all `EItem`s that support interface of
* class `interfaceClass` contained inside the caller `EInventory`.
* Guaranteed to not contain `none` references of interfaces to
* inexistent entities.
*/
public function array<EItem> GetItemsSupporting(class<EItem> interfaceClass)
{
local array<EItem> emptyArray;
return emptyArray;
}
/** /**
* Returns array with all `EItem`s contained inside the caller `EInventory` * Returns array with all `EItem`s contained inside the caller `EInventory`
* that has specified tag `tag`. * that has specified tag `tag`.
@ -235,6 +339,8 @@ public function array<EItem> GetAllItems()
* @param tag Tag, which items we want to get. * @param tag Tag, which items we want to get.
* @return Array with all `EItem`s contained inside the caller `EInventory` * @return Array with all `EItem`s contained inside the caller `EInventory`
* that has specified tag `tag`. * that has specified tag `tag`.
* Guaranteed to not contain `none` references of interfaces to
* inexistent entities.
*/ */
public function array<EItem> GetTagItems(Text tag) public function array<EItem> GetTagItems(Text tag)
{ {
@ -254,6 +360,7 @@ public function array<EItem> GetTagItems(Text tag)
* @param tag Tag, which item we want to get. * @param tag Tag, which item we want to get.
* @return `EItem` contained inside the caller `EInventory` that belongs to * @return `EItem` contained inside the caller `EInventory` that belongs to
* the specified tag `tag`. * the specified tag `tag`.
* Guaranteed to not be `none` or refer to non-existent entity.
*/ */
public function EItem GetTagItem(Text tag) { return none; } public function EItem GetTagItem(Text tag) { return none; }
@ -264,6 +371,8 @@ public function EItem GetTagItem(Text tag) { return none; }
* @param template Template, that items we want to get originated from. * @param template Template, that items we want to get originated from.
* @return Array with all `EItem`s contained inside the caller `EInventory` * @return Array with all `EItem`s contained inside the caller `EInventory`
* that originated from the specified template `template`. * that originated from the specified template `template`.
* Guaranteed to not contain `none` references or interfaces to
* inexistent entities.
*/ */
public function array<EItem> GetTemplateItems(Text template) public function array<EItem> GetTemplateItems(Text template)
{ {
@ -272,7 +381,7 @@ public function array<EItem> GetTemplateItems(Text template)
} }
/** /**
* Returns array with all `EItem`s contained inside the caller `EInventory` * Returns `EItem`s contained inside the caller `EInventory`
* that originated from the specified template `template`. * that originated from the specified template `template`.
* *
* If several `EItem`s inside caller `EInventory` originated from * If several `EItem`s inside caller `EInventory` originated from
@ -283,9 +392,27 @@ public function array<EItem> GetTemplateItems(Text template)
* @param template Template, that item we want to get originated from. * @param template Template, that item we want to get originated from.
* @return `EItem`s contained inside the caller `EInventory` that originated * @return `EItem`s contained inside the caller `EInventory` that originated
* from the specified template `template`. * from the specified template `template`.
* Guaranteed to not be `none` or refer to non-existent entity.
*/ */
public function EItem GetTemplateItem(Text template) { return none; } public function EItem GetTemplateItem(Text template) { return none; }
/**
* Returns array of caller `EInventory`'s items that are currently equipped by
* its owner player.
*
* @return Array with all `EItem`s contained inside the caller `EInventory`
* that are equipped by its owner.
* Guaranteed to not contain `none` references or interfaces to
* inexistent entities.
*/
public function array<EItem> GetEquippedItems()
{
local array<EItem> emptyArray;
return emptyArray;
}
public function EItem GetEquippedItem() { return none; }
defaultproperties defaultproperties
{ {
} }

10
sources/Players/Inventory/EItem.uc

@ -30,7 +30,7 @@
* 2. Weight. Accessed by `SetWeight()` / `GetWeight()`. * 2. Weight. Accessed by `SetWeight()` / `GetWeight()`.
* All of these parameters can be ignored if they are not applicable to * All of these parameters can be ignored if they are not applicable to
* a certain type of item. * a certain type of item.
* Copyright 2021 Anton Tarasenko * Copyright 2021 - 2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -47,7 +47,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. * along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/ */
class EItem extends AcediaObject class EItem extends EInterface
abstract; abstract;
/** /**
@ -63,6 +63,12 @@ public function array<Text> GetTags()
return emptyArray; return emptyArray;
} }
// TODO: document this
public function bool HasTag(Text tagToCheck)
{
return false;
}
/** /**
* Returns template caller `EItem` was created from. * Returns template caller `EItem` was created from.
* *

57
sources/Players/Inventory/EWeapon.uc

@ -0,0 +1,57 @@
/**
* Abstract interface that represents any kind of weapon.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class EWeapon extends EItem
abstract;
/**
* Returns `EAmmo` for every ammo item that can be used with the caller's
* referred weapon. Method looks for that ammo in the weapon's owner's
* inventory.
*
* @return Array of `EAmmo`s that refer to ammo items suitable for use with
* referred weapon. Every item of the returned array is guaranteed to not
* be `none` and refer to an existent item.
*/
public function array<EAmmo> GetAvailableAmmo()
{
local array<EAmmo> emptyArray;
return emptyArray;
}
/**
* Fills (@see `EAmmo.Fill()` method) `EAmmo` for every ammo item that can be
* used with the caller's referred weapon. Method looks for that ammo in
* the weapon's owner's inventory.
*/
public final function FillAmmo()
{
local int i;
local array<EAmmo> myAmmo;
myAmmo = GetAvailableAmmo();
for (i = 0; i < myAmmo.length; i += 1) {
myAmmo[i].Fill();
}
_.memory.FreeMany(myAmmo);
}
defaultproperties
{
}

11
sources/Text/MutableText.uc

@ -100,6 +100,17 @@ public final function MutableText AppendCharacter(Text.Character newCharacter)
return MutableText(AppendCodePoint(newCharacter.codePoint)); return MutableText(AppendCodePoint(newCharacter.codePoint));
} }
/**
* Adds new line character to the end of the caller `MutableText`.
*
* @return Caller `MutableText` to allow for method chaining.
*/
public final function MutableText AppendNewLine()
{
AppendCodePoint(CODEPOINT_NEWLINE);
return self;
}
/** /**
* Converts caller `MutableText` instance into lower case. * Converts caller `MutableText` instance into lower case.
*/ */

5
sources/Types/AcediaActor.uc

@ -397,4 +397,9 @@ public static function _cleanup()
defaultproperties defaultproperties
{ {
RemoteRole = ROLE_None
drawType = DT_None
bCollideActors = false
bCollideWorld = false
bBlockActors = false
} }

530
sources/Unreal/InventoryAPI/InventoryAPI.uc

@ -0,0 +1,530 @@
/**
* Low-level API that provides set of utility methods for working with
* unreal script inventory classes, including some Killing Floor specific
* methods that depend on how its weapons work.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class InventoryAPI extends AcediaObject
config(AcediaSystem);
/**
* Describes a single-dual weapons class pair.
* For example, `single = class'MK23Pickup'` and
* `dual = class'DualMK23Pickup'`.
*/
struct DualiesPair
{
var class<KFWeaponPickup> single;
var class<KFWeaponPickup> dual;
};
// All dual pairs that Acedia will recognize
var private const config array<DualiesPair> dualiesClasses;
/**
* Describe the role of the weapon regarding a dual wielding.
* All weapons have a dual wielding role, although for most it
* is simply `DWR_None`.
*/
enum DualWieldingRole
{
// Not a dual weapons and cannot be dual wielded;
// Most weapons are in this category (e.g. lar, ak47, husk cannon, etc.)
DWR_None,
// Not a dual weapon, but can be dual wielded (e.g. single pistols)
DWR_Single,
// A dual weapon, consisted of two single ones (e.g. dual pistols)
DWR_Dual
};
/**
* Returns array of single - dual pairs (`DualiesPair`) that defines which
* single weapon class corresponds to which dual class.
* For example, `KFMod.GoldenDeaglePickup` is a single class corresponding
* to the `KFMod.GoldenDualDeaglePickup` dual class.
*/
public function array<DualiesPair> GetDualiesPairs()
{
return dualiesClasses;
}
/**
* Returns dual wielding role of the given class of weapon `weaponClass`.
* See `DualWieldingRole` enum for more details.
*
* @param weaponClass Weapon class to check the role for.
* @return Dual wielding role of the weapon of given class `weaponClass`.
* `DWR_None` in case given `weaponClass` is `none`.
*/
public function DualWieldingRole GetDualWieldingRole(
class<KFWeapon> weaponClass)
{
local int i;
local class<KFWeaponPickup> pickupClass;
if (weaponClass == none) return DWR_None;
pickupClass = class<KFWeaponPickup>(weaponClass.default.pickupClass);
if (pickupClass == none) return DWR_None;
for (i = 0; i < dualiesClasses.length; i += 1)
{
if (dualiesClasses[i].single == pickupClass) {
return DWR_Single;
}
if (dualiesClasses[i].dual == pickupClass) {
return DWR_Dual;
}
}
return DWR_None;
}
/**
* For "dual" weapons (`DWR_Dual`), corresponding of two "single" version
* returns class of corresponding single version, for any other
* (including single weapons themselves) returns `none`.
*
* @param weaponClass Weapon class for which to find matching single class.
* @return Single class that corresponds to the given `weaponClass`, if it is
* classified as `DWR_Dual`. `none` for every other class.
*/
public function class<KFWeapon> GetSingleClass(class<KFWeapon> weapon)
{
local int i;
local class<KFWeaponPickup> pickupClass;
local class<KFWeaponPickup> singlePickupClass;
if (weapon == none) return none;
pickupClass = class<KFWeaponPickup>(weapon.default.pickupClass);
if (pickupClass == none) return none;
for (i = 0; i < dualiesClasses.length; i += 1)
{
if (dualiesClasses[i].dual == pickupClass)
{
singlePickupClass = dualiesClasses[i].single;
if (singlePickupClass != none) {
return class<KFWeapon>(singlePickupClass.default.inventoryType);
}
}
}
return none;
}
/**
* For "single" weapons (`DWR_Single`) that can have a "dual" version returns
* class of corresponding dual version, for any other (including dual weapons
* themselves) returns `none`.
*
* @param weaponClass Weapon class for which to find matching dual class.
* @return Dual class that corresponds to the given `weaponClass`, if it is
* classified as `DWR_Single`. `none` for every other class.
*/
public function class<KFWeapon> GetDualClass(class<KFWeapon> weaponClass)
{
local int i;
local class<KFWeaponPickup> pickupClass;
local class<KFWeaponPickup> dualPickupClass;
if (weaponClass == none) return none;
pickupClass = class<KFWeaponPickup>(weaponClass.default.pickupClass);
if (pickupClass == none) return none;
for (i = 0; i < dualiesClasses.length; i += 1)
{
if (dualiesClasses[i].single == pickupClass)
{
dualPickupClass = dualiesClasses[i].dual;
if (dualPickupClass != none) {
return class<KFWeapon>(dualPickupClass.default.inventoryType);
}
}
}
return none;
}
/**
* 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 Get(
class<Inventory> 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 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<Inventory> GetAll(
class<Inventory> inventoryClass,
Inventory inventoryChain,
optional bool acceptChildClass)
{
local bool shouldAdd;
local array<Inventory> 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;
}
/**
* Checks whether `inventory` is contained in the inventory given by
* `inventoryChain`.
*
* @param inventory Item we are searching for.
* @param inventoryChain Inventory chain in which we should search for
* the given item.
* @return `true` if `inventoryChain` contains `inventory` and
* `false` otherwise.
*/
public final function bool Contains(
Inventory inventory,
Inventory inventoryChain)
{
while (inventoryChain != none)
{
if (inventoryChain == inventory) {
return true;
}
inventoryChain = inventoryChain.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:
* 1. Vanilla game rules are such that player can only have two weapons
* in the inventory if they have different roots;
* 2. Root is easy to find.
*
* @param weaponClass Weapon class for which we must find root class.
* @return Root class for the provided `weaponClass` class.
* If `weaponClass` is `none`, method will also return `none`.
*/
public final function class<KFWeaponPickup> GetRootPickupClass(
class<KFWeapon> weaponClass)
{
local int i;
local class<KFWeaponPickup> root;
if (weaponClass == none) return none;
// Start with a pickup of the given weapons
root = class<KFWeaponPickup>(weaponClass.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 != none && root.default.variantClasses.length > 0) {
root = class<KFWeaponPickup>(root.default.variantClasses[0]);
}
return root;
}
/**
* Convenience method for finding a first inventory entry with the same root as
* class `inventoryClass` in the given inventory chain `inventoryChain`.
* For information of what a "root" is, see `GetRootPickupClass()`.
*
* 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.
* @return First inventory from `inventoryChain` that has the same root as
* given `inventoryClass` class.
*/
public function KFWeapon GetByRoot(
class<KFWeapon> inventoryClass,
Inventory inventoryChain)
{
local class<KFWeapon> nextWeaponClass;
local class<KFWeaponPickup> itemRoot, nextRoot;
itemRoot = GetRootPickupClass(inventoryClass);
if (itemRoot == none) {
return none;
}
while (inventoryChain != none)
{
nextWeaponClass = class<KFWeapon>(inventoryChain.class);
nextRoot = GetRootPickupClass(nextWeaponClass);
if (itemRoot == nextRoot) {
return KFWeapon(inventoryChain);
}
inventoryChain = inventoryChain.inventory;
}
return none;
}
/**
* Convenience method for finding all inventory entries with the same root as
* class `inventoryClass` in the given inventory chain `inventoryChain`.
* For information of what a "root" is, see `GetRootPickupClass()`.
*
* 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.
* @return Array of inventory items from `inventoryChain` that have the same
* root as given `inventoryClass` class.
*/
public function array<KFWeapon> GetAllByRoot(
class<KFWeapon> inventoryClass,
Inventory inventoryChain)
{
local array<KFWeapon> result;
local KFWeapon nextWeapon;
local class<KFWeapon> nextWeaponClass;
local class<KFWeaponPickup> itemRoot, nextRoot;
itemRoot = GetRootPickupClass(inventoryClass);
if (itemRoot == none) {
return result;
}
while (inventoryChain != none)
{
nextWeaponClass = class<KFWeapon>(inventoryChain.class);
nextRoot = GetRootPickupClass(nextWeaponClass);
if (itemRoot == nextRoot) {
nextWeapon = KFWeapon(inventoryChain);
}
if (nextWeapon != none)
{
result[result.length] = nextWeapon;
nextWeapon = none;
}
inventoryChain = inventoryChain.inventory;
}
return result;
}
/**
* Returns ammunition class for the given `weapon` weapon, that it uses for
* fire mode numbered `modeNumber`.
*
* @param weapon Weapon for which ammunition class should be found.
* @param modeNumber Fire mode for which ammunition class should be found.
* @return Class of ammunition used for `weapon`'s fire mode,
* numbered `modeNumber`.
* `none` if `weapon` is `none`, fire mode does not exist or
* it is not associated with inventory ammo class.
*/
public function class<Ammunition> GetAmmoClass(Weapon weapon, int modeNumber)
{
local WeaponFire relevantWeaponFire;
if (weapon == none) {
return none;
}
// Just use majestic rjp's hack method `GetFireMode()` to get ammo class
// through a weapon fire
relevantWeaponFire = weapon.GetFireMode(modeNumber);
if (relevantWeaponFire != none) {
return relevantWeaponFire.ammoClass;
}
return none;
}
/**
* Removes all ammo from the given `weapon`. Assumes weapons has no more than
* two fire modes.
*
* In case given weapon is a child class of `KFWeapon`, also clears its
* magazine counter.
*
* @param weapon Weapon to remove all ammo from. If `none`,
* method does nothing.
*/
public final function ClearAmmo(Weapon weapon)
{
local InventoryService service;
service = InventoryService(class'InventoryService'.static.Require());
if (service != none) {
service.ClearAmmo(weapon);
}
}
/**
* Creates and adds a weapons of the given class to the `pawn` with specified
* amount of ammunition.
*
* @param pawn `Pawn` to which we should add new weapon.
* If `none` - method does nothing.
* @param weaponClassToAdd Class of the weapon we need to add.
* If `none` - method does nothing.
* @param totalAmmoPrimary Ammo to add to the primary fire.
* @param totalAmmoSecondary Ammo to add to the secondary fire.
* @param magazineAmmo Ammo to add to the new weapon's magazine count.
* Only relevant if `weaponClassToAdd` is a child class of `KFWeapon` and
* otherwise ignored.
* @param clearStarterAmmo Newly created weapons usually come with some
* default amount of ammo. Setting this flag to `true` will remove it
* before adding `totalAmmoPrimary`, `totalAmmoSecondary` and
* `magazineAmmo`.
* @return Instance of the newly created weapon. `none` in case of failure or
* if created weapon was destroyed in the process of adding it to
* the `pawn` (can happen as a result of interaction with preexisting
* weapons - e.g. pistol can merge with another one of the same type and
* produce a new weapon).
*/
public function Weapon AddWeaponWithAmmo(
Pawn pawn,
class<Weapon> weaponClassToAdd,
optional int totalAmmoPrimary,
optional int totalAmmoSecondary,
optional int magazineAmmo,
optional bool clearStarterAmmo)
{
local InventoryService service;
service = InventoryService(class'InventoryService'.static.Require());
if (service == none) {
return none;
}
return service.AddWeaponWithAmmo( pawn, weaponClassToAdd,
totalAmmoPrimary, totalAmmoSecondary,
magazineAmmo, clearStarterAmmo);
}
/**
* Auxiliary method for "merging" weapons. Basically acts as
* a `AddWeaponWithAmmo()` - creates and adds a weapons of the given class to
* the `pawn`. But instead of taking numeric parameters to specify starter
* ammunition, copies ammunition counts (adding them together) from two weapons
* (`weaponToMerge1` and `weaponToMerge2`) specified for "merging"
*
* @param pawn `Pawn` to which we should add new merged weapon.
* If `none` - method does nothing.
* @param mergedClass Class of the weapon we need to add as a result
* of "merging". If `none` - method does nothing.
* @param weaponToMerge1 First weapon from which to copy ammunition counts.
* In case it is of a child class of `KFWeapon`, also copies magazine size.
* If `none` - assumes all ammo counts to be zero.
* @param weaponToMerge2 Second weapon from which to copy ammunition counts.
* Completely interchangeable with `weaponToMerge1`.
* @param clearStarterAmmo Newly created weapons usually come with some
* default amount of ammo. Setting this flag to `true` will remove it
* before adding ammunition counts from `weaponToMerge1` and
* `weaponToMerge2`.
* @return Instance of the newly created weapon. `none` in case of failure or
* if created weapon was destroyed in the process of adding it to
* the `pawn` (can happen as a result of interaction with preexisting
* weapons - e.g. pistol can merge with another one of the same type and
* produce a new weapon).
*/
public function Weapon MergeWeapons(
Pawn ownerPawn,
class<Weapon> mergedClass,
optional Weapon weaponToMerge1,
optional Weapon weaponToMerge2,
optional bool clearStarterAmmo)
{
local InventoryService service;
service = InventoryService(class'InventoryService'.static.Require());
if (service == none) {
return none;
}
return service.MergeWeapons(ownerPawn, mergedClass,
weaponToMerge1, weaponToMerge2);
}
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')
}

128
sources/Unreal/InventoryAPI/InventoryService.uc

@ -0,0 +1,128 @@
/**
* Service that simply does some of the work of `InventoryService`, since
* working with `Actor`s that can get destroyed in the process is much safer
* inside another `Actor`.
* For description of all methods see `InventoryAPI`.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class InventoryService extends Service;
public function Weapon AddWeaponWithAmmo(
Pawn pawn,
class<Weapon> weaponClassToAdd,
optional int totalAmmoPrimary,
optional int totalAmmoSecondary,
optional int magazineAmmo,
optional bool clearStarterAmmo)
{
local Weapon newWeapon;
local KFWeapon newKFWeapon;
if (pawn == none) return none;
newWeapon = Weapon(_.memory.Allocate(weaponClassToAdd));
if (newWeapon == none) return none;
// It is possible that `newWeapon` can get destroyed somewhere here,
// so add two more checks
_.unreal.GetKFGameType().WeaponSpawned(newWeapon);
if (newWeapon == none) return none;
newWeapon.GiveTo(pawn);
if (newWeapon == none) return none;
// Update ammo & magazine (if applicable)
if (clearStarterAmmo) {
ClearAmmo(newWeapon);
}
newKFWeapon = KFWeapon(newWeapon);
if (newKFWeapon != none)
{
if (clearStarterAmmo) {
newKFWeapon.magAmmoRemaining = 0;
}
newKFWeapon.magAmmoRemaining += magazineAmmo;
}
if (totalAmmoPrimary > 0) {
newWeapon.AddAmmo(totalAmmoPrimary, 0);
}
if (totalAmmoSecondary > 0) {
newWeapon.AddAmmo(totalAmmoSecondary, 1);
}
return newWeapon;
}
public function Weapon MergeWeapons(
Pawn pawn,
class<Weapon> mergedClass,
optional Weapon weaponToMerge1,
optional Weapon weaponToMerge2,
optional bool clearStarterAmmo)
{
local int totalAmmoPrimary, totalAmmoSecondary, magazineAmmo;
local KFWeapon kfWeapon;
if (pawn == none) {
return none;
}
if (weaponToMerge1 != none)
{
kfWeapon = KFWeapon(weaponToMerge1);
if (kfWeapon != none) {
magazineAmmo += kfWeapon.magAmmoRemaining;
}
totalAmmoPrimary += weaponToMerge1.AmmoAmount(0);
totalAmmoSecondary += weaponToMerge1.AmmoAmount(1);
weaponToMerge1.Destroyed();
if (weaponToMerge1 != none) {
weaponToMerge1.Destroy();
}
}
if (weaponToMerge2 != none)
{
kfWeapon = KFWeapon(weaponToMerge2);
if (kfWeapon != none) {
magazineAmmo += kfWeapon.magAmmoRemaining;
}
totalAmmoPrimary += weaponToMerge2.AmmoAmount(0);
totalAmmoSecondary += weaponToMerge2.AmmoAmount(1);
weaponToMerge2.Destroyed();
if (weaponToMerge2 != none) {
weaponToMerge2.Destroy();
}
}
return AddWeaponWithAmmo( pawn, mergedClass, totalAmmoPrimary,
totalAmmoSecondary, magazineAmmo,
clearStarterAmmo);
}
public final function ClearAmmo(Weapon weapon)
{
local float auxiliary, currentAmmoPrimary, currentAmmoSecondary;
local KFWeapon kfWeapon;
if (weapon == none) {
return;
}
weapon.GetAmmoCount(auxiliary, currentAmmoPrimary);
//weapon.GetSecondaryAmmoCount(auxiliary, currentAmmoSecondary);
weapon.AddAmmo(-currentAmmoPrimary, 0);
weapon.AddAmmo(-currentAmmoSecondary, 1);
kfWeapon = KFWeapon(weapon);
if (kfWeapon != none) {
kfWeapon.magAmmoRemaining = 0;
}
}
defaultproperties
{
}

30
sources/Unreal/Tests/TEST_UnrealAPI.uc

@ -156,35 +156,35 @@ protected static function SubTest_InventoryChainFetchingSingle(Inventory chain)
{ {
Issue("Does not find correct first entry inside the inventory chain."); Issue("Does not find correct first entry inside the inventory chain.");
TEST_ExpectTrue( TEST_ExpectTrue(
__().unreal.GetInventoryFrom(class'MockInventoryA', chain) __().unreal.inventory.Get(class'MockInventoryA', chain)
== chain.inventory.inventory); == chain.inventory.inventory);
TEST_ExpectTrue( TEST_ExpectTrue(
__().unreal.GetInventoryFrom(class'MockInventoryB', chain) __().unreal.inventory.Get(class'MockInventoryB', chain)
== chain.inventory); == chain.inventory);
TEST_ExpectTrue( TEST_ExpectTrue(
__().unreal.GetInventoryFrom(class'MockInventoryAChild', chain) __().unreal.inventory.Get(class'MockInventoryAChild', chain)
== chain); == chain);
Issue("Incorrectly finds missing inventory entries."); Issue("Incorrectly finds missing inventory entries.");
TEST_ExpectNone(__().unreal.GetInventoryFrom(none, chain)); TEST_ExpectNone(__().unreal.inventory.Get(none, chain));
TEST_ExpectNone(__().unreal.GetInventoryFrom(class'Winchester', chain)); TEST_ExpectNone(__().unreal.inventory.Get(class'Winchester', chain));
Issue("Does not find correct first entry inside the inventory chain when" @ Issue("Does not find correct first entry inside the inventory chain when" @
"allowing for child classes."); "allowing for child classes.");
TEST_ExpectTrue( TEST_ExpectTrue(
__().unreal.GetInventoryFrom(class'MockInventoryA', chain, true) __().unreal.inventory.Get(class'MockInventoryA', chain, true)
== chain); == chain);
TEST_ExpectTrue( TEST_ExpectTrue(
__().unreal.GetInventoryFrom(class'MockInventoryB', chain, true) __().unreal.inventory.Get(class'MockInventoryB', chain, true)
== chain.inventory); == chain.inventory);
TEST_ExpectTrue( TEST_ExpectTrue(
__().unreal.GetInventoryFrom(class'MockInventoryAChild', chain, true) __().unreal.inventory.Get(class'MockInventoryAChild', chain, true)
== chain); == chain);
Issue("Incorrectly finds missing inventory entries when allowing for" @ Issue("Incorrectly finds missing inventory entries when allowing for" @
"child classes."); "child classes.");
TEST_ExpectNone(__().unreal.GetInventoryFrom(none, chain, true)); TEST_ExpectNone(__().unreal.inventory.Get(none, chain, true));
TEST_ExpectNone(__().unreal.GetInventoryFrom( class'Winchester', chain, TEST_ExpectNone(__().unreal.inventory.Get( class'Winchester', chain,
true)); true));
} }
@ -192,7 +192,7 @@ protected static function SubTest_InventoryChainFetchingMany(Inventory chain)
{ {
local array<Inventory> result; local array<Inventory> result;
Issue("Does not find correct entries inside the inventory chain."); Issue("Does not find correct entries inside the inventory chain.");
result = __().unreal.GetAllInventoryFrom(class'MockInventoryB', chain); result = __().unreal.inventory.GetAll(class'MockInventoryB', chain);
TEST_ExpectTrue(result.length == 2); TEST_ExpectTrue(result.length == 2);
TEST_ExpectTrue(result[0] == chain.inventory); TEST_ExpectTrue(result[0] == chain.inventory);
TEST_ExpectTrue(result[1] == chain.inventory.inventory.inventory.inventory); TEST_ExpectTrue(result[1] == chain.inventory.inventory.inventory.inventory);
@ -200,12 +200,12 @@ protected static function SubTest_InventoryChainFetchingMany(Inventory chain)
Issue("Does not find correct entries inside the inventory chain when" @ Issue("Does not find correct entries inside the inventory chain when" @
"allowing for child classes."); "allowing for child classes.");
result = result =
__().unreal.GetAllInventoryFrom(class'MockInventoryB', chain, true); __().unreal.inventory.GetAll(class'MockInventoryB', chain, true);
TEST_ExpectTrue(result.length == 2); TEST_ExpectTrue(result.length == 2);
TEST_ExpectTrue(result[0] == chain.inventory); TEST_ExpectTrue(result[0] == chain.inventory);
TEST_ExpectTrue(result[1] == chain.inventory.inventory.inventory.inventory); TEST_ExpectTrue(result[1] == chain.inventory.inventory.inventory.inventory);
result = result =
__().unreal.GetAllInventoryFrom(class'MockInventoryA', chain, true); __().unreal.inventory.GetAll(class'MockInventoryA', chain, true);
TEST_ExpectTrue(result.length == 5); TEST_ExpectTrue(result.length == 5);
TEST_ExpectTrue(result[0] == chain); TEST_ExpectTrue(result[0] == chain);
TEST_ExpectTrue(result[1] == chain.inventory.inventory); TEST_ExpectTrue(result[1] == chain.inventory.inventory);
@ -218,9 +218,9 @@ protected static function SubTest_InventoryChainFetchingMany(Inventory chain)
== chain.inventory.inventory.inventory.inventory.inventory.inventory); == chain.inventory.inventory.inventory.inventory.inventory.inventory);
Issue("Does not return empty array for non-existing inventory class."); Issue("Does not return empty array for non-existing inventory class.");
result = __().unreal.GetAllInventoryFrom(class'Winchester', chain); result = __().unreal.inventory.GetAll(class'Winchester', chain);
TEST_ExpectTrue(result.length == 0); TEST_ExpectTrue(result.length == 0);
result = __().unreal.GetAllInventoryFrom(class'Winchester', chain, true); result = __().unreal.inventory.GetAll(class'Winchester', chain, true);
TEST_ExpectTrue(result.length == 0); TEST_ExpectTrue(result.length == 0);
} }

92
sources/Unreal/UnrealAPI.uc

@ -1,7 +1,7 @@
/** /**
* Low-level API that provides set of utility methods for working with * Low-level API that provides set of utility methods for working with
* unreal script classes. * unreal script classes.
* Copyright 2021 Anton Tarasenko * Copyright 2021 - 2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -23,6 +23,7 @@ class UnrealAPI extends AcediaObject;
var public MutatorAPI mutator; var public MutatorAPI mutator;
var public GameRulesAPI gameRules; var public GameRulesAPI gameRules;
var public BroadcastAPI broadcasts; var public BroadcastAPI broadcasts;
var public InventoryAPI inventory;
var private LoggerAPI.Definition fatalNoStalker; var private LoggerAPI.Definition fatalNoStalker;
@ -31,6 +32,7 @@ protected function Constructor()
mutator = MutatorAPI(_.memory.Allocate(class'MutatorAPI')); mutator = MutatorAPI(_.memory.Allocate(class'MutatorAPI'));
gameRules = GameRulesAPI(_.memory.Allocate(class'GameRulesAPI')); gameRules = GameRulesAPI(_.memory.Allocate(class'GameRulesAPI'));
broadcasts = BroadcastAPI(_.memory.Allocate(class'BroadcastAPI')); broadcasts = BroadcastAPI(_.memory.Allocate(class'BroadcastAPI'));
inventory = InventoryAPI(_.memory.Allocate(class'InventoryAPI'));
} }
public function DropAPI() public function DropAPI()
@ -38,6 +40,7 @@ public function DropAPI()
mutator = none; mutator = none;
gameRules = none; gameRules = none;
broadcasts = none; broadcasts = none;
inventory = none;
} }
/** /**
@ -199,93 +202,6 @@ public final function PlayerController GetLocalPlayer()
.GetLocalPlayerController(); .GetLocalPlayerController();
} }
/**
* 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<Inventory> 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<Inventory> GetAllInventoryFrom(
class<Inventory> inventoryClass,
Inventory inventoryChain,
optional bool acceptChildClass)
{
local bool shouldAdd;
local array<Inventory> 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;
}
/** /**
* Creates reference object to store a `Actor` value. * Creates reference object to store a `Actor` value.
* *

Loading…
Cancel
Save