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