diff --git a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc index bb6f078..7524648 100644 --- a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc +++ b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc @@ -2,7 +2,7 @@ * Player's inventory implementation for classic Killing Floor that changes * as little as possible and only on request from another mod, otherwise not * altering gameplay at all. - * Copyright 2021 Anton Tarasenko + * Copyright 2021 - 2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -57,11 +57,14 @@ private function Pawn GetOwnerPawn() return myController.pawn; } +// TODO: needs more testing public function bool Add(EItem newItem, optional bool forceAddition) { - local Pawn pawn; - local EKFWeapon kfWeaponItem; - local KFWeapon kfWeapon; + local Pawn pawn; + local EKFWeapon kfWeaponItem; + local KFWeapon kfWeapon; + local class dualClass; + local Inventory collidingItem; if (!CanAdd(newItem, forceAddition)) return false; kfWeaponItem = EKFWeapon(newItem); if (kfWeaponItem == none) return false; @@ -70,6 +73,24 @@ public function bool Add(EItem newItem, optional bool forceAddition) kfWeapon = kfWeaponItem.GetNativeInstance(); if (kfWeapon == none) return false; + dualClass = GetDualClass(kfWeapon.class); + if (dualClass != none) + { + collidingItem = FindSkinnedInventory(pawn, kfWeapon.class); + if (collidingItem != none) + { + collidingItem.Destroyed(); + collidingItem.Destroy(); + } + kfWeapon.Destroy(); + kfWeapon = KFWeapon(_.memory.Allocate(dualClass)); + if (kfWeapon != none) { + _.unreal.GetKFGameType().WeaponSpawned(kfWeapon); + } + else { + return false; + } + } kfWeapon.GiveTo(pawn); return true; } @@ -78,18 +99,42 @@ public function bool AddTemplate( Text newItemTemplate, optional bool forceAddition) { - local Pawn pawn; - local KFWeapon newWeapon; + local Pawn pawn; + local KFWeapon newWeapon; + local class newWeaponClass; + local class dualClass; + local KFWeapon collidingWeapon; + local int totalAmmo, magazineAmmo; if (newItemTemplate == none) return false; if (!CanAddTemplate(newItemTemplate, forceAddition)) return false; pawn = GetOwnerPawn(); if (pawn == none) return false; - newWeapon = KFWeapon(_.memory.AllocateByReference(newItemTemplate)); + newWeaponClass = class(_.memory.LoadClass(newItemTemplate)); + if (newWeaponClass != none) { + dualClass = GetDualClass(newWeaponClass); + } + if (dualClass != none) + { + collidingWeapon = FindSkinnedInventory(pawn, newWeaponClass); + if (collidingWeapon != none) + { + totalAmmo = collidingWeapon.AmmoAmount(0); + magazineAmmo = collidingWeapon.magAmmoRemaining; + collidingWeapon.Destroyed(); + collidingWeapon.Destroy(); + newWeaponClass = dualClass; + } + } + newWeapon = KFWeapon(_.memory.Allocate(newWeaponClass)); if (newWeapon != none) { _.unreal.GetKFGameType().WeaponSpawned(newWeapon); newWeapon.GiveTo(pawn); + if (totalAmmo > 0) { + newWeapon.AddAmmo(totalAmmo, 0); + } + newWeapon.magAmmoRemaining += magazineAmmo; return true; } return false; @@ -117,11 +162,12 @@ public function bool CanAddTemplate( return CanAddWeaponClass(kfWeaponClass, forceAddition); } -public function bool CanAddWeaponClass( +private function bool CanAddWeaponClass( class kfWeaponClass, optional bool forceAddition) { - local KFPawn kfPawn; + local KFPawn kfPawn; + local Inventory collidingItem; if (kfWeaponClass == none) return false; kfPawn = KFPawn(GetOwnerPawn()); if (kfPawn == none) return false; @@ -129,11 +175,12 @@ public function bool CanAddWeaponClass( if (!forceAddition && !kfPawn.CanCarry(kfWeaponClass.default.weight)) { return false; } - if (kfPawn.FindInventoryType(kfWeaponClass) != none) { - return false; - } - if (!forceAddition && HasSameTypeWeapons(kfWeaponClass, kfPawn)) { - return false; + if (!forceAddition && HasSameTypeWeapons(kfWeaponClass, kfPawn)) + { + if (GetDualClass(kfWeaponClass) != none) { + collidingItem = FindSkinnedInventory(kfPawn, kfWeaponClass); + } + return (collidingItem != none); } return true; } @@ -157,6 +204,69 @@ private function bool HasSameTypeWeapons( return false; } +// For "single" weapons that can have a "dual" version returns class of +// corresponding dual version, for any other +// (including dual weapons themselves) returns `none`. +private final function class GetDualClass(class weapon) +{ + local int i; + local class pickupClass; + local class dualPickupClass; + if (weapon == none) return none; + pickupClass = class(weapon.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(dualPickupClass.default.inventoryType); + } + } + } + return none; +} + +// Returns inventory type of class `desiredClass` if it exists in +// `pawn`'s inventory. +// Unlike `Pawn`'s `FindInventoryType()` method, this one will find +// inventory type in case it corresponds to the different (weapon's) skin by +// comparing first item of `KFWeaponPickup`'s `variantClasses`. +private function KFWeapon FindSkinnedInventory( + Pawn pawn, + class desiredClass) +{ + local Inventory nextInv; + local class rootVariant; + local class nextPickupClass, desiredPickupClass; + if (desiredClass == none) return none; + if (pawn == none) return none; + + desiredPickupClass = class(desiredClass.default.pickupClass); + if ( desiredPickupClass != none + && desiredPickupClass.default.variantClasses.length > 0) + { + rootVariant = desiredPickupClass.default.variantClasses[0]; + } + for (nextInv = pawn.inventory; nextInv != none; nextInv = nextInv.inventory) + { + if (nextInv.class == desiredClass) { + return KFWeapon(nextInv); + } + // Variant check + if (rootVariant == none) continue; + nextPickupClass = class(nextInv.pickupClass); + if (nextPickupClass == none) continue; + if (nextPickupClass.default.variantClasses.length <= 0) continue; + if (rootVariant == nextPickupClass.default.variantClasses[0]) { + return KFWeapon(nextInv); + } + } + return none; +} + // 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; @@ -222,9 +332,9 @@ public function bool Remove( if (nextInventory.inventory == kfWeapon) { nextInventory.inventory = kfWeapon.inventory; - kfWeapon.inventory = none; - nextInventory.netUpdateTime = passedTime; - kfWeapon.netUpdateTime = passedTime; + kfWeapon.inventory = none; + nextInventory.netUpdateTime = passedTime; + kfWeapon.netUpdateTime = passedTime; kfWeapon.Destroy(); removedItem = true; } @@ -271,9 +381,9 @@ public function bool RemoveTemplate( && nextInventory.inventory.class == kfWeaponClass) { nextInventory.inventory = nextKFWeapon.inventory; - nextKFWeapon.inventory = none; - nextInventory.netUpdateTime = passedTime; - nextKFWeapon.netUpdateTime = passedTime; + nextKFWeapon.inventory = none; + nextInventory.netUpdateTime = passedTime; + nextKFWeapon.netUpdateTime = passedTime; nextKFWeapon.Destroy(); removedItem = true; if (!removeAll) { diff --git a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFItemTemplateInfo.uc b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFItemTemplateInfo.uc index 6a24105..a0898da 100644 --- a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFItemTemplateInfo.uc +++ b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFItemTemplateInfo.uc @@ -2,7 +2,7 @@ * Implementation of `EKFItemTemplateInfo` for classic Killing Floor items that * changes as little as possible and only on request from another mod, * otherwise not altering gameplay at all. - * Copyright 2021 Anton Tarasenko + * Copyright 2021 - 2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -30,8 +30,9 @@ var private class classReference; * @param newClass Native inventory class that new `EKFItemTemplateInfo` * will represent. * @return New `EKFItemTemplateInfo` that represents given `newClass`. + * `none` if passed argument `newInventoryClass` is `none`. */ -public final static function EKFItemTemplateInfo Wrap( +public final static /*unreal*/ function EKFItemTemplateInfo Wrap( class newInventoryClass) { local EKFItemTemplateInfo newTemplateReference; diff --git a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFWeapon.uc b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFWeapon.uc index b5d3298..e030b98 100644 --- a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFWeapon.uc +++ b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFWeapon.uc @@ -2,7 +2,7 @@ * Implementation of `EItem` for classic Killing Floor weapons that changes * as little as possible and only on request from another mod, otherwise not * altering gameplay at all. - * Copyright 2021 Anton Tarasenko + * Copyright 2021 - 2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -37,7 +37,7 @@ protected function Finalizer() * represent. * @return New `EKFWeapon` that represents given `weaponInstance`. */ -public final static function EKFWeapon Make(KFWeapon weaponInstance) +public final static /*unreal*/ function EKFWeapon Wrap(KFWeapon weaponInstance) { local EKFWeapon newReference; newReference = EKFWeapon(__().memory.Allocate(class'EKFWeapon')); @@ -50,7 +50,7 @@ public final static function EKFWeapon Make(KFWeapon weaponInstance) * * @return `KFWeapon` instance represented by the caller `EKFWeapon`. */ -public final function KFWeapon GetNativeInstance() +public final /*unreal*/ function KFWeapon GetNativeInstance() { if (weaponReference != none) { return KFWeapon(weaponReference.Get());