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