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;