Browse Source

Refactor default entities iterator implementation

pull/8/head
Anton Tarasenko 2 years ago
parent
commit
fc41aad3ed
  1. 15
      sources/BaseRealm/Iter.uc
  2. 97
      sources/Gameplay/BaseClasses/Frontend/World/EntityIterator.uc
  3. 51
      sources/Gameplay/BaseClasses/Frontend/World/TracingIterator.uc
  4. 2
      sources/Gameplay/KF1Frontend/BaseImplementation/EKFPawn.uc
  5. 202
      sources/Gameplay/KF1Frontend/BaseImplementation/EKFPlaceable.uc
  6. 126
      sources/Gameplay/KF1Frontend/BaseImplementation/EKFUnknownPlaceable.uc
  7. 184
      sources/Gameplay/KF1Frontend/World/KF1_EntityIterator.uc
  8. 706
      sources/Gameplay/KF1Frontend/World/KF1_TracingIterator.uc
  9. 2
      sources/Gameplay/KF1Frontend/World/KF1_WorldComponent.uc

15
sources/BaseRealm/Iter.uc

@ -21,6 +21,21 @@
class Iter extends AcediaObject class Iter extends AcediaObject
abstract; 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. * Makes iterator pick next item.
* Use `HasFinished()` to check whether you have iterated all of them. * Use `HasFinished()` to check whether you have iterated all of them.

97
sources/Gameplay/BaseClasses/Frontend/World/EntityIterator.uc

@ -60,13 +60,29 @@ public function EPawn GetPawn();
public function EntityIterator LeaveOnlyPawns(); public function EntityIterator LeaveOnlyPawns();
/** /**
* Makes caller iterator skip any entities that are not visible in the game * Makes caller iterator skip any entities that support `EPawn` interface
* world. * 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. * @return Reference to caller `EntityIterator` to allow for method chaining.
*/ */
public function EntityIterator LeaveOnlyPlaceables(); 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 * Makes caller iterator skip any entities that are not visible in the game
* world. * world.
@ -75,6 +91,83 @@ public function EntityIterator LeaveOnlyPlaceables();
*/ */
public function EntityIterator LeaveOnlyVisible(); 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 defaultproperties
{ {
} }

51
sources/Gameplay/BaseClasses/Frontend/World/TracingIterator.uc

@ -17,13 +17,13 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. * along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/ */
class TracingIterator extends Iter class TracingIterator extends EntityIterator
abstract; abstract;
/** /**
* Returns position from which tracing is started. * 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(); public function Vector GetTracingStart();
@ -34,14 +34,6 @@ public function Vector GetTracingStart();
*/ */
public function Vector GetTracingEnd(); 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 * Returns hit location for the `EPlaceable` that `TracingIterator` is
* currently at. * currently at.
@ -62,45 +54,6 @@ public function Vector GetHitLocation();
*/ */
public function Vector GetHitNormal(); 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 defaultproperties
{ {
} }

2
sources/Gameplay/KF1Frontend/BaseImplementation/EKFPawn.uc

