From fc41aad3ed283f63f281115480c16e7321fc34c7 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Sun, 17 Jul 2022 20:29:34 +0700 Subject: [PATCH] Refactor default entities iterator implementation --- sources/BaseRealm/Iter.uc | 15 + .../Frontend/World/EntityIterator.uc | 97 ++- .../Frontend/World/TracingIterator.uc | 51 +- .../KF1Frontend/BaseImplementation/EKFPawn.uc | 2 - .../BaseImplementation/EKFPlaceable.uc | 202 ----- .../BaseImplementation/EKFUnknownPlaceable.uc | 126 +++- .../KF1Frontend/World/KF1_EntityIterator.uc | 184 ----- .../KF1Frontend/World/KF1_TracingIterator.uc | 706 ++++++++++++++++-- .../KF1Frontend/World/KF1_WorldComponent.uc | 2 +- 9 files changed, 861 insertions(+), 524 deletions(-) delete mode 100644 sources/Gameplay/KF1Frontend/BaseImplementation/EKFPlaceable.uc delete mode 100644 sources/Gameplay/KF1Frontend/World/KF1_EntityIterator.uc 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; }