diff --git a/sources/Unreal/ActorStalker.uc b/sources/Unreal/ActorStalker.uc
new file mode 100644
index 0000000..ca15b83
--- /dev/null
+++ b/sources/Unreal/ActorStalker.uc
@@ -0,0 +1,90 @@
+/**
+ * An auxiliary actor class to help detect the exact moment when non-Acedia
+ * `Actor`s get destroyed. This is accomplished by attaching to them like to
+ * the base and then waiting for `BaseChange` event that will be called once
+ * our base gets destroyed.
+ * 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 ActorStalker extends AcediaActor;
+
+var private bool initialized;
+
+// Actor, whos destruction we want to detect
+var private Actor target;
+
+// To notify that stalked target got destroyed
+var private SimpleSignal onActorDestructionSignal;
+
+protected function Constructor()
+{
+ onActorDestructionSignal =
+ SimpleSignal(_.memory.Allocate(class'SimpleSignal'));
+}
+
+protected function Finalizer()
+{
+ _.memory.Free(onActorDestructionSignal);
+}
+
+/**
+ * Signal that will be emitted once stalked actor gets destroyed.
+ *
+ * [Signature]
+ * void ()
+ */
+/* SIGNAL */
+public final function SimpleSlot OnActorDestruction(AcediaObject receiver)
+{
+ return SimpleSlot(onActorDestructionSignal.NewSlot(receiver));
+}
+
+/**
+ * Initialized `ActorStalker` to stalk a particular target.
+ *
+ * Cannot fail unless `none` is passed as an argument.
+ *
+ * @param initTarget Target that new `ActorStalker` will stalk.
+ */
+public final function Initialize(Actor initTarget)
+{
+ if (initTarget == none) {
+ Destroy();
+ return;
+ }
+ target = initTarget;
+ SetBase(initTarget);
+ initialized = true;
+}
+
+event BaseChange()
+{
+ if (initialized && target == none)
+ {
+ onActorDestructionSignal.Emit();
+ Destroy();
+ }
+}
+
+defaultproperties
+{
+ RemoteRole = ROLE_None
+ drawType = DT_None
+ bCollideActors = false
+ bCollideWorld = false
+ bBlockActors = false
+}
\ No newline at end of file
diff --git a/sources/Unreal/UnrealAPI.uc b/sources/Unreal/UnrealAPI.uc
index 38d2d79..37e5877 100644
--- a/sources/Unreal/UnrealAPI.uc
+++ b/sources/Unreal/UnrealAPI.uc
@@ -22,7 +22,7 @@ class UnrealAPI extends AcediaObject;
var public GameRulesAPI gameRules;
-var private LoggerAPI.Definition errNoService;
+var private LoggerAPI.Definition fatalNoStalker;
protected function Constructor()
{
@@ -46,13 +46,43 @@ public final function Unreal_OnTick_Slot OnTick(
local Signal signal;
local UnrealService service;
service = UnrealService(class'UnrealService'.static.Require());
- if (service == none)
+ signal = service.GetSignal(class'Unreal_OnTick_Signal');
+ return Unreal_OnTick_Slot(signal.NewSlot(receiver));
+}
+
+/**
+ * Signal that will be emitted when a passed `targetToStalk` is destroyed.
+ *
+ * Passed parameter `targetToStalk` cannot be `none`, otherwise `none` will be
+ * returned instead of a valid slot.
+ *
+ * @param receiver Specify a receiver like for any other signal.
+ * @param targetToStalk Actor whose destruction we want to detect.
+ *
+ * [Signature]
+ * void ()
+ */
+/* SIGNAL */
+public final function SimpleSlot OnDestructionFor(
+ AcediaObject receiver,
+ Actor targetToStalk)
+{
+ local ActorStalker stalker;
+ if (receiver == none) return none;
+ if (targetToStalk == none) return none;
+
+ // Failing to spawn this actor without any collision flags is considered
+ // completely unexpected and grounds for fatal failure on Acedia' part
+ stalker = ActorStalker(_.memory.Allocate(class'ActorStalker'));
+ if (stalker == none)
{
- _.logger.Auto(errNoService);
+ _.logger.Auto(fatalNoStalker);
return none;
}
- signal = service.GetSignal(class'Unreal_OnTick_Signal');
- return Unreal_OnTick_Slot(signal.NewSlot(receiver));
+ // This will not fail, since we have already ensured that
+ // `targetToStalk == none`
+ stalker.Initialize(targetToStalk);
+ return stalker.OnActorDestruction(receiver);
}
/**
@@ -236,5 +266,5 @@ public final function NativeActorRef ActorRef(optional Actor value)
defaultproperties
{
- errNoService = (l=LOG_Error,m="`UnrealService` could not be reached.")
+ fatalNoStalker = (l=LOG_Fatal,m="Cannot spawn `PawnStalker`")
}
\ No newline at end of file