You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
223 lines
8.3 KiB
223 lines
8.3 KiB
/** |
|
* 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 <https://www.gnu.org/licenses/>. |
|
*/ |
|
class FixInventoryAbuse extends Feature |
|
config(AcediaFixes); |
|
|
|
// 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; |
|
|
|
var private Timer checkTimer; |
|
|
|
struct DualiesPair |
|
{ |
|
var class<KFWeaponPickup> single; |
|
var class<KFWeaponPickup> 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<DualiesPair> dualiesClasses; |
|
|
|
protected function OnEnabled() |
|
{ |
|
local float actualInterval; |
|
actualInterval = checkInterval; |
|
if (actualInterval <= 0) { |
|
actualInterval = 0.25; |
|
} |
|
checkTimer = _.time.StartTimer(actualInterval, true); |
|
checkTimer.OnElapsed(self).connect = Timer; |
|
} |
|
|
|
protected function OnDisabled() |
|
{ |
|
_.memory.Free(checkTimer); |
|
} |
|
|
|
// 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<KFWeaponPickup> GetRootPickupClass(KFWeapon weapon) |
|
{ |
|
local int i; |
|
local class<KFWeaponPickup> root; |
|
if (weapon == none) return none; |
|
// Start with a pickup of the given weapons |
|
root = class<KFWeaponPickup>(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<KFWeaponPickup>(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<KFWeaponPickup> rootClass; |
|
local array< class<Pickup> > 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; |
|
} |
|
|
|
private final function Vector DropWeapon(KFWeapon weaponToDrop) |
|
{ |
|
local Vector x, y, z; |
|
local Vector weaponVelocity; |
|
local Vector dropLocation; |
|
local KFHumanPawn playerPawn; |
|
if (weaponToDrop == none) return Vect(0, 0, 0); |
|
playerPawn = KFHumanPawn(weaponToDrop.instigator); |
|
if (playerPawn == none) return Vect(0, 0, 0); |
|
|
|
// Calculations from `PlayerController.ServerThrowWeapon()` |
|
weaponVelocity = Vector(playerPawn.GetViewRotation()); |
|
weaponVelocity *= (playerPawn.velocity dot weaponVelocity) + 150; |
|
weaponVelocity += Vect(0, 0, 100); |
|
// Calculations from `Pawn.TossWeapon()` |
|
GetAxes(playerPawn.rotation, x, y, z); |
|
dropLocation = playerPawn.location + 0.8 * playerPawn.collisionRadius * x - |
|
0.5 * playerPawn.collisionRadius * y; |
|
// Do the drop |
|
weaponToDrop.velocity = weaponVelocity; |
|
weaponToDrop.DropFrom(dropLocation); |
|
} |
|
|
|
// Kill the gun devil! |
|
private final function DropEverything(KFHumanPawn playerPawn) |
|
{ |
|
local int i; |
|
local Inventory inv; |
|
local KFWeapon nextWeapon; |
|
local array<KFWeapon> 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) { |
|
DropWeapon(weaponList[i]); |
|
} |
|
} |
|
|
|
private function Timer(Timer source) |
|
{ |
|
local int i; |
|
local KFHumanPawn nextPawn; |
|
local ConnectionService service; |
|
local array<ConnectionService.Connection> 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)) { |
|
DropEverything(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') |
|
} |