diff --git a/sources/Gameplay/BaseClasses/Frontend/Health/AHealthComponent.uc b/sources/Gameplay/BaseClasses/Frontend/Health/AHealthComponent.uc index f7d4235..4b34d30 100644 --- a/sources/Gameplay/BaseClasses/Frontend/Health/AHealthComponent.uc +++ b/sources/Gameplay/BaseClasses/Frontend/Health/AHealthComponent.uc @@ -54,7 +54,7 @@ protected function Finalizer() * as a result of the damage dealt. */ /* SIGNAL */ -public final function Health_OnDamage_Slot OnDamage(AcediaObject receiver) +public function Health_OnDamage_Slot OnDamage(AcediaObject receiver) { return Health_OnDamage_Slot(onDamageSignal.NewSlot(receiver)); } diff --git a/sources/Gameplay/KF1Frontend/Health/KF1_HealthComponent.uc b/sources/Gameplay/KF1Frontend/Health/KF1_HealthComponent.uc index c0ed1c8..ef6e2b6 100644 --- a/sources/Gameplay/KF1Frontend/Health/KF1_HealthComponent.uc +++ b/sources/Gameplay/KF1Frontend/Health/KF1_HealthComponent.uc @@ -21,6 +21,8 @@ class KF1_HealthComponent extends AHealthComponent dependson(ConnectionService) config(AcediaSystem); +var private bool connectedToGameRules; + /** * 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 @@ -58,7 +60,6 @@ public function PseudoConstructor() { local LevelCore core; - _.unreal.gameRules.OnNetDamage(self).connect = OnNetDamageHandler; if (!replaceBloatAndSirenDamageTypes) { return; } @@ -67,8 +68,6 @@ public function PseudoConstructor() if (core != none) { ReplaceDamageTypes(core); - // Fixes achievements - _.unreal.gameRules.OnScoreKill(self).connect = UpdateBileAchievement; core.OnShutdown(self).connect = RestoreDamageTypes; } else { @@ -84,6 +83,24 @@ protected function Finalizer() if (replaceBloatAndSirenDamageTypes) { RestoreDamageTypes(); } + connectedToGameRules = false; +} + +public function Health_OnDamage_Slot OnDamage(AcediaObject receiver) +{ + TryConnectToGameRules(); + return super.OnDamage(receiver); +} + +private final function TryConnectToGameRules() +{ + if (connectedToGameRules) { + return; + } + connectedToGameRules = true; + _.unreal.gameRules.OnNetDamage(self).connect = OnNetDamageHandler; + // Fixes achievements + _.unreal.gameRules.OnScoreKill(self).connect = UpdateBileAchievement; } private final function ReplaceDamageTypes(LevelCore core) diff --git a/sources/Unreal/GameRulesAPI/GameRulesAPI.uc b/sources/Unreal/GameRulesAPI/GameRulesAPI.uc index 9ae4533..2021f24 100644 --- a/sources/Unreal/GameRulesAPI/GameRulesAPI.uc +++ b/sources/Unreal/GameRulesAPI/GameRulesAPI.uc @@ -20,6 +20,8 @@ */ class GameRulesAPI extends AcediaObject; +var private LoggerAPI.Definition infoAddedGameRules; + /** * Called when game decides on a player's spawn point. If a `NavigationPoint` * is returned, signal propagation will be interrupted and returned value will @@ -43,7 +45,9 @@ public final function GameRules_OnFindPlayerStart_Slot OnFindPlayerStart( { local Signal signal; local UnrealService service; + service = UnrealService(class'UnrealService'.static.Require()); + TryAddingGameRules(service); signal = service.GetSignal(class'GameRules_OnFindPlayerStart_Signal'); return GameRules_OnFindPlayerStart_Slot(signal.NewSlot(receiver)); } @@ -67,7 +71,9 @@ public final function GameRules_OnHandleRestartGame_Slot OnHandleRestartGame( { local Signal signal; local UnrealService service; + service = UnrealService(class'UnrealService'.static.Require()); + TryAddingGameRules(service); signal = service.GetSignal(class'GameRules_OnHandleRestartGame_Signal'); return GameRules_OnHandleRestartGame_Slot(signal.NewSlot(receiver)); } @@ -93,7 +99,9 @@ public final function GameRules_OnCheckEndGame_Slot OnCheckEndGame( { local Signal signal; local UnrealService service; + service = UnrealService(class'UnrealService'.static.Require()); + TryAddingGameRules(service); signal = service.GetSignal(class'GameRules_OnCheckEndGame_Signal'); return GameRules_OnCheckEndGame_Slot(signal.NewSlot(receiver)); } @@ -120,7 +128,9 @@ public final function GameRules_OnCheckScore_Slot OnCheckScore( { local Signal signal; local UnrealService service; + service = UnrealService(class'UnrealService'.static.Require()); + TryAddingGameRules(service); signal = service.GetSignal(class'GameRules_OnCheckScore_Signal'); return GameRules_OnCheckScore_Slot(signal.NewSlot(receiver)); } @@ -151,7 +161,9 @@ public final function GameRules_OnOverridePickupQuery_Slot { local Signal signal; local UnrealService service; + service = UnrealService(class'UnrealService'.static.Require()); + TryAddingGameRules(service); signal = service.GetSignal(class'GameRules_OnOverridePickupQuery_Signal'); return GameRules_OnOverridePickupQuery_Slot(signal.NewSlot(receiver)); } @@ -192,7 +204,9 @@ public final function GameRules_OnNetDamage_Slot OnNetDamage( { local Signal signal; local UnrealService service; + service = UnrealService(class'UnrealService'.static.Require()); + TryAddingGameRules(service); signal = service.GetSignal(class'GameRules_OnNetDamage_Signal'); return GameRules_OnNetDamage_Slot(signal.NewSlot(receiver)); } @@ -224,7 +238,9 @@ public final function GameRules_OnPreventDeath_Slot OnPreventDeath( { local Signal signal; local UnrealService service; + service = UnrealService(class'UnrealService'.static.Require()); + TryAddingGameRules(service); signal = service.GetSignal(class'GameRules_OnPreventDeath_Signal'); return GameRules_OnPreventDeath_Slot(signal.NewSlot(receiver)); } @@ -244,11 +260,34 @@ public final function GameRules_OnScoreKill_Slot OnScoreKill( { local Signal signal; local UnrealService service; + service = UnrealService(class'UnrealService'.static.Require()); + TryAddingGameRules(service); signal = service.GetSignal(class'GameRules_OnScoreKill_Signal'); return GameRules_OnScoreKill_Slot(signal.NewSlot(receiver)); } +private final function TryAddingGameRules(UnrealService service) +{ + local AcediaGameRules gameRules; + local GameRulesSideEffect sideEffect; + + if (AreAdded(class'AcediaGameRules')) { + return; + } + gameRules = AcediaGameRules(Add(class'AcediaGameRules')); + if (gameRules != none) + { + gameRules.Initialize(service); + sideEffect = + GameRulesSideEffect(_.memory.Allocate(class'GameRulesSideEffect')); + sideEffect.Initialize(); + _server.sideEffects.Add(sideEffect); + _.memory.Free(sideEffect); + _.logger.Auto(infoAddedGameRules); + } +} + /** * Adds new `GameRules` class to the current `GameInfo`. * Does nothing if given `GameRules` class was already added before. @@ -351,4 +390,5 @@ public final function bool AreAdded( defaultproperties { + infoAddedGameRules = (l=LOG_Info,m="Added AcediaCore's `AcediaGameRules`.") } \ No newline at end of file diff --git a/sources/Unreal/GameRulesAPI/GameRulesSideEffect.uc b/sources/Unreal/GameRulesAPI/GameRulesSideEffect.uc new file mode 100644 index 0000000..da70011 --- /dev/null +++ b/sources/Unreal/GameRulesAPI/GameRulesSideEffect.uc @@ -0,0 +1,43 @@ +/** + * Object representing a side effect introduced into the game/server. + * Side effects in Acedia refer to changes that aren't a part of mod's main + * functionality, but rather something necessary to make that functionality + * possible that might also affect how other mods work. + * This is a simple data container that is meant to describe relevant + * changes to the human user. + * 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 GameRulesSideEffect extends SideEffect; + +public final function Initialize() +{ + sideEffectName = + _.text.FromString("AcediaCore's `AcediaGameRules` added"); + sideEffectDescription = + _.text.FromString("`GameRule`s is one of the main ways to get notified" + @ "about various gameplay-related events in Unreal Engine." + @ "Of course AcediaCore would require handling some of those events," + @ "depending on how it's used."); + sideEffectPackage = _.text.FromString("AcediaCore"); + sideEffectSource = _.text.FromString("UnrealAPI"); + sideEffectStatus = _.text.FromFormattedString("{$TextPositive active}"); +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Unreal/UnrealService.uc b/sources/Unreal/UnrealService.uc index c444ce4..26b9d51 100644 --- a/sources/Unreal/UnrealService.uc +++ b/sources/Unreal/UnrealService.uc @@ -27,24 +27,27 @@ struct SignalRecord }; var private array serviceSignals; var private Unreal_OnTick_Signal onTickSignal; -var private AcediaGameRules gameRules; protected function OnLaunch() { - CreateSignals(); - // Create game rules - gameRules = AcediaGameRules(_.unreal.gameRules.Add(class'AcediaGameRules')); - if (gameRules != none) { - gameRules.Initialize(self); + local int i; + + onTickSignal = Unreal_OnTick_Signal( + _.memory.Allocate(class'Unreal_OnTick_Signal')); + for (i = 0; i < serviceSignals.length; i += 1) + { + if (serviceSignals[i].instance != none) continue; + if (serviceSignals[i].signalClass == none) continue; + + serviceSignals[i].instance = + Signal(_.memory.Allocate(serviceSignals[i].signalClass)); } } protected function OnShutdown() { local int i; - if (gameRules != none) { - gameRules.Cleanup(); - } + _.unreal.broadcasts.Remove(class'BroadcastEventsObserver'); _.unreal.gameRules.Remove(class'AcediaGameRules'); for (i = 0; i < serviceSignals.length; i += 1) { @@ -55,21 +58,6 @@ protected function OnShutdown() onTickSignal = none; } -private final function CreateSignals() -{ - local int i; - onTickSignal = Unreal_OnTick_Signal( - _.memory.Allocate(class'Unreal_OnTick_Signal')); - for (i = 0; i < serviceSignals.length; i += 1) - { - if (serviceSignals[i].instance != none) continue; - if (serviceSignals[i].signalClass == none) continue; - - serviceSignals[i].instance = - Signal(_.memory.Allocate(serviceSignals[i].signalClass)); - } -} - public final function Signal GetSignal(class requiredClass) { local int i;