From 07de2d94f93bf5353dab71f53da87aa6fae1ff89 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Sun, 4 Apr 2021 12:30:38 +0700 Subject: [PATCH] Add fix for trader log spam to `FixLogSpam` --- sources/FixLogSpam/FixLogSpam.uc | 13 +- sources/FixLogSpam/SpamTrader/HelperTrader.uc | 141 ++++++++++++++++++ 2 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 sources/FixLogSpam/SpamTrader/HelperTrader.uc diff --git a/sources/FixLogSpam/FixLogSpam.uc b/sources/FixLogSpam/FixLogSpam.uc index 93fbb38..2773d9f 100644 --- a/sources/FixLogSpam/FixLogSpam.uc +++ b/sources/FixLogSpam/FixLogSpam.uc @@ -33,20 +33,28 @@ class FixLogSpam extends Feature // This is responsible for fixing log spam due to picking up dropped // weapons without set `inventory` variable. -var private const bool fixPickupSpam; -var private HelperPickup helperPickupSpam; +var private config const bool fixPickupSpam; +var private HelperPickup helperPickupSpam; + +var private config const bool fixTraderSpam; +var private HelperTrader helperTraderSpam; protected function OnEnabled() { if (fixPickupSpam) { helperPickupSpam = HelperPickup(_.memory.Allocate(class'HelperPickup')); } + if (fixTraderSpam) { + helperTraderSpam = HelperTrader(_.memory.Allocate(class'HelperTrader')); + } } protected function OnDisabled() { _.memory.Free(helperPickupSpam); helperPickupSpam = none; + _.memory.Free(helperTraderSpam); + helperTraderSpam = none; } public function Tick(float delta) @@ -59,4 +67,5 @@ public function Tick(float delta) defaultproperties { fixPickupSpam = true + fixTraderSpam = true } \ No newline at end of file diff --git a/sources/FixLogSpam/SpamTrader/HelperTrader.uc b/sources/FixLogSpam/SpamTrader/HelperTrader.uc new file mode 100644 index 0000000..2d3467a --- /dev/null +++ b/sources/FixLogSpam/SpamTrader/HelperTrader.uc @@ -0,0 +1,141 @@ +/** + * Helper object for `FixLogSpam` fix, responsible for fixing log spam + * due to missing `ShopVolume`s missing `WeaponLocker` in the `myTrader` + * variable. + * Copyright 2021 Anton Tarasenko + *------------------------------------------------------------------------------ + * This file is part of Acedia. + * + * Acedia is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, or + * (at your option) any later version. + * + * Acedia is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Acedia. If not, see . + */ +class HelperTrader extends AcediaObject + config(AcediaFixes); + +/** + * `WeaponLocker` seems be an actor that was supposed to play a certain + * animation whenever players enter a shop. Each `WeaponLocker` is also + * supposed to find the shop (`ShopVolume`) it's linked to during + * `PreBeginPlay()` event and record itself inside `ShopVolume`'s `myTrader` + * variable. However this sometimes does not happen: either due to locker's + * placement or due to map maker forgetting to put one in at all. + * Since `ShoVolume`s do not attempt to check whether their `myTrader` has + * a valid reference, they generate log spam when it does not. + * Note that while `WeaponLocker` was supposed to do some action when + * players enter the shop, it does not actually do anything, so we do not need + * to care about linking lost lockers to their intended shops, we just need to + * link some locker to shops with empty `myTrader` references. Maps with + * custom animated (or otherwise somehow active) lockers should be themselves + * responsible that their animations are being played correctly. + * This fix deals with issue by either: + * 1. Spawning our own `WeaponLocker` and assigning it to the shops + * with empty `myTrader`; + * 2. Finding any already existing locker on the map and linking that + * to the shops with empty `myTrader`. + * First way is prefereable, since we prefer not to touch placed + * `WeaponLocker`s in case someone decides to use them correctly and makes + * a custom class that plays welcome animation in some shop, but forgets about + * `WeaponLocker`s for other regular shops. + */ + +// Remember instance of `WeaponLocker` that we spawned, so we can clean +// it up later. +var private WeaponLocker dummyLocker; +// Remember fixed shops, so we can unfix them later. +var private array fixedShops; + +var LoggerAPI.Definition errNoReplacementLocker; + +protected function Constructor() +{ + local LevelInfo level; + local ShopVolume nextShop; + local WeaponLocker replacementLocker; + level = _.unreal.GetLevel(); + // Get locker + replacementLocker = SpawnDummyWeaponLocker(); + if (replacementLocker == none) { + replacementLocker = FindExistingWeaponLocker(); + } + if (replacementLocker == none) + { + _.logger.Auto(errNoReplacementLocker); + return; + } + // Setup locker + foreach level.DynamicActors(class'KFMod.ShopVolume', nextShop) + { + if (nextShop == none) continue; + if (nextShop.myTrader != none) continue; + nextShop.myTrader = replacementLocker; + fixedShops[fixedShops.length] = nextShop; + } +} + +protected function Finalizer() +{ + local int i; + for (i = 0; i < fixedShops.length; i += 1) + { + if (fixedShops[i] != none) { + fixedShops[i].myTrader = none; + } + } + if (dummyLocker != none && !dummyLocker.bPendingDelete) { + dummyLocker.Destroy(); + } +} + +private final function WeaponLocker SpawnDummyWeaponLocker() +{ + local bool savedCollideWorld, savedCollideActors; + if (dummyLocker != none && !dummyLocker.bPendingDelete) { + return dummyLocker; + } + // Disable collision so that dummy `WeaponLocker` would both not affect + // gameplay and would not fail spawning due to being inside terrain or + // another actor. + savedCollideWorld = class'WeaponLocker'.default.bCollideWorld; + savedCollideActors = class'WeaponLocker'.default.bBlockActors; + class'WeaponLocker'.default.bCollideWorld = false; + class'WeaponLocker'.default.bBlockActors = false; + dummyLocker = WeaponLocker(_.memory.Allocate(class'WeaponLocker')); + class'WeaponLocker'.default.bCollideWorld = savedCollideWorld; + class'WeaponLocker'.default.bBlockActors = savedCollideActors; + if (dummyLocker != none) + { + dummyLocker.bHidden = true; + dummyLocker.SetDrawType(DT_None); + dummyLocker.SetCollision(false); + } + return dummyLocker; +} + +private final function WeaponLocker FindExistingWeaponLocker() +{ + local LevelInfo level; + local WeaponLocker nextLocker; + level = _.unreal.GetLevel(); + foreach level.DynamicActors(class'KFMod.WeaponLocker', nextLocker) + { + if (nextLocker != none) { + return nextLocker; + } + } + return none; +} + +defaultproperties +{ + errNoReplacementLocker = (l=LOG_Error,m="`FixLogSpam` cannot find or spawn `WeaponLocker` actor. Shop log spam will not be disabled.") +} \ No newline at end of file