From fa5efe353799d70944a617a2e4d09291729316ca Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Fri, 15 Jul 2022 02:40:36 +0700 Subject: [PATCH] Add `SideEffects` settings Previously settings about side effects were scattered through different relevant classes. Now they will all be recorded in a single config object. Additionally, log messages about both success and failure of introducing these side effects. --- config/AcediaSystem.ini | 59 +++++++----- sources/CoreRealm/SideEffects.uc | 92 +++++++++++++++++++ .../KF1Frontend/Health/KF1_HealthComponent.uc | 56 +++-------- sources/Unreal/BroadcastsAPI/BroadcastAPI.uc | 24 ++++- .../BroadcastsAPI/BroadcastEventsObserver.uc | 8 +- sources/Unreal/GameRulesAPI/GameRulesAPI.uc | 21 ++++- 6 files changed, 185 insertions(+), 75 deletions(-) create mode 100644 sources/CoreRealm/SideEffects.uc diff --git a/config/AcediaSystem.ini b/config/AcediaSystem.ini index 980a049..fdc0466 100644 --- a/config/AcediaSystem.ini +++ b/config/AcediaSystem.ini @@ -1,48 +1,57 @@ -; Every single option in this config should be considered [ADVANCED] -[AcediaCore.BroadcastEventsObserver] -; Acedia requires injecting it's own `BroadcastHandler` to listen to -; the broadcasted messages. -; It's normal for a mod to add it's own broadcast handler: broadcast handlers -; are implemented in such a way that they form a linked list and, after -; first (root) handler receives a message it tells about said message to -; the next handler, which does the same, propagating messages through +; Every single option in this config should be considered [ADVANCED]. +; DO NOT CHANGE THEM unless you are sure you know what you're doing. +[AcediaCore.SideEffects] +; Acedia requires adding its own `GameRules` to listen to many different +; game events. +; It's normal for a mod to add its own game rules: game rules are +; implemented in such a way that they form a linked list and, after +; first (root) rules object receives a message it tells about said message to +; the next rules object, which does the same, propagating messages through ; the whole list. -; If you do not wish Acedia to add it's own handler, you should specify `0` as -; `usedInjectionLevel`'s value. If you want to allow it to simply add it's -; broadcast handler to the end of the handler's linked list, as described above, -; set it to `1`. -; However, more information can be obtained if Acedia's broadcast handler is -; inserted at the root of the whole chain. This is the prefered way for Acedia -; and if you do not have a reason to forbid it, you should leave this value -; at `2`. -usedInjectionLevel=BHIJ_Root - -[AcediaCore.KF1_HealthComponent] -; Unfortunately, thanks to the TWI's code, there's no way to catch events +; This is the least offensive side effect of AcediaCore and there should +; be no reason to prevents its `GameRules` from being added. +allowAddingGameRules=true +; Unfortunately, thanks to the TWI's code, there's no way to catch events ; of when certain kinds of damage are dealt: from welder, bloat's bile and ; siren's scream. At least not without something drastic, like replacing game ; type class. -; As a workaround, Acedia can optionally replace bloat and siren damage +; As a workaround, Acedia can optionally replace bloat and siren damage ; type to at least catch damage dealt by zeds (as being dealt welder damage is ; pretty rare and insignificant). This change has several unfortunate ; side-effects: -; 1. Potentially breaking mods that are looking for `DamTypeVomit` and +; 1. Potentially breaking mods that are looking for `DamTypeVomit` and ; `SirenScreamDamage` damage types specifically. Fixing this issue ; would require these mods to either also try and catch Acedia's ; replacements `AcediaCore.Dummy_DamTypeVomit` and ; `AcediaCore.Dummy_SirenScreamDamage` or to catch any child classes ; of `DamTypeVomit` and `SirenScreamDamage` (as Acedia's replacements ; are also their child classes). -; 2. Breaking some achievements that rely on +; 2. Breaking some achievements that rely on ; `KFSteamStatsAndAchievements`'s `KilledEnemyWithBloatAcid()` method ; being called. This is mostly dealt with by Acedia calling it ; manually. However it relies on killed pawn to have ; `lastDamagedByType` set to `DamTypeVomit`, which sometimes might not ; be the case. Achievements should still be obtainable. -; 3. A lot of siren's visual damage effects code does direct checks for +; 3. A lot of siren's visual damage effects code does direct checks for ; `SirenScreamDamage` class. These can also break, stopping working as ; intended. -replaceBloatAndSirenDamageTypes=true +allowReplacingDamageTypes=true +; Acedia requires injecting its own `BroadcastHandler` to listen to +; the broadcasted messages. +; It's normal for a mod to add its own broadcast handler: broadcast handlers +; are implemented in such a way that they form a linked list and, after +; first (root) handler receives a message it tells about said message to +; the next handler, which does the same, propagating messages through +; the whole list. +; If you do not wish Acedia to add its own handler, you should specify +; `BHIJ_None` as `broadcastHandlerInjectionLevel`'s value. If you want to allow +; it to simply add its broadcast handler to the end of the handler's +; linked list, as described above, set it to `BHIJ_Registered`. +; However, more information can be obtained if Acedia's broadcast handler is +; inserted at the root of the whole chain. This is the preferred way for +; Acedia and if you do not have a reason to forbid that (for example, for mod +; compatibility reasons), you should set this value at `BHIJ_Root`. +broadcastHandlerInjectionLevel=BHIJ_Root [AcediaCore.UserAPI] userDataDBLink="local:database/users" diff --git a/sources/CoreRealm/SideEffects.uc b/sources/CoreRealm/SideEffects.uc new file mode 100644 index 0000000..104ab12 --- /dev/null +++ b/sources/CoreRealm/SideEffects.uc @@ -0,0 +1,92 @@ +/** + * This object is meant purely as a dummy class to load config values about + * side effects in AcediaCore. Class name is chosen to make config more + * readable. + * Copyright 2022 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 SideEffects extends AcediaObject + dependson(BroadcastAPI) + abstract + config(AcediaSystem); + +/** + * Acedia requires adding its own `GameRules` to listen to many different + * game events. + * It's normal for a mod to add its own game rules: game rules are + * implemented in such a way that they form a linked list and, after + * first (root) rules object receives a message it tells about said message to + * the next rules object, which does the same, propagating messages through + * the whole list. + * This is the least offensive side effect of AcediaCore and there should + * be no reason to prevents its `GameRules` from being added. + */ +var public const config bool allowAddingGameRules; + +/** + * Unfortunately, thanks to the TWI's code, there's no way to catch events + * of when certain kinds of damage are dealt: from welder, bloat's bile and + * siren's scream. At least not without something drastic, like replacing game + * type class. + * As a workaround, Acedia can optionally replace bloat and siren damage + * type to at least catch damage dealt by zeds (as being dealt welder damage is + * pretty rare and insignificant). This change has several unfortunate + * side-effects: + * 1. Potentially breaking mods that are looking for `DamTypeVomit` and + * `SirenScreamDamage` damage types specifically. Fixing this issue + * would require these mods to either also try and catch Acedia's + * replacements `AcediaCore.Dummy_DamTypeVomit` and + * `AcediaCore.Dummy_SirenScreamDamage` or to catch any child classes + * of `DamTypeVomit` and `SirenScreamDamage` (as Acedia's replacements + * are also their child classes). + * 2. Breaking some achievements that rely on + * `KFSteamStatsAndAchievements`'s `KilledEnemyWithBloatAcid()` method + * being called. This is mostly dealt with by Acedia calling it + * manually. However it relies on killed pawn to have + * `lastDamagedByType` set to `DamTypeVomit`, which sometimes might not + * be the case. Achievements should still be obtainable. + * 3. A lot of siren's visual damage effects code does direct checks for + * `SirenScreamDamage` class. These can also break, stopping working as + * intended. + */ +var public const config bool allowReplacingDamageTypes; + +/** + * Acedia requires injecting its own `BroadcastHandler` to listen to + * the broadcasted messages. + * It's normal for a mod to add its own broadcast handler: broadcast + * handlers are implemented in such a way that they form a linked list and, + * after first (root) handler receives a message it tells about said message to + * the next handler, which does the same, propagating messages through + * the whole list. + * If you do not wish Acedia to add its own handler, you should specify + * `BHIJ_None` as `broadcastHandlerInjectionLevel`'s value. If you want to + * allow it to simply add its broadcast handler to the end of the handler's + * linked list, as described above, set it to `BHIJ_Registered`. + * However, more information can be obtained if Acedia's broadcast handler + * is inserted at the root of the whole chain. This is the preferred way for + * Acedia and if you do not have a reason to forbid that (for example, for mod + * compatibility reasons), you should set this value at `BHIJ_Root`. + */ +var public const config BroadcastAPI.InjectionLevel broadcastHandlerInjectionLevel; + +defaultproperties +{ + allowAddingGameRules = true + allowReplacingDamageTypes = true + broadcastHandlerInjectionLevel = BHIJ_Root +} \ No newline at end of file diff --git a/sources/Gameplay/KF1Frontend/Health/KF1_HealthComponent.uc b/sources/Gameplay/KF1Frontend/Health/KF1_HealthComponent.uc index c55c7a8..7ff559f 100644 --- a/sources/Gameplay/KF1Frontend/Health/KF1_HealthComponent.uc +++ b/sources/Gameplay/KF1Frontend/Health/KF1_HealthComponent.uc @@ -22,40 +22,13 @@ class KF1_HealthComponent extends AHealthComponent config(AcediaSystem); var private bool connectedToGameRules; -var private bool replacedDamageTypes; - -/** - * Unfortunately, thanks to the TWI's code, there's no way to catch events - * of when certain kinds of damage are dealt: from welder, bloat's bile and - * siren's scream. At least not without something drastic, like replacing game - * type class. - * As a workaround, Acedia can optionally replace bloat and siren damage - * type to at least catch damage dealt by zeds (as being dealt welder damage is - * pretty rare and insignificant). This change has several unfortunate - * side-effects: - * 1. Potentially breaking mods that are looking for `DamTypeVomit` and - * `SirenScreamDamage` damage types specifically. Fixing this issue - * would require these mods to either also try and catch Acedia's - * replacements `AcediaCore.Dummy_DamTypeVomit` and - * `AcediaCore.Dummy_SirenScreamDamage` or to catch any child classes - * of `DamTypeVomit` and `SirenScreamDamage` (as Acedia's replacements - * are also their child classes). - * 2. Breaking some achievements that rely on - * `KFSteamStatsAndAchievements`'s `KilledEnemyWithBloatAcid()` method - * being called. This is mostly dealt with by Acedia calling it - * manually. However it relies on killed pawn to have - * `lastDamagedByType` set to `DamTypeVomit`, which sometimes might not - * be the case. Achievements should still be obtainable. - * 3. A lot of siren's visual damage effects code does direct checks for - * `SirenScreamDamage` class. These can also break, stopping working as - * intended. - */ -var private const config bool replaceBloatAndSirenDamageTypes; +var private bool triedToReplaceDamageTypes; var private const int TDAMAGE, TORIGINAL_DAMAGE, THIT_LOCATION, TMOMENTUM; var private LoggerAPI.Definition infoReplacingDamageTypes, errNoServerLevelCore; var private LoggerAPI.Definition infoRestoringReplacingDamageTypes; +var private LoggerAPI.Definition warnReplacingDamageTypesForbidden; protected function Finalizer() { @@ -66,11 +39,6 @@ protected function Finalizer() _.unreal.gameRules.OnScoreKill(self).Disconnect(); connectedToGameRules = false; } - if (replaceBloatAndSirenDamageTypes) - { - RestoreDamageTypes(); - replacedDamageTypes = false; - } } public function Health_OnDamage_Slot OnDamage(AcediaObject receiver) @@ -95,10 +63,15 @@ private final function TryReplaceDamageTypes() { local LevelCore core; - if (!replaceBloatAndSirenDamageTypes) return; - if (replacedDamageTypes) return; - - replacedDamageTypes = true; + if (triedToReplaceDamageTypes) { + return; + } + triedToReplaceDamageTypes = true; + if (!class'SideEffects'.default.allowReplacingDamageTypes) + { + _.logger.Auto(warnReplacingDamageTypesForbidden); + return; + } _.logger.Auto(infoReplacingDamageTypes); core = class'ServerLevelCore'.static.GetInstance(); if (core != none) @@ -153,7 +126,6 @@ private final function RestoreDamageTypes() class'ZombieSiren_XMas'.default.screamDamageType = class'SirenScreamDamage'; class'ZombieSiren_CIRCUS'.default.screamDamageType = class'SirenScreamDamage'; - replacedDamageTypes = false; } private function int OnNetDamageHandler( @@ -248,7 +220,6 @@ private function UpdateBileAchievement(Controller killer, Controller killed) defaultproperties { - replaceBloatAndSirenDamageTypes = true TDAMAGE = 0 stringConstants(0) = "damage" TORIGINAL_DAMAGE = 1 @@ -257,7 +228,8 @@ defaultproperties stringConstants(2) = "hitLocation" TMOMENTUM = 3 stringConstants(3) = "momentum" - infoReplacingDamageTypes = (l=LOG_Info,m="Replacing bloat's and siren's damage types with dummy ones.") - infoRestoringReplacingDamageTypes = (l=LOG_Info,m="Restoring bloat and siren's damage types to their original values.") + infoReplacingDamageTypes = (l=LOG_Info,m="Replaced bloat's and siren's damage types with dummy ones.") + infoRestoringReplacingDamageTypes = (l=LOG_Info,m="Restored bloat and siren's damage types to their original values.") errNoServerLevelCore = (l=LOG_Error,m="Server level core is missing. Either this isn't a server or Acedia was wrongly initialized. Bloat and siren damage type will not be replaced.") + warnReplacingDamageTypesForbidden = (l=LOG_Warning,m="`OnDamage()` signal is used, but might not work properly because bloat and siren damage type replacement is forbidden by AcediaCore's settings: in file \"AcediaSystem.ini\", section [AcediaCore.SideEffects], variable `allowReplacingDamageTypes`.") } \ No newline at end of file diff --git a/sources/Unreal/BroadcastsAPI/BroadcastAPI.uc b/sources/Unreal/BroadcastsAPI/BroadcastAPI.uc index 23d653b..5504c37 100644 --- a/sources/Unreal/BroadcastsAPI/BroadcastAPI.uc +++ b/sources/Unreal/BroadcastsAPI/BroadcastAPI.uc @@ -20,6 +20,10 @@ */ class BroadcastAPI extends AcediaObject; +// Tracks if we have already tried to add our own `BroadcastHandler` to avoid +// wasting resources/spamming errors in the log about our inability to do so +var private bool triedToInjectBroadcastHandler; + /** * Defines ways to add a new `BroadcastHandler` into the `GameInfo`'s * `BroadcastHandler` linked list. @@ -58,6 +62,8 @@ struct LocalizedMessage }; var private LoggerAPI.Definition infoInjectedBroadcastEventsObserver; +var private LoggerAPI.Definition errBroadcasthandlerForbidden; +var private LoggerAPI.Definition errBroadcasthandlerUnknown; /** * Called before text message is sent to any player, during the check for @@ -277,10 +283,11 @@ private final function TryInjectBroadcastHandler(UnrealService service) local BroadcastSideEffect sideEffect; local BroadcastEventsObserver broadcastObserver; - if (IsAdded(class'BroadcastEventsObserver')) { + if (triedToInjectBroadcasthandler) { return; } - usedLevel = class'BroadcastEventsObserver'.default.usedInjectionLevel; + triedToInjectBroadcasthandler = true; + usedLevel = class'SideEffects'.default.broadcastHandlerInjectionLevel; broadcastObserver = BroadcastEventsObserver(_.unreal.broadcasts.Add( class'BroadcastEventsObserver', usedLevel)); if (broadcastObserver != none) @@ -294,6 +301,17 @@ private final function TryInjectBroadcastHandler(UnrealService service) _.logger .Auto(infoInjectedBroadcastEventsObserver) .Arg(InjectionLevelIntoText(usedLevel)); + return; + } + // We are here if we have failed + if (usedLevel == BHIJ_None) { + _.logger.Auto(errBroadcastHandlerForbidden); + } + else + { + _.logger + .Auto(errBroadcastHandlerUnknown) + .Arg(InjectionLevelIntoText(usedLevel)); } } @@ -448,4 +466,6 @@ public final function bool IsAdded(class BHClassToFind) defaultproperties { infoInjectedBroadcastEventsObserver = (l=LOG_Info,m="Injected AcediaCore's `BroadcastEventsObserver` with level `%1`.") + errBroadcastHandlerForbidden = (l=LOG_Error,m="Injected AcediaCore's `BroadcastEventsObserver` is required, but forbidden by AcediaCore's settings: in file \"AcediaSystem.ini\", section [AcediaCore.SideEffects], variable `broadcastHandlerInjectionLevel`.") + errBroadcastHandlerUnknown = (l=LOG_Error,m="Injected AcediaCore's `BroadcastEventsObserver` failed to be injected with level `%1` for unknown reason.") } \ No newline at end of file diff --git a/sources/Unreal/BroadcastsAPI/BroadcastEventsObserver.uc b/sources/Unreal/BroadcastsAPI/BroadcastEventsObserver.uc index f0d1350..71b3fa6 100644 --- a/sources/Unreal/BroadcastsAPI/BroadcastEventsObserver.uc +++ b/sources/Unreal/BroadcastsAPI/BroadcastEventsObserver.uc @@ -30,7 +30,7 @@ class BroadcastEventsObserver extends Engine.BroadcastHandler // To alleviate this issue Acedia allows server admins to control how it's // `BroadcastHandler` is injected. Do note however that anything other than // `BHIJ_Root` can lead to issues with Acedia's features. -var public config const BroadcastAPI.InjectionLevel usedInjectionLevel; +var private BroadcastAPI.InjectionLevel usedInjectionLevel; /** * To understand how what our broadcast handler does, let us first explain @@ -206,6 +206,8 @@ var private Broadcast_OnHandleTextFor_Signal onHandleTextFor; public final function Initialize(UnrealService service) { + usedInjectionLevel = + class'SideEffects'.default.broadcastHandlerInjectionLevel; if (usedInjectionLevel != BHIJ_Root) { Disable('Tick'); } @@ -475,9 +477,7 @@ event Tick(float delta) ResetTracking(); } -// senders, out for handletext defaultproperties { - blockAllowsBroadcast = false - usedInjectionLevel = BHIJ_Root + blockAllowsBroadcast = false } \ No newline at end of file diff --git a/sources/Unreal/GameRulesAPI/GameRulesAPI.uc b/sources/Unreal/GameRulesAPI/GameRulesAPI.uc index 2021f24..c736374 100644 --- a/sources/Unreal/GameRulesAPI/GameRulesAPI.uc +++ b/sources/Unreal/GameRulesAPI/GameRulesAPI.uc @@ -20,7 +20,13 @@ */ class GameRulesAPI extends AcediaObject; +// Tracks if we have already tried to add our own `BroadcastHandler` to avoid +// wasting resources/spamming errors in the log about our inability to do so +var private bool triedToInjectGameRules; + var private LoggerAPI.Definition infoAddedGameRules; +var private LoggerAPI.Definition errGameRulesForbidden; +var private LoggerAPI.Definition errGameRulesUnknown; /** * Called when game decides on a player's spawn point. If a `NavigationPoint` @@ -272,7 +278,13 @@ private final function TryAddingGameRules(UnrealService service) local AcediaGameRules gameRules; local GameRulesSideEffect sideEffect; - if (AreAdded(class'AcediaGameRules')) { + if (triedToInjectGameRules) { + return; + } + triedToInjectGameRules = true; + if (!class'SideEffects'.default.allowAddingGameRules) + { + _.logger.Auto(errGameRulesForbidden); return; } gameRules = AcediaGameRules(Add(class'AcediaGameRules')); @@ -286,6 +298,9 @@ private final function TryAddingGameRules(UnrealService service) _.memory.Free(sideEffect); _.logger.Auto(infoAddedGameRules); } + else { + _.logger.Auto(errGameRulesUnknown); + } } /** @@ -390,5 +405,7 @@ public final function bool AreAdded( defaultproperties { - infoAddedGameRules = (l=LOG_Info,m="Added AcediaCore's `AcediaGameRules`.") + infoAddedGameRules = (l=LOG_Info,m="Added AcediaCore's `AcediaGameRules`.") + errGameRulesForbidden = (l=LOG_Error,m="Adding AcediaCore's `AcediaGameRules` is required, but forbidden by AcediaCore's settings: in file \"AcediaSystem.ini\", section [AcediaCore.SideEffects], variable `allowAddingGameRules`.") + errGameRulesUnknown = (l=LOG_Error,m="Adding AcediaCore's `AcediaGameRules` failed to be injected with level for unknown reason.") } \ No newline at end of file