@ -59,7 +59,6 @@ public function bool Supports(class<EInterface> newInterfaceClass)
{ {
if (newInterfaceClass == none) return false; if (newInterfaceClass == none) return false;
if (newInterfaceClass == class'EPlaceable') return true; if (newInterfaceClass == class'EPlaceable') return true;
if (newInterfaceClass == class'EKFPlaceable') return true;
if (newInterfaceClass == class'EKFPawn') return true; if (newInterfaceClass == class'EKFPawn') return true;
return false; return false;
@ -71,7 +70,6 @@ public function EInterface As(class<EInterface> newInterfaceClass)
return none; return none;
} }
if ( newInterfaceClass == class'EPlaceable' if ( newInterfaceClass == class'EPlaceable'
|| newInterfaceClass == class'EKFPlaceable'
|| newInterfaceClass == class'EKFPawn') || newInterfaceClass == class'EKFPawn')
{ {
return Copy(); return Copy();

202
sources/Gameplay/KF1Frontend/BaseImplementation/EKFPlaceable.uc

@ -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 <https://www.gnu.org/licenses/>.
*/
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<EInterface> 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<EInterface> 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
{
}

126
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. * will represent.
* @return New `EKFUnknownPlaceable` that represents given `actorInstance`. * @return New `EKFUnknownPlaceable` that represents given `actorInstance`.
*/ */
@ -44,25 +44,12 @@ public final static /*unreal*/ function EKFUnknownPlaceable Wrap(
if (actorInstance == none) { if (actorInstance == none) {
return none; return none;
} }
newReference = EKFUnknownPlaceable( newReference =
__().memory.Allocate(class'EKFUnknownPlaceable')); EKFUnknownPlaceable(__().memory.Allocate(class'EKFUnknownPlaceable'));
newReference.actorReference = __server().unreal.ActorRef(actorInstance); newReference.actorReference = __server().unreal.ActorRef(actorInstance);
return newReference; 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() public function EInterface Copy()
{ {
local Actor actorInstance; local Actor actorInstance;
@ -73,15 +60,20 @@ public function EInterface Copy()
public function bool Supports(class<EInterface> newInterfaceClass) public function bool Supports(class<EInterface> newInterfaceClass)
{ {
if (newInterfaceClass == none) return false; if (newInterfaceClass == none) return false;
if (newInterfaceClass == class'EPlaceable') return true; if (newInterfaceClass == class'EPlaceable') return true;
if (newInterfaceClass == class'EKFUnknownPlaceable') return true; if (newInterfaceClass == class'EKFUnknownPlaceable') return true;
if (newInterfaceClass == class'EKFPawn') {
return (Pawn(GetNativeInstance()) != none);
}
return false; return false;
} }
public function EInterface As(class<EInterface> newInterfaceClass) public function EInterface As(class<EInterface> newInterfaceClass)
{ {
local Pawn pawnInstance;
if (!IsExistent()) { if (!IsExistent()) {
return none; return none;
} }
@ -90,6 +82,14 @@ public function EInterface As(class<EInterface> newInterfaceClass)
{ {
return Copy(); return Copy();
} }
if ( newInterfaceClass == class'EPawn'
|| newInterfaceClass == class'EKFPawn')
{
pawnInstance = Pawn(GetNativeInstance());
if (pawnInstance != none) {
return class'EKFPawn'.static.Wrap(pawnInstance);
}
}
return none; return none;
} }
@ -100,13 +100,26 @@ public function bool IsExistent()
public function bool SameAs(EInterface other) public function bool SameAs(EInterface other)
{ {
local EKFUnknownPlaceable otherUnknown; local EKFUnknownPlaceable otherPlaceable;
otherUnknown = EKFUnknownPlaceable(other); otherPlaceable = EKFUnknownPlaceable(other);
if (otherUnknown == none) { if (otherPlaceable == none) {
return false; 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() public function Vector GetLocation()
@ -120,6 +133,71 @@ public function Vector GetLocation()
return Vect(0.0, 0.0, 0.0); 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 defaultproperties
{ {
} }

184
sources/Gameplay/KF1Frontend/World/KF1_EntityIterator.uc

@ -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 <https://www.gnu.org/licenses/>.
*/
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<EPlaceable> 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<Actor> 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
{
}

706
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 * Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
@ -19,43 +21,150 @@
*/ */
class KF1_TracingIterator extends TracingIterator; 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 // Simply store all traced `Actor`s here at the moment of user first
// interacting with iterator's items: when either `Next()` or one of // interacting with iterator's items: when either `Next()` or one of
// the `Get...()` methods were called. // the `Get...()` methods were called.
var private array<EPlaceable> tracedActors; var private array<EPlaceable> 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, // Store information about hit location and normal in the other arrays,
// alongside `tracedActors`. // alongside `foundActors`.
var private array<Vector> hitLocations, hitNormals; var private array<Vector> hitLocations, hitNormals;
// Did we already perform tracing?
var private bool traced;
// Iterator filters // Iterator filters
var private bool onlyPawns; var private IterFilter pawnsFilter;
var private bool onlyVisible; 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<NearbyLimitation> otherDistanceLimitations;
var private bool touchingCheck;
var private array<EPlaceable> 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() protected function Finalizer()
{ {
_.memory.FreeMany(tracedActors); local int i;
tracedActors.length = 0;
initialized = false; 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 * Initializes iterator for entities that can be traced between `start` and
* `end` positions, in order starting from the `start` * `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) { if (initialized) {
return; return;
} }
startPosition = start; startPosition = start;
endPosition = end; endPosition = end;
initialized = true; collidingFilter = ITF_Have;
initialized = true;
tracingIterator = true;
} }
public function Vector GetTracingStart() public function Vector GetTracingStart()
@ -77,29 +186,302 @@ private final function bool IsActorVisible(Actor actorToCheck)
return true; return true;
} }
private final function bool IsAllowed(Actor nextActor, array<Actor> 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<Actor> 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<Actor> GetActorsToTouch(
class<Actor> targetClass)
{
local int i;
local Actor nextActor;
local EKFPawn asPawn;
local EKFUnknownPlaceable asUnknown;
local array<Actor> 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<Actor> GetAllowList(
class<Actor> targetClass,
array<Actor> actorsToTouch)
{
local int i, j;
local Actor nextActor;
local int requiredTouchesAmount;
local array<int> touchesAmount;
local array<Actor> 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. // Does actual tracing, but only once per iterator's lifecycle.
// Assumes `initialized` is `true`. // Assumes `initialized` is `true`.
private final function TryTracing() private final function TryIterating()
{ {
local Pawn nextPawn;
local Actor nextActor;
local class<Actor> targetClass;
local ServerLevelCore core; local ServerLevelCore core;
local Vector nextHitLocation, nextHitNormal; local class<Actor> targetClass;
local array<Actor> actorsToTouch;
local array<Actor> allowedActors;
// Checking `initialized` flag is already done by every method that if (iterated) {
// calls `TryTracing()`
if (traced) {
return; return;
} }
currentIndex = 0; if (emptyIterator)
if (onlyPawns) { {
targetClass = class'Pawn'; iterated = true;
return;
}
core = ServerLevelCore(class'ServerLevelCore'.static.GetInstance());
if (pawnsFilter == ITF_Have) {
targetClass = class'Pawn';
} }
else { 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<Actor> targetClass,
array<Actor> 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<Actor> targetClass,
array<Actor> allowedActors)
{
local Actor nextActor;
local Vector nextHitLocation, nextHitNormal;
foreach core.TraceActors(targetClass, foreach core.TraceActors(targetClass,
nextActor, nextActor,
nextHitLocation, nextHitLocation,
@ -107,23 +489,96 @@ private final function TryTracing()
endPosition, endPosition,
startPosition) startPosition)
{ {
if (onlyVisible && !IsActorVisible(nextActor)) {
continue; if (ProcessActor(nextActor, allowedActors))
}
hitLocations[hitLocations.length] = nextHitLocation;
hitNormals[hitNormals.length] = nextHitNormal;
nextPawn = Pawn(nextActor);
if (nextPawn != none)
{ {
tracedActors[tracedActors.length] = hitLocations[hitLocations.length] = nextHitLocation;
class'EKFPawn'.static.Wrap(nextPawn); hitNormals[hitNormals.length] = nextHitNormal;
}
else {
tracedActors[tracedActors.length] =
class'EKFUnknownPlaceable'.static.Wrap(nextActor);
} }
} }
traced = true; }
private final function DoIterate_Touching(
class<Actor> targetClass,
array<Actor> allowedActors)
{
local Actor nextActor;
foreach mainToucher.TouchingActors(targetClass, nextActor) {
ProcessActor(nextActor, allowedActors);
}
}
private final function DoIterate_Colliding(
LevelCore core,
class<Actor> targetClass)
{
local Actor nextActor;
local Vector location;
local array<Actor> 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<Actor> targetClass)
{
local Actor nextActor;
local Vector location;
local array<Actor> 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<Actor> targetClass)
{
local Actor nextActor;
local array<Actor> emptyActorArray;
foreach core.DynamicActors(targetClass, nextActor) {
ProcessActor(nextActor, emptyActorArray);
}
}
private final function DoIterate_All(
LevelCore core,
class<Actor> targetClass)
{
local Actor nextActor;
local array<Actor> emptyActorArray;
foreach core.AllActors(targetClass, nextActor) {
ProcessActor(nextActor, emptyActorArray);
}
} }
public function Iter Next() public function Iter Next()
@ -131,7 +586,7 @@ public function Iter Next()
if (!initialized) { if (!initialized) {
return self; return self;
} }
TryTracing(); TryIterating();
currentIndex += 1; currentIndex += 1;
return self; return self;
} }
@ -141,11 +596,11 @@ public function AcediaObject Get()
if (!initialized) { if (!initialized) {
return none; return none;
} }
TryTracing(); TryIterating();
if (HasFinished()) { if (HasFinished()) {
return none; return none;
} }
return tracedActors[currentIndex].NewRef(); return foundActors[currentIndex].NewRef();
} }
public function Vector GetHitLocation() public function Vector GetHitLocation()
@ -153,7 +608,7 @@ public function Vector GetHitLocation()
if (!initialized) { if (!initialized) {
return Vect(0.0f, 0.0f, 0.0f); return Vect(0.0f, 0.0f, 0.0f);
} }
TryTracing(); TryIterating();
if (HasFinished()) { if (HasFinished()) {
return Vect(0.0f, 0.0f, 0.0f); return Vect(0.0f, 0.0f, 0.0f);
} }
@ -165,7 +620,7 @@ public function Vector GetHitNormal()
if (!initialized) { if (!initialized) {
return Vect(0.0f, 0.0f, 0.0f); return Vect(0.0f, 0.0f, 0.0f);
} }
TryTracing(); TryIterating();
if (HasFinished()) { if (HasFinished()) {
return Vect(0.0f, 0.0f, 0.0f); return Vect(0.0f, 0.0f, 0.0f);
} }
@ -196,29 +651,160 @@ public function EPawn GetPawn()
public function bool HasFinished() public function bool HasFinished()
{ {
TryTracing(); TryIterating();
return (currentIndex >= tracedActors.length); return (currentIndex >= foundActors.length);
} }
public function Iter LeaveOnlyNotNone() 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; return self;
} }
public function TracingIterator LeaveOnlyPawns() private final function UpdateFilter(
out IterFilter actualValue,
IterFilter newValue)
{ {
if (initialized && !traced) { if (!initialized) return;
onlyPawns = true; 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; return self;
} }
public function TracingIterator LeaveOnlyVisible() private function AddDistanceLimitation(NearbyLimitation newLimitation)
{ {
if (initialized && !traced) { if (!distanceCheck)
onlyVisible = true; {
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; return self;
} }

2
sources/Gameplay/KF1Frontend/World/KF1_WorldComponent.uc

@ -35,7 +35,7 @@ public function TracingIterator TraceBetween(Vector start, Vector end)
newIterator = KF1_TracingIterator( newIterator = KF1_TracingIterator(
_.memory.Allocate(class'KF1_TracingIterator')); _.memory.Allocate(class'KF1_TracingIterator'));
newIterator.Initialize(start, end); newIterator.InitializeTracing(start, end);
return newIterator; return newIterator;
} }

Loading…
Cancel
Save