diff --git a/sources/BaseRealm/Iter.uc b/sources/BaseRealm/Iter.uc
index ef21bdb..2092e22 100644
--- a/sources/BaseRealm/Iter.uc
+++ b/sources/BaseRealm/Iter.uc
@@ -21,6 +21,21 @@
class Iter extends AcediaObject
abstract;
+/**
+ * Iterators can filter objects they're iterating on by a presence or lack of
+ * a certain property, recording this choice requires 3 values, so `bool`
+ * isn't enough and we need to use this `enum` instead.
+ */
+enum IterFilter
+{
+ // We don't use relevant property for filtering
+ ITF_Nothing,
+ // Iterated objects must have that property
+ ITF_Have,
+ // Iterated objects must not have that property
+ ITF_NotHave
+};
+
/**
* Makes iterator pick next item.
* Use `HasFinished()` to check whether you have iterated all of them.
diff --git a/sources/Gameplay/BaseClasses/Frontend/World/EntityIterator.uc b/sources/Gameplay/BaseClasses/Frontend/World/EntityIterator.uc
index 1f8826e..5b442d2 100644
--- a/sources/Gameplay/BaseClasses/Frontend/World/EntityIterator.uc
+++ b/sources/Gameplay/BaseClasses/Frontend/World/EntityIterator.uc
@@ -60,13 +60,29 @@ public function EPawn GetPawn();
public function EntityIterator LeaveOnlyPawns();
/**
- * Makes caller iterator skip any entities that are not visible in the game
- * world.
+ * Makes caller iterator skip any entities that support `EPawn` interface
+ * during iteration.
+ *
+ * @return Reference to caller `EntityIterator` to allow for method chaining.
+ */
+public function EntityIterator LeaveOnlyNonPawns();
+
+/**
+ * Makes caller iterator skip any entities that are placeable (support
+ * `EPlaceable` interface) in the game.
*
* @return Reference to caller `EntityIterator` to allow for method chaining.
*/
public function EntityIterator LeaveOnlyPlaceables();
+/**
+ * Makes caller iterator skip any entities that are not placeable (don't
+ * support `EPlaceable` interface) into the game world.
+ *
+ * @return Reference to caller `EntityIterator` to allow for method chaining.
+ */
+public function EntityIterator LeaveOnlyNonPlaceables();
+
/**
* Makes caller iterator skip any entities that are not visible in the game
* world.
@@ -75,6 +91,83 @@ public function EntityIterator LeaveOnlyPlaceables();
*/
public function EntityIterator LeaveOnlyVisible();
+/**
+ * Makes caller iterator skip any entities that are visible in the game
+ * world.
+ *
+ * @return Reference to caller `EntityIterator` to allow for method chaining.
+ */
+public function EntityIterator LeaveOnlyInvisible();
+
+/**
+ * Makes caller iterator skip any entities that are able to collide with other
+ * entities in the game world.
+ *
+ * @return Reference to caller `EntityIterator` to allow for method chaining.
+ */
+public function EntityIterator LeaveOnlyColliding();
+
+/**
+ * Makes caller iterator skip any entities that are unable to collide with
+ * other entities in the game world.
+ *
+ * @return Reference to caller `EntityIterator` to allow for method chaining.
+ */
+public function EntityIterator LeaveOnlyNonColliding();
+
+/**
+ * Makes caller iterator skip any non-static entities that do not change over
+ * time, leaving only dynamic ones.
+ *
+ * @return Reference to caller `EntityIterator` to allow for method chaining.
+ */
+public function EntityIterator LeaveOnlyStatic();
+
+/**
+ * Makes caller iterator skip any static entities that do not change over time.
+ *
+ * @return Reference to caller `EntityIterator` to allow for method chaining.
+ */
+public function EntityIterator LeaveOnlyDynamic();
+
+/**
+ * Leaves only placeable entities that are located no further than `radius`
+ * distance from `placeable`.
+ *
+ * @see `LeaveOnlyNearbyToLocation()`
+ *
+ * @param placeable Interface to entity that iterated entities must be
+ * close to.
+ * @param radius Maximum distance that entities are allowed to be away
+ * from `location`.
+ * @return Reference to caller `EntityIterator` to allow for method chaining.
+ */
+public function EntityIterator LeaveOnlyNearby(
+ EPlaceable placeable,
+ float radius);
+
+/**
+ * Leaves only placeable entities that are located no further than `radius`
+ * distance from `location`.
+ *
+ * @see `LeaveOnlyNearby()`
+ *
+ * @param location Location to which entities must be close to.
+ * @param radius Maximum distance that entities are allowed to be away
+ * from `location`.
+ * @return Reference to caller `EntityIterator` to allow for method chaining.
+ */
+public function EntityIterator LeaveOnlyNearbyToLocation(
+ Vector location,
+ float radius);
+
+/**
+ * Leaves only placeable entities that are touching `placeable`.
+ *
+ * `placeable` must have collisions enabled for any entity to touch it.
+ */
+public function EntityIterator LeaveOnlyTouching(EPlaceable placeable);
+
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
index 90b0626..29f67d7 100644
--- a/sources/Gameplay/BaseClasses/Frontend/World/TracingIterator.uc
+++ b/sources/Gameplay/BaseClasses/Frontend/World/TracingIterator.uc
@@ -17,13 +17,13 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see .
*/
-class TracingIterator extends Iter
+class TracingIterator extends EntityIterator
abstract;
/**
* Returns position from which tracing is started.
*
- * @return Position from which stracing has started.
+ * @return Position from which tracing has started.
*/
public function Vector GetTracingStart();
@@ -34,14 +34,6 @@ public function Vector GetTracingStart();
*/
public function Vector GetTracingEnd();
-/**
- * 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.
@@ -62,45 +54,6 @@ public function Vector GetHitLocation();
*/
public function Vector GetHitNormal();
-/**
- * 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();
-
-/**
- * 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();
-
-/**
- * Makes caller iterator skip any entities that are not visible in the game
- * world.
- *
- * @return Reference to caller `TracingIterator` to allow for method chaining.
- */
-public function TracingIterator LeaveOnlyVisible();
-
defaultproperties
{
}
\ No newline at end of file
diff --git a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFPawn.uc b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFPawn.uc
index 2f96c57..67cb5a8 100644
--- a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFPawn.uc
+++ b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFPawn.uc
@@ -59,7 +59,6 @@ public function bool Supports(class newInterfaceClass)
{
if (newInterfaceClass == none) return false;
if (newInterfaceClass == class'EPlaceable') return true;
- if (newInterfaceClass == class'EKFPlaceable') return true;
if (newInterfaceClass == class'EKFPawn') return true;
return false;
@@ -71,7 +70,6 @@ public function EInterface As(class newInterfaceClass)
return none;
}
if ( newInterfaceClass == class'EPlaceable'
- || newInterfaceClass == class'EKFPlaceable'
|| newInterfaceClass == class'EKFPawn')
{
return Copy();
diff --git a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFPlaceable.uc b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFPlaceable.uc
deleted file mode 100644
index 19dd984..0000000
--- a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFPlaceable.uc
+++ /dev/null
@@ -1,202 +0,0 @@
-/**
- * Implementation of `EPlaceable` for classic Killing Floor weapons that
- * changes as little as possible and only on request from another mod,
- * otherwise not altering gameplay at all.
- * 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 EKFPlaceable extends EPlaceable
- abstract;
-
-var private NativeActorRef actorReference;
-
-protected function Finalizer()
-{
- _.memory.Free(actorReference);
- actorReference = none;
-}
-
-/**
- * Creates new `EKFPlaceable` that refers to the `actorInstance` pawn.
- *
- * @param actorInstance Native actor class that new `EKFPlaceable` will
- * represent.
- * @return New `EKFPlaceable` that represents given `actorInstance`.
- */
-public final static /*unreal*/ function EKFPlaceable Wrap(Actor actorInstance)
-{
- local EKFPlaceable newReference;
-
- if (actorInstance == none) {
- return none;
- }
- newReference = EKFPlaceable(__().memory.Allocate(class'EKFPlaceable'));
- newReference.actorReference = __server().unreal.ActorRef(actorInstance);
- return newReference;
-}
-
-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'EKFPlaceable') return true;
-
- if (newInterfaceClass == class'EKFPawn') {
- return (Pawn(GetNativeInstance()) != none);
- }
- return false;
-}
-
-public function EInterface As(class newInterfaceClass)
-{
- local Pawn pawnInstance;
-
- if (!IsExistent()) {
- return none;
- }
- if ( newInterfaceClass == class'EPlaceable'
- || newInterfaceClass == class'EKFPlaceable')
- {
- return Copy();
- }
- if ( newInterfaceClass == class'EPawn'
- || newInterfaceClass == class'EKFPawn')
- {
- pawnInstance = Pawn(GetNativeInstance());
- if (pawnInstance != none) {
- return class'EKFPawn'.static.Wrap(pawnInstance);
- }
- }
- return none;
-}
-
-public function bool IsExistent()
-{
- return (GetNativeInstance() != none);
-}
-
-public function bool SameAs(EInterface other)
-{
- local EKFPlaceable otherPlaceable;
-
- otherPlaceable = EKFPlaceable(other);
- if (otherPlaceable == none) {
- return false;
- }
- return (GetNativeInstance() == otherPlaceable.GetNativeInstance());
-}
-
-/**
- * Returns `Pawn` instance represented by the caller `EKFPlaceable`.
- *
- * @return `Pawn` instance represented by the caller `EKFPlaceable`.
- */
-public final /*unreal*/ function Actor GetNativeInstance()
-{
- if (actorReference != none) {
- return actorReference.Get();
- }
- return none;
-}
-
-public function Vector GetLocation()
-{
- local Actor actorInstance;
-
- actorInstance = GetNativeInstance();
- if (actorInstance != none) {
- return actorInstance.location;
- }
- return Vect(0.0, 0.0, 0.0);
-}
-
-public function Rotator GetRotation()
-{
- local Actor actorInstance;
-
- actorInstance = GetNativeInstance();
- if (actorInstance != none) {
- return actorInstance.rotation;
- }
- return Rot(0.0, 0.0, 0.0);
-}
-
-public function bool IsStatic()
-{
- local Actor actorInstance;
-
- actorInstance = GetNativeInstance();
- if (actorInstance != none) {
- return actorInstance.bStatic;
- }
- return false;
-}
-
-public function bool IsColliding()
-{
- local Actor actorInstance;
-
- actorInstance = GetNativeInstance();
- if (actorInstance != none) {
- return actorInstance.bCollideActors;
- }
- return false;
-}
-
-public function bool IsBlocking()
-{
- local Actor actorInstance;
-
- actorInstance = GetNativeInstance();
- if (actorInstance != none) {
- return actorInstance.bBlockActors;
- }
- return false;
-}
-
-public function SetBlocking(bool newBlocking)
-{
- local Actor actorInstance;
-
- actorInstance = GetNativeInstance();
- if (actorInstance != none) {
- actorInstance.bBlockActors = newBlocking;
- }
-}
-
-public function bool IsVisible()
-{
- local Actor actorInstance;
-
- actorInstance = GetNativeInstance();
- if (actorInstance != none) {
- return (!actorInstance.bHidden && actorInstance.drawType != DT_None);
- }
- return false;
-}
-
-defaultproperties
-{
-}
\ No newline at end of file
diff --git a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFUnknownPlaceable.uc b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFUnknownPlaceable.uc
index 859634f..1d88923 100644
--- a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFUnknownPlaceable.uc
+++ b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFUnknownPlaceable.uc
@@ -30,9 +30,9 @@ protected function Finalizer()
}
/**
- * Creates new `EKFUnknownPlaceable` that refers to the `actorInstance` actor.
+ * Creates new `EKFUnknownPlaceable` that refers to the `actorInstance` pawn.
*
- * @param actorInstance Native `Actor` class that new `EKFUnknownPlaceable`
+ * @param actorInstance Native actor class that new `EKFUnknownPlaceable`
* will represent.
* @return New `EKFUnknownPlaceable` that represents given `actorInstance`.
*/
@@ -44,25 +44,12 @@ public final static /*unreal*/ function EKFUnknownPlaceable Wrap(
if (actorInstance == none) {
return none;
}
- newReference = EKFUnknownPlaceable(
- __().memory.Allocate(class'EKFUnknownPlaceable'));
+ newReference =
+ EKFUnknownPlaceable(__().memory.Allocate(class'EKFUnknownPlaceable'));
newReference.actorReference = __server().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;
@@ -73,15 +60,20 @@ public function EInterface Copy()
public function bool Supports(class newInterfaceClass)
{
- if (newInterfaceClass == none) return false;
- if (newInterfaceClass == class'EPlaceable') return true;
- if (newInterfaceClass == class'EKFUnknownPlaceable') return true;
+ if (newInterfaceClass == none) return false;
+ if (newInterfaceClass == class'EPlaceable') return true;
+ if (newInterfaceClass == class'EKFUnknownPlaceable') return true;
+ if (newInterfaceClass == class'EKFPawn') {
+ return (Pawn(GetNativeInstance()) != none);
+ }
return false;
}
public function EInterface As(class newInterfaceClass)
{
+ local Pawn pawnInstance;
+
if (!IsExistent()) {
return none;
}
@@ -90,6 +82,14 @@ public function EInterface As(class newInterfaceClass)
{
return Copy();
}
+ if ( newInterfaceClass == class'EPawn'
+ || newInterfaceClass == class'EKFPawn')
+ {
+ pawnInstance = Pawn(GetNativeInstance());
+ if (pawnInstance != none) {
+ return class'EKFPawn'.static.Wrap(pawnInstance);
+ }
+ }
return none;
}
@@ -100,13 +100,26 @@ public function bool IsExistent()
public function bool SameAs(EInterface other)
{
- local EKFUnknownPlaceable otherUnknown;
+ local EKFUnknownPlaceable otherPlaceable;
- otherUnknown = EKFUnknownPlaceable(other);
- if (otherUnknown == none) {
+ otherPlaceable = EKFUnknownPlaceable(other);
+ if (otherPlaceable == none) {
return false;
}
- return (GetNativeInstance() == otherUnknown.GetNativeInstance());
+ return (GetNativeInstance() == otherPlaceable.GetNativeInstance());
+}
+
+/**
+ * Returns `Pawn` instance represented by the caller `EKFUnknownPlaceable`.
+ *
+ * @return `Pawn` instance represented by the caller `EKFUnknownPlaceable`.
+ */
+public final /*unreal*/ function Actor GetNativeInstance()
+{
+ if (actorReference != none) {
+ return actorReference.Get();
+ }
+ return none;
}
public function Vector GetLocation()
@@ -120,6 +133,71 @@ public function Vector GetLocation()
return Vect(0.0, 0.0, 0.0);
}
+public function Rotator GetRotation()
+{
+ local Actor actorInstance;
+
+ actorInstance = GetNativeInstance();
+ if (actorInstance != none) {
+ return actorInstance.rotation;
+ }
+ return Rot(0.0, 0.0, 0.0);
+}
+
+public function bool IsStatic()
+{
+ local Actor actorInstance;
+
+ actorInstance = GetNativeInstance();
+ if (actorInstance != none) {
+ return actorInstance.bStatic;
+ }
+ return false;
+}
+
+public function bool IsColliding()
+{
+ local Actor actorInstance;
+
+ actorInstance = GetNativeInstance();
+ if (actorInstance != none) {
+ return actorInstance.bCollideActors;
+ }
+ return false;
+}
+
+public function bool IsBlocking()
+{
+ local Actor actorInstance;
+
+ actorInstance = GetNativeInstance();
+ if (actorInstance != none) {
+ return actorInstance.bBlockActors;
+ }
+ return false;
+}
+
+public function SetBlocking(bool newBlocking)
+{
+ local Actor actorInstance;
+
+ actorInstance = GetNativeInstance();
+ if (actorInstance != none) {
+ actorInstance.bBlockActors = newBlocking;
+ }
+}
+
+public function bool IsVisible()
+{
+ local Actor actorInstance;
+
+ actorInstance = GetNativeInstance();
+ if (actorInstance != none) {
+ return (!actorInstance.bHidden && actorInstance.drawType != DT_None);
+ }
+ return false;
+}
+
defaultproperties
{
}
\ No newline at end of file
diff --git a/sources/Gameplay/KF1Frontend/World/KF1_EntityIterator.uc b/sources/Gameplay/KF1Frontend/World/KF1_EntityIterator.uc
deleted file mode 100644
index 00962f2..0000000
--- a/sources/Gameplay/KF1Frontend/World/KF1_EntityIterator.uc
+++ /dev/null
@@ -1,184 +0,0 @@
-/**
- * `EntityIterator` 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_EntityIterator extends EntityIterator;
-
-var private bool initialized;
-
-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 foundActors;
-// Did we already perform iteration through actors?
-var private bool iterated;
-
-// Iterator filters
-var private bool onlyPawns;
-var private bool onlyPlaceables;
-var private bool onlyVisible;
-
-protected function Finalizer()
-{
- _.memory.FreeMany(foundActors);
- foundActors.length = 0;
- initialized = false;
-}
-
-/**
- * Initializes `TracingIterator` that traces entities between `start` and
- * `end` positions, in order starting from the `start`
- */
-public final function Initialize()
-{
- initialized = true;
-}
-
-private final function bool IsActorVisible(Actor actorToCheck)
-{
- if (actorToCheck == none) return false;
- if (actorToCheck.bHidden && !actorToCheck.bWorldGeometry) return false;
- if (actorToCheck.drawType == DT_None) return false;
-
- return true;
-}
-
-// Does actual tracing, but only once per iterator's lifecycle.
-// Assumes `initialized` is `true`.
-private final function TryIterating()
-{
- local Pawn nextPawn;
- local Actor nextActor;
- local class targetClass;
- local ServerLevelCore core;
-
- // Checking `initialized` flag is already done by every method that
- // calls `TryTracing()`
- if (iterated) {
- return;
- }
- currentIndex = 0;
- if (onlyPawns) {
- targetClass = class'Pawn';
- }
- else {
- targetClass = class'Actor';
- }
- core = ServerLevelCore(class'ServerLevelCore'.static.GetInstance());
- // TODO: We should not always use slow `AllActors()` method
- foreach core.AllActors(targetClass, nextActor)
- {
- if (onlyVisible && !IsActorVisible(nextActor)) {
- continue;
- }
- nextPawn = Pawn(nextActor);
- if (nextPawn != none)
- {
- foundActors[foundActors.length] =
- class'EKFPawn'.static.Wrap(nextPawn);
- }
- else {
- foundActors[foundActors.length] =
- class'EKFUnknownPlaceable'.static.Wrap(nextActor);
- }
- }
- iterated = true;
-}
-
-public function Iter Next()
-{
- if (!initialized) {
- return self;
- }
- TryIterating();
- currentIndex += 1;
- return self;
-}
-
-public function AcediaObject Get()
-{
- if (!initialized) {
- return none;
- }
- TryIterating();
- if (HasFinished()) {
- return none;
- }
- return foundActors[currentIndex].NewRef();
-}
-
-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()
-{
- TryIterating();
- return (currentIndex >= foundActors.length);
-}
-
-public function Iter LeaveOnlyNotNone()
-{
- // We cannot tracer `none` actors, so no need to do anything
- return self;
-}
-
-public function EntityIterator LeaveOnlyPawns()
-{
- if (initialized && !iterated) {
- onlyPawns = true;
- }
- return self;
-}
-
-public function EntityIterator LeaveOnlyPlaceables() {
- // Doesn't do anything for now
- // TODO: make it actually do something
- return self;
-}
-
-public function EntityIterator LeaveOnlyVisible()
-{
- if (initialized && !iterated) {
- onlyVisible = true;
- }
- return self;
-}
-
-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
index 7767e99..a20cdc5 100644
--- a/sources/Gameplay/KF1Frontend/World/KF1_TracingIterator.uc
+++ b/sources/Gameplay/KF1Frontend/World/KF1_TracingIterator.uc
@@ -1,5 +1,7 @@
/**
- * `TracingIterator` implementation for `KF1_Frontend`.
+ * `EntityIterator` / `TracingIterator` implementation for `KF1_Frontend`.
+ * Both iterators do essentially the same and can be implemented with
+ * a single class.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
@@ -19,43 +21,150 @@
*/
class KF1_TracingIterator extends TracingIterator;
-var private bool initialized;
-var private Vector startPosition, endPosition;
+/**
+ * # `KF1_TracingIterator`
+ *
+ * This iterator class implements both `EntityIterator` and
+ * `TracingIterator` for default Acedia implementation. It would've been better
+ * to have two separate classes in case someone tries to use `EntityIterator`
+ * as if it was `TracingIterator`, but that's highly unlikely and typical use
+ * should be unaffected.
+ *
+ * ## Implementation
+ *
+ * `KF1_TracingIterator` collects information about what filters user wants
+ * to apply to entities to be iterated over and, when user tries to access
+ * these entities in any way, it calls `TryIterating()` that decides whether
+ * actual `Actor` lookup is even needed, sets up necessary variables and calls
+ * `DoIterate_PickBest()` method that picks the best (fastest) method for
+ * iterating over `Actor`s based on the limitations given by the user.
+ * In case several entities were specified with `LeaveOnlyTouching()`
+ * method (or it was only one entity, but we're performing tracing), then
+ * several `Actor.TouchingActors()` lookups will be performed to find out which
+ * actors are touching all specified entities.
+ */
+var private bool initialized;
+var private bool tracingIterator;
+// If two contradictory conditions were specified, iterator becomes empty
+// and doesn't bother doing any work
+var private bool emptyIterator;
+// (For tracing iteration only)
+var private Vector startPosition, endPosition;
-var private int currentIndex;
+// Did we already perform iteration through actors?
+var private bool iterated;
// 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;
+var private array foundActors;
+// Index we're at in `foundActors`, advanced by calling `Next()`
+var private int currentIndex;
+// (For tracing iteration only)
// Store information about hit location and normal in the other arrays,
-// alongside `tracedActors`.
+// alongside `foundActors`.
var private array hitLocations, hitNormals;
-// Did we already perform tracing?
-var private bool traced;
// Iterator filters
-var private bool onlyPawns;
-var private bool onlyVisible;
+var private IterFilter pawnsFilter;
+var private IterFilter placeablesFilter;
+var private IterFilter collidingFilter;
+var private IterFilter visibleFilter;
+var private IterFilter staticFilter;
+
+/**
+ * Describes limitations provided to iterator by `LeaveOnlyNearby()` or
+ * `LeaveOnlyNearbyToLocation()`.
+ */
+struct NearbyLimitation
+{
+ // If this isn't `none` - use its location
+ var EPlaceable placeable;
+ // If `location` is `none` - use this vector instead
+ var Vector location;
+ // Distance entities must be away from `placeable`
+ var float distance;
+ // Squared `distance` to make distance checks faster
+ var float distanceSquared;
+};
+// Should we even do distance checks?
+var private bool distanceCheck;
+// Store the limitation with the shortest `distance` filed here.
+// Native iterators only take one location into an account, so we pick
+// the one most likely to filter majority of the entities to make native code
+// do most of the work.
+var private NearbyLimitation shortestLimitation;
+var private array otherDistanceLimitations;
+
+var private bool touchingCheck;
+var private array touchers;
+// `Actor` from first existing `EPlaceable`, picked at the moment of building
+// `foundActors` to be used as a source for `TouchingActors`
+// We store it directly as `Actor`, so outside of iteration code it is only
+// allowed to be set to `none` to avoid crashes
+var private Actor mainToucher;
protected function Finalizer()
{
- _.memory.FreeMany(tracedActors);
- tracedActors.length = 0;
- initialized = false;
+ local int i;
+
+ currentIndex = 0;
+ initialized = false;
+ iterated = false;
+ // Clear conditions
+ // ~ Simple conditions
+ pawnsFilter = ITF_Nothing;
+ placeablesFilter = ITF_Nothing;
+ collidingFilter = ITF_Nothing;
+ visibleFilter = ITF_Nothing;
+ staticFilter = ITF_Nothing;
+ // ~ Distance conditions
+ distanceCheck = false;
+ _.memory.Free(shortestLimitation.placeable);
+ shortestLimitation.placeable = none;
+ for (i = 0; i < otherDistanceLimitations.length; i += 1) {
+ _.memory.Free(otherDistanceLimitations[i].placeable);
+ }
+ otherDistanceLimitations.length = 0;
+ // ~ Touching conditions
+ touchingCheck = false;
+ _.memory.FreeMany(touchers);
+ touchers.length = 0;
+ mainToucher = none;
+ // Clear iterated actors
+ _.memory.FreeMany(foundActors);
+ foundActors.length = 0;
+ hitLocations.length = 0;
+ hitNormals.length = 0;
+}
+
+/**
+ * Initializes iterator to iterate over entities in the game world without
+ * tracing limitations.
+ */
+public final function Initialize()
+{
+ if (initialized) {
+ return;
+ }
+ initialized = true;
+ tracingIterator = false;
}
/**
- * Initializes `TracingIterator` that traces entities between `start` and
- * `end` positions, in order starting from the `start`
+ * Initializes iterator for entities that can be traced between `start` and
+ * `end` positions, in order starting from the `start`.
+ * Will iterate only over colliding entities.
*/
-public final function Initialize(Vector start, Vector end)
+public final function InitializeTracing(Vector start, Vector end)
{
if (initialized) {
return;
}
- startPosition = start;
- endPosition = end;
- initialized = true;
+ startPosition = start;
+ endPosition = end;
+ collidingFilter = ITF_Have;
+ initialized = true;
+ tracingIterator = true;
}
public function Vector GetTracingStart()
@@ -77,29 +186,302 @@ private final function bool IsActorVisible(Actor actorToCheck)
return true;
}
+private final function bool IsAllowed(Actor nextActor, array allowedList)
+{
+ local int i;
+
+ if (nextActor == none) {
+ return false;
+ }
+ for (i = 0; i < allowedList.length; i += 1)
+ {
+ if (nextActor == allowedList[i]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+private final function bool IsCloseEnough(Actor nextActor)
+{
+ local int i;
+ local Vector nextLocation;
+
+ for (i = 0; i < otherDistanceLimitations.length; i += 1)
+ {
+ if (otherDistanceLimitations[i].placeable != none) {
+ nextLocation = otherDistanceLimitations[i].placeable.GetLocation();
+ }
+ else {
+ nextLocation = otherDistanceLimitations[i].location;
+ }
+ if ( VSizeSquared(nextActor.location - nextLocation)
+ > otherDistanceLimitations[i].distanceSquared)
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Performs specified filter checks on the next `Actor` and adds it to
+// `foundActors` array in case it passes all.
+// If `allowedList` is empty, it is assumed that there is no limitations.
+private final function bool ProcessActor(
+ Actor nextActor,
+ array allowedList)
+{
+ local bool isVisible, isCollidable;
+ local Pawn nextPawn;
+
+ if (allowedList.length > 0 && IsAllowed(nextActor, allowedList)) {
+ return false;
+ }
+ if (staticFilter == ITF_Have && !nextActor.bStatic) {
+ return false;
+ }
+ if (staticFilter == ITF_NotHave && nextActor.bStatic) {
+ return false;
+ }
+ isCollidable = (nextActor.bCollideActors || (LevelInfo(nextActor) != none));
+ if (collidingFilter == ITF_Have && !isCollidable) {
+ return false;
+ }
+ if (collidingFilter == ITF_NotHave && isCollidable) {
+ return false;
+ }
+ if (placeablesFilter == ITF_Have && nextActor.bWorldGeometry) {
+ return false;
+ }
+ if (placeablesFilter == ITF_NotHave && !nextActor.bWorldGeometry) {
+ return false;
+ }
+ isVisible = IsActorVisible(nextActor);
+ if (visibleFilter == ITF_Have && !isVisible) {
+ return false;
+ }
+ if (visibleFilter == ITF_NotHave && isVisible) {
+ return false;
+ }
+ if (distanceCheck && !IsCloseEnough(nextActor)) {
+ return false;
+ }
+ nextPawn = Pawn(nextActor);
+ if (nextPawn != none)
+ {
+ foundActors[foundActors.length] =
+ class'EKFPawn'.static.Wrap(nextPawn);
+ }
+ else {
+ foundActors[foundActors.length] =
+ class'EKFUnknownPlaceable'.static.Wrap(nextActor);
+ }
+ return true;
+}
+
+private final function array GetActorsToTouch(
+ class targetClass)
+{
+ local int i;
+ local Actor nextActor;
+ local EKFPawn asPawn;
+ local EKFUnknownPlaceable asUnknown;
+ local array actorsToTouch;
+
+ if (!touchingCheck) {
+ return actorsToTouch;
+ }
+ for (i = 0; i < touchers.length; i += 1)
+ {
+ // Get native instance
+ asPawn = EKFPawn(touchers[i]);
+ asUnknown = EKFUnknownPlaceable(touchers[i]);
+ if (asPawn != none) {
+ nextActor = asPawn.GetNativeInstance();
+ }
+ if (nextActor == none && asUnknown != none) {
+ nextActor = asUnknown.GetNativeInstance();
+ }
+ if (nextActor == none) {
+ continue;
+ }
+ // Setup `mainToucher` / `actorsToTouch`
+ if (!tracingIterator && mainToucher == none) {
+ mainToucher = nextActor;
+ }
+ else {
+ actorsToTouch[actorsToTouch.length] = nextActor;
+ }
+ }
+ return actorsToTouch;
+}
+
+// Calculates allow list based on additional `Actor`s that must be touched
+// by entities of interest.
+// Basically we call `TouchingActors()` for each of them and then take
+// intersection.
+private final function array GetAllowList(
+ class targetClass,
+ array actorsToTouch)
+{
+ local int i, j;
+ local Actor nextActor;
+ local int requiredTouchesAmount;
+ local array touchesAmount;
+ local array allowedList;
+
+ if (actorsToTouch.length <= 0) {
+ return allowedList;
+ }
+ // First get all actors touching the first one in array;
+ // This can be seen as intersection with all `Actors` in the game world.
+ foreach actorsToTouch[0].TouchingActors(targetClass, nextActor)
+ {
+ allowedList[allowedList.length] = nextActor;
+ touchesAmount[touchesAmount.length] = 0;
+ }
+ // Then for all `Actor`s we've found, count how many others from
+ // `actorsToTouch` they are touching
+ for (i = 1; i < actorsToTouch.length; i += 1)
+ {
+ foreach actorsToTouch[i].TouchingActors(targetClass, nextActor)
+ {
+ for (j = 0; j < allowedList.length; j += 1)
+ {
+ if (allowedList[j] == nextActor)
+ {
+ touchesAmount[j] += 1;
+ break;
+ }
+ }
+ }
+ }
+ // Actors remaining in `allowedList` must touch all actors from
+ // `actorsToTouch`.
+ // First one they touch by the way they were constructed, so we just
+ // need to ensure they touch `actorsToTouch.length - 1` of other `Actor`s
+ // from `actorsToTouch`
+ i = 0;
+ requiredTouchesAmount = actorsToTouch.length - 1;
+ while (i < touchesAmount.length)
+ {
+ if (touchesAmount[i] != requiredTouchesAmount)
+ {
+ touchesAmount.Remove(i, 1);
+ allowedList.Remove(i, 1);
+ }
+ else {
+ i += 1;
+ }
+ }
+ return allowedList;
+}
+
// Does actual tracing, but only once per iterator's lifecycle.
// Assumes `initialized` is `true`.
-private final function TryTracing()
+private final function TryIterating()
{
- local Pawn nextPawn;
- local Actor nextActor;
- local class targetClass;
local ServerLevelCore core;
- local Vector nextHitLocation, nextHitNormal;
+ local class targetClass;
+ local array actorsToTouch;
+ local array allowedActors;
- // Checking `initialized` flag is already done by every method that
- // calls `TryTracing()`
- if (traced) {
+ if (iterated) {
return;
}
- currentIndex = 0;
- if (onlyPawns) {
- targetClass = class'Pawn';
+ if (emptyIterator)
+ {
+ iterated = true;
+ return;
+ }
+ core = ServerLevelCore(class'ServerLevelCore'.static.GetInstance());
+ if (pawnsFilter == ITF_Have) {
+ targetClass = class'Pawn';
}
else {
- targetClass = class'Actor';
+ targetClass = class'Actor';
}
- core = ServerLevelCore(class'ServerLevelCore'.static.GetInstance());
+ actorsToTouch = GetActorsToTouch(targetClass);
+ if (actorsToTouch.length > 0)
+ {
+ allowedActors = GetAllowList(targetClass, actorsToTouch);
+ // If no actors are allowed - no need to iterate further,
+ // result of this iterator is an empty collection
+ if (allowedActors.length <= 0)
+ {
+ iterated = true;
+ return;
+ }
+ }
+ DoIterate_PickBest(core, targetClass, allowedActors);
+ iterated = true;
+}
+
+// For iterations where we don't use `shortestLimitation`'s data in native
+// iterator, so we have to do that check manually
+private final function MergeShortestDistanceLimitationIntoOthers()
+{
+ otherDistanceLimitations[otherDistanceLimitations.length] =
+ shortestLimitation;
+ shortestLimitation.placeable = none;
+}
+
+// Empty `allowedActors` here means no limitations
+private final function DoIterate_PickBest(
+ LevelCore core,
+ class targetClass,
+ array allowedActors)
+{
+ // If tracing is required - there is no choice, but to use tracing iterator
+ if (tracingIterator)
+ {
+ MergeShortestDistanceLimitationIntoOthers();
+ DoIterate_Trace(core, targetClass, allowedActors);
+ mainToucher = none;
+ return;
+ }
+ // Limiting iteration to touching actors is probably the fastest
+ if (touchingCheck)
+ {
+ MergeShortestDistanceLimitationIntoOthers();
+ DoIterate_Touching(targetClass, allowedActors);
+ mainToucher = none;
+ return;
+ }
+ // There is no need to have `mainToucher` in the code below, since if touch
+ // limitations were specified, it is needed either tracing or
+ // `DoIterate_Touching()`
+ mainToucher = none;
+ // Otherwise limiting iteration to colliding actors is always
+ // preferable, but only doable if we're also filtering by distance.
+ if (distanceCheck)
+ {
+ if (collidingFilter == ITF_Have) {
+ DoIterate_Colliding(core, targetClass);
+ }
+ else {
+ // Otherwise `RadiusActors` is still better than nothing
+ DoIterate_Radius(core, targetClass);
+ }
+ return;
+ }
+ // If above fails - try to at least limit iteration to dynamic actors
+ if (staticFilter == ITF_NotHave)
+ {
+ DoIterate_Dynamic(core, targetClass);
+ return;
+ }
+ DoIterate_All(core, targetClass);
+}
+
+private final function DoIterate_Trace(
+ LevelCore core,
+ class targetClass,
+ array allowedActors)
+{
+ local Actor nextActor;
+ local Vector nextHitLocation, nextHitNormal;
+
foreach core.TraceActors(targetClass,
nextActor,
nextHitLocation,
@@ -107,23 +489,96 @@ private final function TryTracing()
endPosition,
startPosition)
{
- if (onlyVisible && !IsActorVisible(nextActor)) {
- continue;
- }
- hitLocations[hitLocations.length] = nextHitLocation;
- hitNormals[hitNormals.length] = nextHitNormal;
- nextPawn = Pawn(nextActor);
- if (nextPawn != none)
+
+ if (ProcessActor(nextActor, allowedActors))
{
- tracedActors[tracedActors.length] =
- class'EKFPawn'.static.Wrap(nextPawn);
- }
- else {
- tracedActors[tracedActors.length] =
- class'EKFUnknownPlaceable'.static.Wrap(nextActor);
+ hitLocations[hitLocations.length] = nextHitLocation;
+ hitNormals[hitNormals.length] = nextHitNormal;
}
}
- traced = true;
+}
+
+private final function DoIterate_Touching(
+ class targetClass,
+ array allowedActors)
+{
+ local Actor nextActor;
+
+ foreach mainToucher.TouchingActors(targetClass, nextActor) {
+ ProcessActor(nextActor, allowedActors);
+ }
+}
+
+private final function DoIterate_Colliding(
+ LevelCore core,
+ class targetClass)
+{
+ local Actor nextActor;
+ local Vector location;
+ local array emptyActorArray;
+
+ if (shortestLimitation.placeable != none) {
+ location = shortestLimitation.placeable.GetLocation();
+ }
+ else {
+ location = shortestLimitation.location;
+ }
+ foreach core.CollidingActors(
+ targetClass,
+ nextActor,
+ shortestLimitation.distance,
+ location)
+ {
+ ProcessActor(nextActor, emptyActorArray);
+ }
+}
+
+private final function DoIterate_Radius(
+ LevelCore core,
+ class targetClass)
+{
+ local Actor nextActor;
+ local Vector location;
+ local array emptyActorArray;
+
+ if (shortestLimitation.placeable != none) {
+ location = shortestLimitation.placeable.GetLocation();
+ }
+ else {
+ location = shortestLimitation.location;
+ }
+ foreach core.RadiusActors(
+ targetClass,
+ nextActor,
+ shortestLimitation.distance,
+ location)
+ {
+ ProcessActor(nextActor, emptyActorArray);
+ }
+}
+
+private final function DoIterate_Dynamic(
+ LevelCore core,
+ class targetClass)
+{
+ local Actor nextActor;
+ local array emptyActorArray;
+
+ foreach core.DynamicActors(targetClass, nextActor) {
+ ProcessActor(nextActor, emptyActorArray);
+ }
+}
+
+private final function DoIterate_All(
+ LevelCore core,
+ class targetClass)
+{
+ local Actor nextActor;
+ local array emptyActorArray;
+
+ foreach core.AllActors(targetClass, nextActor) {
+ ProcessActor(nextActor, emptyActorArray);
+ }
}
public function Iter Next()
@@ -131,7 +586,7 @@ public function Iter Next()
if (!initialized) {
return self;
}
- TryTracing();
+ TryIterating();
currentIndex += 1;
return self;
}
@@ -141,11 +596,11 @@ public function AcediaObject Get()
if (!initialized) {
return none;
}
- TryTracing();
+ TryIterating();
if (HasFinished()) {
return none;
}
- return tracedActors[currentIndex].NewRef();
+ return foundActors[currentIndex].NewRef();
}
public function Vector GetHitLocation()
@@ -153,7 +608,7 @@ public function Vector GetHitLocation()
if (!initialized) {
return Vect(0.0f, 0.0f, 0.0f);
}
- TryTracing();
+ TryIterating();
if (HasFinished()) {
return Vect(0.0f, 0.0f, 0.0f);
}
@@ -165,7 +620,7 @@ public function Vector GetHitNormal()
if (!initialized) {
return Vect(0.0f, 0.0f, 0.0f);
}
- TryTracing();
+ TryIterating();
if (HasFinished()) {
return Vect(0.0f, 0.0f, 0.0f);
}
@@ -196,29 +651,160 @@ public function EPawn GetPawn()
public function bool HasFinished()
{
- TryTracing();
- return (currentIndex >= tracedActors.length);
+ TryIterating();
+ return (currentIndex >= foundActors.length);
}
public function Iter LeaveOnlyNotNone()
{
- // We cannot tracer `none` actors, so no need to do anything
+ // We cannot iterate over `none` actors with native iterators, so this
+ // condition is automatically satisfied
return self;
}
-public function TracingIterator LeaveOnlyPawns()
+private final function UpdateFilter(
+ out IterFilter actualValue,
+ IterFilter newValue)
{
- if (initialized && !traced) {
- onlyPawns = true;
+ if (!initialized) return;
+ if (iterated) return;
+
+ if (actualValue == ITF_Nothing) {
+ actualValue = ITF_Have;
+ }
+ else if (actualValue != newValue)
+ {
+ // Filter already had value and it contradicted our current one
+ emptyIterator = true;
}
+}
+
+public function EntityIterator LeaveOnlyPawns()
+{
+ UpdateFilter(pawnsFilter, ITF_Have);
+ return self;
+}
+
+public function EntityIterator LeaveOnlyNonPawns()
+{
+
+ UpdateFilter(pawnsFilter, ITF_NotHave);
+ return self;
+}
+
+public function EntityIterator LeaveOnlyPlaceables()
+{
+ UpdateFilter(placeablesFilter, ITF_Have);
+ return self;
+}
+
+public function EntityIterator LeaveOnlyNonPlaceables()
+{
+ UpdateFilter(placeablesFilter, ITF_NotHave);
+ return self;
+}
+
+public function EntityIterator LeaveOnlyVisible()
+{
+ UpdateFilter(visibleFilter, ITF_Have);
+ return self;
+}
+
+public function EntityIterator LeaveOnlyInvisible()
+{
+ UpdateFilter(visibleFilter, ITF_NotHave);
+ return self;
+}
+
+public function EntityIterator LeaveOnlyColliding()
+{
+ UpdateFilter(collidingFilter, ITF_Have);
+ return self;
+}
+
+public function EntityIterator LeaveOnlyNonColliding()
+{
+ UpdateFilter(collidingFilter, ITF_NotHave);
+ return self;
+}
+
+public function EntityIterator LeaveOnlyStatic()
+{
+ UpdateFilter(staticFilter, ITF_Have);
+ return self;
+}
+
+public function EntityIterator LeaveOnlyDynamic()
+{
+ UpdateFilter(staticFilter, ITF_NotHave);
return self;
}
-public function TracingIterator LeaveOnlyVisible()
+private function AddDistanceLimitation(NearbyLimitation newLimitation)
{
- if (initialized && !traced) {
- onlyVisible = true;
+ if (!distanceCheck)
+ {
+ distanceCheck = true;
+ shortestLimitation = newLimitation;
+ return;
+ }
+ if (newLimitation.distance < shortestLimitation.distance)
+ {
+ otherDistanceLimitations[otherDistanceLimitations.length] =
+ shortestLimitation;
+ shortestLimitation = newLimitation;
}
+ else
+ {
+ otherDistanceLimitations[otherDistanceLimitations.length] =
+ newLimitation;
+ }
+}
+
+public function EntityIterator LeaveOnlyNearby(
+ EPlaceable placeable,
+ float radius)
+{
+ local NearbyLimitation newLimitation;
+
+ if (!initialized) return self;
+ if (iterated) return self;
+ if (placeable == none) return self;
+
+ placeable.NewRef();
+ newLimitation.placeable = placeable;
+ newLimitation.location = placeable.GetLocation();
+ newLimitation.distance = radius;
+ newLimitation.distanceSquared = radius * radius;
+ AddDistanceLimitation(newLimitation);
+ return self;
+}
+
+public function EntityIterator LeaveOnlyNearbyToLocation(
+ Vector location,
+ float radius)
+{
+ local NearbyLimitation newLimitation;
+
+ if (!initialized) return self;
+ if (iterated) return self;
+
+ newLimitation.location = location;
+ newLimitation.distance = radius;
+ newLimitation.distanceSquared = radius * radius;
+ AddDistanceLimitation(newLimitation);
+ return self;
+}
+
+public function EntityIterator LeaveOnlyTouching(EPlaceable placeable)
+{
+ if (!initialized) return self;
+ if (iterated) return self;
+ if (placeable == none) return self;
+
+ touchingCheck = true;
+ placeable.NewRef();
+ touchers[touchers.length] = placeable;
return self;
}
diff --git a/sources/Gameplay/KF1Frontend/World/KF1_WorldComponent.uc b/sources/Gameplay/KF1Frontend/World/KF1_WorldComponent.uc
index a70cd36..d5c965f 100644
--- a/sources/Gameplay/KF1Frontend/World/KF1_WorldComponent.uc
+++ b/sources/Gameplay/KF1Frontend/World/KF1_WorldComponent.uc
@@ -35,7 +35,7 @@ public function TracingIterator TraceBetween(Vector start, Vector end)
newIterator = KF1_TracingIterator(
_.memory.Allocate(class'KF1_TracingIterator'));
- newIterator.Initialize(start, end);
+ newIterator.InitializeTracing(start, end);
return newIterator;
}