diff --git a/sources/Gameplay/BaseClasses/Frontend/World/AWorldComponent.uc b/sources/Gameplay/BaseClasses/Frontend/World/AWorldComponent.uc
index 78ac7ed..803155f 100644
--- a/sources/Gameplay/BaseClasses/Frontend/World/AWorldComponent.uc
+++ b/sources/Gameplay/BaseClasses/Frontend/World/AWorldComponent.uc
@@ -21,6 +21,56 @@
class AWorldComponent extends AcediaObject
abstract;
+/**
+ * Traces world for entities starting from `start` point and continuing into
+ * the direction `direction` for a "far distance". What is considered
+ * a "far distance" depends on the implementation (can potentially be infinite
+ * distance).
+ *
+ * @param start Point from which to start tracing.
+ * @param direction Direction alongside which to trace.
+ * @return `TracingIterator` that will iterate among `EPlaceable` interfaces
+ * for traced entities. Iteration is done in order from the entity closest
+ * to `start` point.
+ */
+public function TracingIterator Trace(Vector start, Rotator direction);
+
+/**
+ * Traces world for entities starting from `start` point and until `end` point.
+ *
+ * @param start Point from which to start tracing.
+ * @param end Point at which to stop tracing.
+ * @return `TracingIterator` that will iterate among `EPlaceable` interfaces
+ * for traced entities. Iteration is done in order from the entity closest
+ * to `start` point.
+ */
+public function TracingIterator TraceBetween(Vector start, Vector end);
+
+/**
+ * Traces world for entities starting from the `player`'s camera position and
+ * along the direction of his sight.
+ *
+ * This method works for both players with and without pawns. For player with
+ * a pawn it produce identical results to `TraceSight()` method.
+ *
+ * @param player Player alongside whos sight method is supposed to trace.
+ * @return `TracingIterator` that will iterate among `EPlaceable` interfaces
+ * for traced entities. Iteration is done in order from the entity closest
+ * to `player`'s camera location.
+ */
+public function TracingIterator TracePlayerSight(EPlayer player);
+
+/**
+ * Traces world for entities starting from the `pawn`'s camera position and
+ * along the direction of his sight.
+ *
+ * @param pawn Pawn alongside whos sight method is supposed to trace.
+ * @return `TracingIterator` that will iterate among `EPlaceable` interfaces
+ * for traced entities. Iteration is done in order from the entity closest
+ * to `pawn`'s eyes location.
+ */
+public function TracingIterator TraceSight(EPawn pawn);
+
defaultproperties
{
}
\ No newline at end of file
diff --git a/sources/Gameplay/BaseClasses/Frontend/World/TracingIterator.uc b/sources/Gameplay/BaseClasses/Frontend/World/TracingIterator.uc
new file mode 100644
index 0000000..ec01ddc
--- /dev/null
+++ b/sources/Gameplay/BaseClasses/Frontend/World/TracingIterator.uc
@@ -0,0 +1,108 @@
+/**
+ * Iterator for tracing entities inside the game world.
+ * 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 TracingIterator extends Iter
+ abstract;
+
+/**
+ * Returns only `EPlaceable` interfaces for traced entities.
+ *
+ * Resulting `EPlaceable` can refer to now non-existing entities if they were
+ * destroyed after the start of iteration.
+ */
+public function AcediaObject Get() { return none; }
+
+/**
+ * Returns hit location for the `EPlaceable` that `TracingIterator` is
+ * currently at.
+ *
+ * @return Hit location for the `EPlaceable` that `TracingIterator` is
+ * currently at. Origin vector (with all coordinates set to `0.0`) if
+ * iteration has already finished.
+ */
+public function Vector GetHitLocation()
+{
+ if (!initialized) {
+ return Vect(0.0f, 0.0f, 0.0f);
+ }
+ TryTracing();
+ if (HasFinished()) {
+ return Vect(0.0f, 0.0f, 0.0f);
+ }
+ return hitLocations[currentIndex];
+}
+
+/**
+ * Returns hit normal for the `EPlaceable` that `TracingIterator` is
+ * currently at.
+ *
+ * @return Hit normal for the `EPlaceable` that `TracingIterator` is
+ * currently at. Origin vector (with all coordinates set to `0.0`) if
+ * iteration has already finished.
+ */
+public function Vector GetHitNormal()
+{
+ if (!initialized) {
+ return Vect(0.0f, 0.0f, 0.0f);
+ }
+ TryTracing();
+ if (HasFinished()) {
+ return Vect(0.0f, 0.0f, 0.0f);
+ }
+ return hitNormals[currentIndex];
+}
+
+/**
+ * Returns `EPlaceable` caller `TracingIterator` is currently at.
+ * Guaranteed to be not `none` as long as iteration hasn't finished.
+ *
+ * Resulting `EPlaceable` can refer to now non-existing entities if they were
+ * destroyed after the start of iteration.
+ *
+ * @return `EPlaceable` caller `TracingIterator` is currently at.
+ */
+public function EPlaceable GetPlaceable()
+{
+ // We only create `EPlaceable` child classes in this class
+ return EPlaceable(Get());
+}
+
+/**
+ * Returns `EPlaceable` caller `TracingIterator` is currently at as `EPawn`,
+ * assuming that its entity support that interface.
+ *
+ * Resulting `EPawn` can refer to now non-existing entities if they were
+ * destroyed after the start of iteration.
+ *
+ * @return `EPawn` interface for `EPlaceable` that `Get()` would have returned.
+ * If `EPawn` is not supported by that `EPlaceable` - returns `none`.
+ */
+public function EPawn GetPawn();
+
+/**
+ * Makes caller iterator skip any entities that do not support `EPawn`
+ * interface during iteration.
+ *
+ * @return Reference to caller `TracingIterator` to allow for method chaining.
+ */
+public function TracingIterator LeaveOnlyPawns();
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFPawn.uc b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFPawn.uc
index 64b51c5..0479609 100644
--- a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFPawn.uc
+++ b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFPawn.uc
@@ -30,7 +30,7 @@ protected function Finalizer()
}
/**
- * Creates new `EKFPawn` that refers to the `pawnInstance` weapon.
+ * Creates new `EKFPawn` that refers to the `pawnInstance` pawn.
*
* @param pawnInstance Native pawn class that new `EKFPawn` will represent.
* @return New `EKFPawn` that represents given `pawnInstance`.
diff --git a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFUnknownPlaceable.uc b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFUnknownPlaceable.uc
new file mode 100644
index 0000000..4690172
--- /dev/null
+++ b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFUnknownPlaceable.uc
@@ -0,0 +1,125 @@
+/**
+ * Dummy implementation for `EPlaceable` interface that can wrap around
+ * `Actor` instances that Acedia does not know about - including the ones
+ * added by any other mods.
+ * 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 EKFUnknownPlaceable extends EPlaceable;
+
+var private NativeActorRef actorReference;
+
+protected function Finalizer()
+{
+ _.memory.Free(actorReference);
+ actorReference = none;
+}
+
+/**
+ * Creates new `EKFUnknownPlaceable` that refers to the `actorInstance` actor.
+ *
+ * @param actorInstance Native `Actor` class that new `EKFUnknownPlaceable`
+ * will represent.
+ * @return New `EKFUnknownPlaceable` that represents given `actorInstance`.
+ */
+public final static /*unreal*/ function EKFUnknownPlaceable Wrap(
+ Actor actorInstance)
+{
+ local EKFUnknownPlaceable newReference;
+
+ if (actorInstance == none) {
+ return none;
+ }
+ newReference = EKFUnknownPlaceable(
+ __().memory.Allocate(class'EKFUnknownPlaceable'));
+ newReference.actorReference = __().unreal.ActorRef(actorInstance);
+ return newReference;
+}
+
+/**
+ * Returns `Actor` instance represented by the caller `EKFUnknownPlaceable`.
+ *
+ * @return `Actor` instance represented by the caller `EKFUnknownPlaceable`.
+ */
+public final /*unreal*/ function Actor GetNativeInstance()
+{
+ if (actorReference != none) {
+ return actorReference.Get();
+ }
+ return none;
+}
+
+public function EInterface Copy()
+{
+ local Actor actorInstance;
+
+ actorInstance = GetNativeInstance();
+ return Wrap(actorInstance);
+}
+
+public function bool Supports(class newInterfaceClass)
+{
+ if (newInterfaceClass == none) return false;
+ if (newInterfaceClass == class'EPlaceable') return true;
+ if (newInterfaceClass == class'EKFUnknownPlaceable') return true;
+
+ return false;
+}
+
+public function EInterface As(class newInterfaceClass)
+{
+ if (!IsExistent()) {
+ return none;
+ }
+ if ( newInterfaceClass == class'EPlaceable'
+ || newInterfaceClass == class'EKFUnknownPlaceable')
+ {
+ return Copy();
+ }
+ return none;
+}
+
+public function bool IsExistent()
+{
+ return (GetNativeInstance() != none);
+}
+
+public function bool SameAs(EInterface other)
+{
+ local EKFUnknownPlaceable otherUnknown;
+
+ otherUnknown = EKFUnknownPlaceable(other);
+ if (otherUnknown == none) {
+ return false;
+ }
+ return (GetNativeInstance() == otherUnknown.GetNativeInstance());
+}
+
+public function Vector GetLocation()
+{
+ local Actor actorInstance;
+
+ actorInstance = GetNativeInstance();
+ if (actorInstance != none) {
+ return actorInstance.location;
+ }
+ return Vect(0.0, 0.0, 0.0);
+}
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/Gameplay/KF1Frontend/World/KF1_TracingIterator.uc b/sources/Gameplay/KF1Frontend/World/KF1_TracingIterator.uc
new file mode 100644
index 0000000..e504d5a
--- /dev/null
+++ b/sources/Gameplay/KF1Frontend/World/KF1_TracingIterator.uc
@@ -0,0 +1,195 @@
+/**
+ * `TracingIterator` implementation for `KF1_Frontend`.
+ * 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 KF1_TracingIterator extends TracingIterator;
+
+var private bool initialized;
+var private Vector startPosition, endPosition;
+
+var private int currentIndex;
+// Simply store all traced `Actor`s here at the moment of user first
+// interacting with iterator's items: when either `Next()` or one of
+// the `Get...()` methods were called.
+var private array tracedActors;
+// Store information about hit location and normal in the other arrays,
+// alongside `tracedActors`.
+var private array hitLocations, hitNormals;
+// Did we already perform tracing?
+var private bool traced;
+
+// Iterator filters
+var private bool onlyPawns;
+
+protected function Finalizer()
+{
+ _.memory.FreeMany(tracedActors);
+ tracedActors.length = 0;
+ initialized = false;
+}
+
+/**
+ * Initializes `TracingIterator` that traces entities between `start` and
+ * `end` positions, in order starting from the `start`
+ */
+public final function Initialize(Vector start, Vector end)
+{
+ if (initialized) {
+ return;
+ }
+ startPosition = start;
+ endPosition = end;
+ initialized = true;
+}
+
+// Does actual tracing, but only once per iterator's lifecycle.
+// Assumes `initialized` is `true`.
+private final function TryTracing()
+{
+ local Pawn nextPawn;
+ local Actor nextActor;
+ local class targetClass;
+ local ServerLevelCore core;
+ local Vector nextHitLocation, nextHitNormal;
+
+ // Checking `initialized` flag is already done by every method that
+ // calls `TryTracing()`
+ if (traced) {
+ return;
+ }
+ currentIndex = 0;
+ if (onlyPawns) {
+ targetClass = class'Pawn';
+ }
+ else {
+ targetClass = class'Actor';
+ }
+ core = ServerLevelCore(class'ServerLevelCore'.static.GetInstance());
+ foreach core.TraceActors(class'Actor',
+ nextActor,
+ nextHitLocation,
+ nextHitNormal,
+ endPosition,
+ startPosition)
+ {
+ hitLocations[hitLocations.length] = nextHitLocation;
+ hitNormals[hitNormals.length] = nextHitNormal;
+ nextPawn = Pawn(nextActor);
+ if (nextPawn != none)
+ {
+ tracedActors[tracedActors.length] =
+ class'EKFPawn'.static.Wrap(nextPawn);
+ }
+ else {
+ tracedActors[tracedActors.length] =
+ class'EKFUnknownPlaceable'.static.Wrap(nextActor);
+ }
+ }
+ traced = true;
+}
+
+public function Iter Next(optional bool skipNone)
+{
+ if (!initialized) {
+ return self;
+ }
+ TryTracing();
+ currentIndex += 1;
+ return self;
+}
+
+public function AcediaObject Get()
+{
+ if (!initialized) {
+ return none;
+ }
+ TryTracing();
+ if (HasFinished()) {
+ return none;
+ }
+ return tracedActors[currentIndex].NewRef();
+}
+
+public function Vector GetHitLocation()
+{
+ if (!initialized) {
+ return Vect(0.0f, 0.0f, 0.0f);
+ }
+ TryTracing();
+ if (HasFinished()) {
+ return Vect(0.0f, 0.0f, 0.0f);
+ }
+ return hitLocations[currentIndex];
+}
+
+public function Vector GetHitNormal()
+{
+ if (!initialized) {
+ return Vect(0.0f, 0.0f, 0.0f);
+ }
+ TryTracing();
+ if (HasFinished()) {
+ return Vect(0.0f, 0.0f, 0.0f);
+ }
+ return hitNormals[currentIndex];
+}
+
+public function EPlaceable GetPlaceable()
+{
+ // We only create `EPlaceable` child classes in this class
+ return EPlaceable(Get());
+}
+
+public function EPawn GetPawn()
+{
+ local AcediaObject result;
+ local EPawn pawnResult;
+
+ if (!initialized) {
+ return none;
+ }
+ result = Get();
+ pawnResult = EPawn(result);
+ if (pawnResult == none) {
+ _.memory.Free(result);
+ }
+ return pawnResult;
+}
+
+public function bool HasFinished()
+{
+ return (currentIndex >= tracedActors.length);
+}
+
+public function Iter LeaveOnlyNotNone()
+{
+ // We cannot tracer `none` actors, so no need to do anything
+ return self;
+}
+
+public function TracingIterator LeaveOnlyPawns()
+{
+ if (initialized && !traced) {
+ onlyPawns = true;
+ }
+ return self;
+}
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/Gameplay/KF1Frontend/World/KF1_WorldComponent.uc b/sources/Gameplay/KF1Frontend/World/KF1_WorldComponent.uc
new file mode 100644
index 0000000..25e600c
--- /dev/null
+++ b/sources/Gameplay/KF1Frontend/World/KF1_WorldComponent.uc
@@ -0,0 +1,90 @@
+/**
+ * `AWorldComponent`'s implementation for `KF1_Frontend`.
+ * 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 KF1_WorldComponent extends AWorldComponent
+ abstract;
+
+var private const float tracingDistance;
+
+public function TracingIterator Trace(Vector start, Rotator direction)
+{
+ local Vector end;
+
+ end = start + tracingDistance * Vector(direction);
+ return TraceBetween(start, end);
+}
+
+public function TracingIterator TraceBetween(Vector start, Vector end)
+{
+ local KF1_TracingIterator newIterator;
+
+ newIterator = KF1_TracingIterator(
+ _.memory.Allocate(class'KF1_TracingIterator'));
+ newIterator.Initialize(start, end);
+ return newIterator;
+}
+
+public function TracingIterator TracePlayerSight(EPlayer player)
+{
+ local Vector start;
+ local Rotator direction;
+ local Actor dummy;
+ local EPawn pawn;
+ local PlayerController controller;
+ local TracingIterator pawnTracingIterator;
+
+ if (player == none) {
+ return none;
+ }
+ pawn = player.GetPawn();
+ if (pawn != none)
+ {
+ pawnTracingIterator = TraceSight(pawn);
+ _.memory.Free(pawn);
+ return pawnTracingIterator;
+ }
+ controller = player.GetController();
+ if (controller != none)
+ {
+ controller.PlayerCalcView(dummy, start, direction);
+ return Trace(start, direction);
+ }
+ return none;
+}
+
+public function TracingIterator TraceSight(EPawn pawn)
+{
+ local EKFPawn kfPawn;
+ local Pawn nativePawn;
+ local Vector start, end;
+
+ kfPawn = EKFPawn(pawn);
+ if (kfPawn == none) return none;
+ nativePawn = kfPawn.GetNativeInstance();
+ if (nativePawn == none) return none;
+
+ start = nativePawn.location + nativePawn.EyePosition();
+ end = start + tracingDistance * Vector(nativePawn.rotation);
+ return TraceBetween(start, end);
+}
+
+defaultproperties
+{
+ tracingDistance = 10000
+}
\ No newline at end of file