Anton Tarasenko
4 years ago
5 changed files with 364 additions and 10 deletions
@ -0,0 +1,62 @@ |
|||||||
|
/** |
||||||
|
* This feature fixes different instances of log spam by the killing floor |
||||||
|
* with various warnings and errors. Some of them have actual underlying bugs |
||||||
|
* that need to be fixed, but a lot seem to be just a byproduct of dead and |
||||||
|
* abandoned features or simple negligence. |
||||||
|
* Whatever the case, now that TWI will no longer make any new changes to |
||||||
|
* the game a lot of them do not serve any purpose and simply pollute |
||||||
|
* log files. We try to get rid of at least some of them. |
||||||
|
* Since changes we make do not actually have gameplay effect and |
||||||
|
* are more aimed at convenience of server owners, our philosophy with the |
||||||
|
* changes will be to avoid solutions that are way too "hacky" and prefer some |
||||||
|
* message spam getting through to the possibility of some unexpected gameplay |
||||||
|
* effects as far as vanilla game is concerned. |
||||||
|
* 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 <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class FixLogSpam extends Feature |
||||||
|
config(AcediaFixes); |
||||||
|
|
||||||
|
// 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; |
||||||
|
|
||||||
|
protected function OnEnabled() |
||||||
|
{ |
||||||
|
if (fixPickupSpam) { |
||||||
|
helperPickupSpam = HelperPickup(_.memory.Allocate(class'HelperPickup')); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected function OnDisabled() |
||||||
|
{ |
||||||
|
_.memory.Free(helperPickupSpam); |
||||||
|
helperPickupSpam = none; |
||||||
|
} |
||||||
|
|
||||||
|
public function Tick(float delta) |
||||||
|
{ |
||||||
|
if (helperPickupSpam != none) { |
||||||
|
helperPickupSpam.Tick(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
fixPickupSpam = true |
||||||
|
} |
@ -0,0 +1,213 @@ |
|||||||
|
/** |
||||||
|
* Helper object for `FixLogSpam` fix, responsible for fixing log spam |
||||||
|
* due to picking up dropped weapons without set `inventory` 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 <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class HelperPickup extends AcediaObject |
||||||
|
config(AcediaFixes); |
||||||
|
|
||||||
|
/** |
||||||
|
* `KFWeaponPickup` class is responsible for spamming log with |
||||||
|
* error messages about missing `inventory` actor: "Accessed None 'Inventory'". |
||||||
|
* This is caused by `Destroyed()` event that tries to call `KFGameType`'s |
||||||
|
* `WeaponDestroyed()` on pickup's `inventory.class`, but it fails because |
||||||
|
* `inventory` is essentially guaranteed to be `none` for pickups dropped |
||||||
|
* by players. Since no `inventory != none` check is made, this leads to |
||||||
|
* log message spam. |
||||||
|
* To fix it we simply disable `Destroyed()` event altogether, since all |
||||||
|
* it does is: |
||||||
|
* 1. Fails as we have described; |
||||||
|
* 2. In `super(Pickup).Destroyed()` sets `myMarker.markedItem` to `none`, |
||||||
|
* which does nothig for pickups dropped by players, since they do not |
||||||
|
* have any `myMarker` in the first place: they are usually defined to |
||||||
|
* refer to `InventorySpot`s, generated for placed pickups when |
||||||
|
* navigation paths are built. |
||||||
|
* |
||||||
|
* The are two issues that need resolving for this solution to work: |
||||||
|
* 1. Distinguish between pickups dropped by the player versus pickups |
||||||
|
* placed on the map; |
||||||
|
* 2. Even if one disables `Destroyed()` event, it can get re-enabled again |
||||||
|
* (possibly by a state change). |
||||||
|
* One way to accomplish the first goal is to check `instigator` during |
||||||
|
* `CheckReplacement()` event: it should be `none` for map pickups and point |
||||||
|
* to the player's pawn for dropped pickups. If, for some reason, instigator |
||||||
|
* would end up being equal to `none` for a dropped pickup, we also record all |
||||||
|
* the other pickups and give them some time to initialize |
||||||
|
* (`CheckReplacement()` is called at the moment they are spawned and |
||||||
|
* before initialization logic is completed) and check their `bDropped` flag, |
||||||
|
* since it is what decides whether log message about accessing `none`-value |
||||||
|
* will be output: `if ( bDropped && class<Weapon>(Inventory.Class) != none )`. |
||||||
|
* Then we also try to fix all the weapons with `bDropped == true`. |
||||||
|
* To resolve issue with `Destroyed()` event being re-enabled we use |
||||||
|
* brute force approach of disabling it every tick: |
||||||
|
* 1. Amount of dropped pickups is small enough for it to not cause |
||||||
|
* performance issues; |
||||||
|
* 2. Checking change in state or trying to determine other causes for |
||||||
|
* re-enabling the event would likely just lead to more complicated |
||||||
|
* logic with around the same or even worse performance. |
||||||
|
* We also enforce additional update when we catch player trying to pickup some |
||||||
|
* `KFWeaponPickup` with `GameRules`'s `OverridePickupQuery()`, which is |
||||||
|
* called soon before pickup's destruction. Note that this update cannot |
||||||
|
* replace update inside the `Tick()`, since pickup can be destroyed without |
||||||
|
* being picked up, i.e. at the start of each wave. |
||||||
|
* |
||||||
|
* Described method does not 100% guarantee removal of the related spam |
||||||
|
* messages, but should make them virtually impossible. At least on |
||||||
|
* vanilla servers. |
||||||
|
*/ |
||||||
|
|
||||||
|
// For easy access in relevant `GameRules` and mutator event listener. |
||||||
|
var private HelperPickup singletonInstance; |
||||||
|
// Already fixed pickups that we periodically "refix". |
||||||
|
var private array<KFWeaponPickup> recordedPickups; |
||||||
|
// Pickups that will get `bDropped` flag checked the next tick to |
||||||
|
// determine if we need to fix them. |
||||||
|
var private array<KFWeaponPickup> pendingPickups; |
||||||
|
|
||||||
|
protected function Constructor() |
||||||
|
{ |
||||||
|
local LevelInfo level; |
||||||
|
local KFWeaponPickup nextPickup; |
||||||
|
if (default.singletonInstance == none) { |
||||||
|
default.singletonInstance = self; |
||||||
|
} |
||||||
|
// To detect when player tries to pick something up |
||||||
|
// (and force additional pickup fix update) |
||||||
|
_.unreal.AddGameRules(class'PickupSpamRule'); |
||||||
|
// To detect newly spawned pickups |
||||||
|
class'MutatorListener_FixLogSpam_Pickup'.static.SetActive(true); |
||||||
|
// Find all `KFWeaponPickup`s laying around on the map, |
||||||
|
// so that we can fix preexisting ones too. |
||||||
|
// But add them to pending list in a freaky case this `HealperPickup` |
||||||
|
// was created during one's initialization. This will give it time to |
||||||
|
// set up variables that distinguish dropped pickup from another |
||||||
|
// kind of pickup. |
||||||
|
level = _.unreal.GetLevel(); |
||||||
|
foreach level.DynamicActors(class'KFMod.KFWeaponPickup', nextPickup) { |
||||||
|
pendingPickups[pendingPickups.length] = nextPickup; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected function Finalizer() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < recordedPickups.length; i += 1) |
||||||
|
{ |
||||||
|
if (recordedPickups[i] != none && !recordedPickups[i].bPendingDelete) { |
||||||
|
recordedPickups[i].Enable('Destroyed'); |
||||||
|
} |
||||||
|
} |
||||||
|
recordedPickups.length = 0; |
||||||
|
pendingPickups.length = 0; |
||||||
|
_.unreal.RemoveGameRules(class'PickupSpamRule'); |
||||||
|
class'MutatorListener_FixLogSpam_Pickup'.static.SetActive(false); |
||||||
|
} |
||||||
|
|
||||||
|
public final static function HelperPickup GetInstance() |
||||||
|
{ |
||||||
|
return default.singletonInstance; |
||||||
|
} |
||||||
|
|
||||||
|
// Checks whether given pickup is recorded in any of our records, |
||||||
|
// including pending pickups. |
||||||
|
// `none` is never recorded. |
||||||
|
private final function bool IsPickupRecorded(KFWeaponPickup pickupToCheck) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
if (pickupToCheck == none) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
for (i = 0; i < recordedPickups.length; i += 1) |
||||||
|
{ |
||||||
|
if (recordedPickups[i] == pickupToCheck) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
for (i = 0; i < pendingPickups.length; i += 1) |
||||||
|
{ |
||||||
|
if (pendingPickups[i] == pickupToCheck) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// Gets rid of pickups that got destroyed at some point and now are set |
||||||
|
// to `none`. |
||||||
|
private final function CleanRecordedPickups() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
while (i < recordedPickups.length) |
||||||
|
{ |
||||||
|
if (recordedPickups[i] == none) { |
||||||
|
recordedPickups.Remove(i, 1); |
||||||
|
} |
||||||
|
else { |
||||||
|
i += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Makes helper handle given pickup `newPickup` by either fixing it or |
||||||
|
// delaying to check whether it is dropped. |
||||||
|
public final function HandlePickup(KFWeaponPickup newPickup) |
||||||
|
{ |
||||||
|
if (newPickup == none) return; |
||||||
|
if (IsPickupRecorded(newPickup)) return; |
||||||
|
|
||||||
|
if (newPickup.instigator != none) |
||||||
|
{ |
||||||
|
newPickup.Disable('Destroyed'); |
||||||
|
recordedPickups[recordedPickups.length] = newPickup; |
||||||
|
} |
||||||
|
else { |
||||||
|
pendingPickups[pendingPickups.length] = newPickup; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Re-disables `Destroyed()` event for all recorded (confirmed) dropped pickups |
||||||
|
// and processes all pending (for the check if they are dropped) pickups. |
||||||
|
public final function UpdatePickups() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < recordedPickups.length; i += 1) |
||||||
|
{ |
||||||
|
if (recordedPickups[i] != none) { |
||||||
|
recordedPickups[i].Disable('Destroyed'); |
||||||
|
} |
||||||
|
} |
||||||
|
for (i = 0; i < pendingPickups.length; i += 1) |
||||||
|
{ |
||||||
|
if (pendingPickups[i].bDropped) |
||||||
|
{ |
||||||
|
pendingPickups[i].Disable('Destroyed'); |
||||||
|
recordedPickups[recordedPickups.length] = pendingPickups[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
pendingPickups.length = 0; |
||||||
|
} |
||||||
|
|
||||||
|
public final function Tick() |
||||||
|
{ |
||||||
|
CleanRecordedPickups(); |
||||||
|
UpdatePickups(); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
/** |
||||||
|
* Overloaded mutator events listener to catch and, possibly, |
||||||
|
* prevent spawning `KFWeaponPickup` for fixing log spam related to them. |
||||||
|
* 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 <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class MutatorListener_FixLogSpam_Pickup extends MutatorListenerBase |
||||||
|
abstract; |
||||||
|
|
||||||
|
static function bool CheckReplacement(Actor other, out byte isSuperRelevant) |
||||||
|
{ |
||||||
|
local HelperPickup helper; |
||||||
|
local KFWeaponPickup otherPickup; |
||||||
|
otherPickup = KFWeaponPickup(other); |
||||||
|
if (otherPickup == none) return true; |
||||||
|
helper = class'HelperPickup'.static.GetInstance(); |
||||||
|
if (helper == none) return true; |
||||||
|
|
||||||
|
helper.HandlePickup(otherPickup); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedEvents = class'MutatorEvents' |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
/** |
||||||
|
* This rule detects when someone is trying to... pick up a pickup and |
||||||
|
* forces additional fixing update on all the ones, dropped by players. |
||||||
|
* 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 <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class PickupSpamRule extends GameRules; |
||||||
|
|
||||||
|
function bool OverridePickupQuery( |
||||||
|
Pawn toucher, |
||||||
|
Pickup touchedPickup, |
||||||
|
out byte allowPickup) |
||||||
|
{ |
||||||
|
local HelperPickup helper; |
||||||
|
helper = class'HelperPickup'.static.GetInstance(); |
||||||
|
if (helper != none) { |
||||||
|
helper.UpdatePickups(); |
||||||
|
} |
||||||
|
return super.OverridePickupQuery(toucher, touchedPickup, allowPickup); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
Loading…
Reference in new issue