diff --git a/sources/Features/FixInventoryAbuse/FixInventoryAbuse.uc b/sources/Features/FixInventoryAbuse/FixInventoryAbuse.uc new file mode 100644 index 0000000..819b6b8 --- /dev/null +++ b/sources/Features/FixInventoryAbuse/FixInventoryAbuse.uc @@ -0,0 +1,202 @@ +/** + * This feature addressed two inventory issues: + * 1. Players carrying amount of weapons that shouldn't be allowed by the + * weight limit. + * 2. Players carrying two variants of the same gun. + * For example carrying both M32 and camo M32. + * Single and dual version of the same weapon are also considered + * the same gun, so you can't carry both MK23 and dual MK23 or + * dual handcannons and golden handcannon. + * + * It fixes them by doing repeated checks to find violations of those rules + * and destroys all droppable weapons of people that use this exploit. + * Copyright 2020 Anton Tarasenko + *------------------------------------------------------------------------------ + * This file is part of Acedia. + * + * Acedia is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, or + * (at your option) any later version. + * + * Acedia is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Acedia. If not, see . + */ +class FixInventoryAbuse extends Feature; + +// How often (in seconds) should we do our inventory validations? +// We shouldn't really worry about performance, but there's also no need to +// do this check too often. +var private config const float checkInterval; + +struct DualiesPair +{ + var class single; + var class dual; +}; +// For this fix to properly work, this array must contain an entry for +// every dual weapon in the game (like pistols, with single and dual versions). +// It's made configurable in case of custom dual weapons. +var private config const array dualiesClasses; + +public function OnEnabled() +{ + local float actualInterval; + actualInterval = checkInterval; + if (actualInterval <= 0) + { + actualInterval = 0.25; + } + SetTimer(actualInterval, true); +} + +public function OnDisabled() +{ + SetTimer(0.0f, false); +} + +// Did player with this controller contribute to the latest dosh generation? +private final function bool IsWeightLimitViolated(KFHumanPawn playerPawn) +{ + if (playerPawn == none) return false; + return (playerPawn.currentWeight > playerPawn.maxCarryWeight); +} + +// Returns a root pickup class. +// For non-dual weapons, root class is defined as either: +// 1. the first variant (reskin), if there are variants for that weapon; +// 2. and as the class itself, if there are no variants. +// For dual weapons (all dual pistols) root class is defined as +// a root of their single version. +// This definition is useful because: +// ~ Vanilla game rules are such that player can only have two weapons +// in the inventory if they have different roots; +// ~ Root is easy to find. +private final function class GetRootPickupClass(KFWeapon weapon) +{ + local int i; + local class root; + if (weapon == none) return none; + // Start with a pickup of the given weapons + root = class(weapon.default.pickupClass); + if (root == none) return none; + + // In case it's a dual version - find corresponding single pickup class + // (it's root would be the same). + for (i = 0; i < dualiesClasses.length; i += 1) + { + if (dualiesClasses[i].dual == root) + { + root = dualiesClasses[i].single; + break; + } + } + // Take either first variant class or the class itself, - + // it's going to be root by definition. + if (root.default.variantClasses.length > 0) + { + root = class(root.default.variantClasses[0]); + } + return root; +} + +// Returns 'true' if passed pawn has two weapons that are just variants of +// each other (they have the same root, see 'GetRootPickupClass'). +private final function bool HasDuplicateGuns(KFHumanPawn playerPawn) +{ + local int i, j; + local Inventory inv; + local KFWeapon nextWeapon; + local class rootClass; + local array< class > rootList; + if (playerPawn == none) return false; + + // First find a root for every weapon in the pawn's inventory. + for (inv = playerPawn.inventory; inv != none; inv = inv.inventory) + { + nextWeapon = KFWeapon(inv); + if (nextWeapon == none) continue; + if (nextWeapon.bKFNeverThrow) continue; + rootClass = GetRootPickupClass(nextWeapon); + if (rootClass != none) + { + rootList[rootList.length] = rootClass; + } + } + // Then just check obtained roots for duplicates. + for (i = 0; i < rootList.length; i += 1) + { + for (j = i + 1; j < rootList.length; j += 1) + { + if (rootList[i] == rootList[j]) + { + return true; + } + } + } + return false; +} + +// Kill the gun devil! +private final function DestroyEverything(KFHumanPawn playerPawn) +{ + local int i; + local Inventory inv; + local KFWeapon nextWeapon; + local array weaponList; + if (playerPawn == none) return; + // Going through the linked list while removing items can be tricky, + // so just find all weapons first. + for (inv = playerPawn.inventory; inv != none; inv = inv.inventory) + { + nextWeapon = KFWeapon(inv); + if (nextWeapon == none) continue; + if (nextWeapon.bKFNeverThrow) continue; + weaponList[weaponList.length] = nextWeapon; + } + // And destroy them later. + for(i = 0; i < weaponList.length; i += 1) + { + weaponList[i].Destroy(); + } +} + +event Timer() +{ + local int i; + local KFHumanPawn nextPawn; + local ConnectionService service; + local array connections; + service = ConnectionService(class'ConnectionService'.static.GetInstance()); + if (service == none) return; + + connections = service.GetActiveConnections(); + for (i = 0; i < connections.length; i += 1) + { + nextPawn = none; + if (connections[i].controllerReference != none) + { + nextPawn = KFHumanPawn(connections[i].controllerReference.pawn); + } + if (IsWeightLimitViolated(nextPawn) || HasDuplicateGuns(nextPawn)) + { + DestroyEverything(nextPawn); + } + } +} + +defaultproperties +{ + checkInterval = 0.25 + dualiesClasses(0)=(single=class'KFMod.SinglePickup',dual=class'KFMod.DualiesPickup') + dualiesClasses(1)=(single=class'KFMod.Magnum44Pickup',dual=class'KFMod.Dual44MagnumPickup') + dualiesClasses(2)=(single=class'KFMod.MK23Pickup',dual=class'KFMod.DualMK23Pickup') + dualiesClasses(3)=(single=class'KFMod.DeaglePickup',dual=class'KFMod.DualDeaglePickup') + dualiesClasses(4)=(single=class'KFMod.GoldenDeaglePickup',dual=class'KFMod.GoldenDualDeaglePickup') + dualiesClasses(5)=(single=class'KFMod.FlareRevolverPickup',dual=class'KFMod.DualFlareRevolverPickup') +} \ No newline at end of file