diff --git a/managed/src/SwiftlyS2.Core/Modules/Engine/TraceManager.cs b/managed/src/SwiftlyS2.Core/Modules/Engine/TraceManager.cs
index 166daaf06..ddcb363fe 100644
--- a/managed/src/SwiftlyS2.Core/Modules/Engine/TraceManager.cs
+++ b/managed/src/SwiftlyS2.Core/Modules/Engine/TraceManager.cs
@@ -1,9 +1,8 @@
-using SwiftlyS2.Core.Extensions;
using SwiftlyS2.Core.Natives;
-using SwiftlyS2.Core.SchemaDefinitions;
using SwiftlyS2.Shared.Natives;
-using SwiftlyS2.Shared.SchemaDefinitions;
using SwiftlyS2.Shared.Services;
+using SwiftlyS2.Core.SchemaDefinitions;
+using SwiftlyS2.Shared.SchemaDefinitions;
namespace SwiftlyS2.Core.Services;
@@ -14,7 +13,9 @@ public void TracePlayerBBox( Vector start, Vector end, BBox_t bounds, CTraceFilt
unsafe
{
fixed (CGameTrace* tracePtr = &trace)
+ {
GameFunctions.TracePlayerBBox(start, end, bounds, &filter, tracePtr);
+ }
}
}
@@ -23,7 +24,9 @@ public void TraceShape( Vector start, Vector end, Ray_t ray, CTraceFilter filter
unsafe
{
fixed (CGameTrace* tracePtr = &trace)
+ {
GameFunctions.TraceShape(NativeEngineHelpers.GetTraceManager(), &ray, start, end, &filter, tracePtr);
+ }
}
}
diff --git a/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CBasePlayerPawn.cs b/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CBasePlayerPawn.cs
index 76525f9cd..fa7677705 100644
--- a/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CBasePlayerPawn.cs
+++ b/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CBasePlayerPawn.cs
@@ -1,17 +1,26 @@
-using SwiftlyS2.Shared.Natives;
+using SwiftlyS2.Shared.Natives;
namespace SwiftlyS2.Shared.SchemaDefinitions;
public partial interface CBasePlayerPawn
{
- ///
- /// Performs a suicide on the pawn, optionally causing an explosion and allowing forced execution.
- ///
- public void CommitSuicide( bool explode, bool force );
-
public Vector? EyePosition { get; }
public float GroundDistance { get; }
public MaskTrace InteractsWith { get; }
public MaskTrace InteractsAs { get; }
public MaskTrace InteractsExclude { get; }
+
+ ///
+ /// Performs a suicide on the pawn, optionally causing an explosion and allowing forced execution.
+ ///
+ public void CommitSuicide( bool explode, bool force );
+
+ ///
+ /// Checks if the target player is within the line of sight of this player.
+ /// Performs both physical obstruction check and field of view validation.
+ ///
+ /// The target player to check visibility for.
+ /// Optional field of view in degrees.
+ /// True if the target player is visible; otherwise, false.
+ public bool HasLineOfSight( CCSPlayerPawn targetPlayer, float? fieldOfViewDegrees = null );
}
\ No newline at end of file
diff --git a/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CBasePlayerPawnImpl.cs b/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CBasePlayerPawnImpl.cs
index ca35b3047..d589ffd95 100644
--- a/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CBasePlayerPawnImpl.cs
+++ b/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CBasePlayerPawnImpl.cs
@@ -1,4 +1,4 @@
-using SwiftlyS2.Core.Natives;
+using SwiftlyS2.Core.Natives;
using SwiftlyS2.Core.Services;
using SwiftlyS2.Shared.Natives;
using SwiftlyS2.Shared.SchemaDefinitions;
@@ -7,29 +7,30 @@ namespace SwiftlyS2.Core.SchemaDefinitions;
internal partial class CBasePlayerPawnImpl : CBasePlayerPawn
{
- public void CommitSuicide( bool explode, bool force )
- {
- GameFunctions.PawnCommitSuicide(Address, explode, force);
- }
-
public Vector? EyePosition {
get {
- if (!IsValid) return null;
- if (AbsOrigin == null) return null;
+ if (!IsValid)
+ {
+ return null;
+ }
- var absOrigin = AbsOrigin.Value;
- var viewmodelOffset = ViewOffset;
- if (viewmodelOffset == null) return null;
+ if (AbsOrigin == null || ViewOffset == null)
+ {
+ return null;
+ }
- absOrigin.Z += viewmodelOffset.Z.Value;
+ var absOrigin = AbsOrigin.Value;
+ absOrigin.Z += ViewOffset.Z.Value;
return absOrigin;
}
}
public float GroundDistance {
get {
- if (!IsValid) return -1f;
- if (AbsOrigin == null) return -1f;
+ if (!IsValid || AbsOrigin == null)
+ {
+ return -1f;
+ }
var start = AbsOrigin.Value;
var angle = new QAngle(90f, 0f, 0f);
@@ -42,7 +43,6 @@ public float GroundDistance {
var trace = new CGameTrace();
TraceManager.SimpleTrace(start, end, RayType_t.RAY_TYPE_HULL, RnQueryObjectSet.All, MaskTrace.Sky, MaskTrace.Empty, MaskTrace.Empty, CollisionGroup.Always, ref trace, Address, nint.Zero);
-
return trace.Distance;
}
}
@@ -64,4 +64,44 @@ public MaskTrace InteractsExclude {
return !IsValid ? MaskTrace.Empty : (MaskTrace)Collision.CollisionAttribute.InteractsExclude;
}
}
+
+ public void CommitSuicide( bool explode, bool force )
+ {
+ GameFunctions.PawnCommitSuicide(Address, explode, force);
+ }
+
+ public bool HasLineOfSight( CCSPlayerPawn targetPlayer, float? fieldOfViewDegrees = null )
+ {
+ if (!IsValid || !targetPlayer.IsValid || this.LifeState != (byte)LifeState_t.LIFE_ALIVE || targetPlayer.LifeState != (byte)LifeState_t.LIFE_ALIVE)
+ {
+ return false;
+ }
+
+ var playerPawn = new CCSPlayerPawnImpl(this.Address);
+ if (!(playerPawn.OriginalController.Value?.IsValid ?? false))
+ {
+ return false;
+ }
+
+ var trace = new CGameTrace();
+ TraceManager.SimpleTrace(this.EyePosition!.Value, targetPlayer.EyePosition!.Value, RayType_t.RAY_TYPE_HULL, RnQueryObjectSet.All, MaskTrace.Player, MaskTrace.Trigger, MaskTrace.Empty, CollisionGroup.Always, ref trace, Address, nint.Zero);
+ if (!trace.HitPlayer(out var player) || player?.PlayerPawn?.Index != targetPlayer.Index)
+ {
+ return false;
+ }
+
+ var desiredFov = playerPawn.OriginalController.Value.DesiredFOV;
+ var halfFov = fieldOfViewDegrees ?? (desiredFov <= 0f ? 52f : desiredFov / 2f);
+
+ // Calculate the angle between the player's view direction and the direction to the target
+ playerPawn.EyeAngles.ToDirectionVectors(out var playerForward, out var _, out var _);
+ var directionToTarget = targetPlayer.EyePosition!.Value - this.EyePosition!.Value;
+ directionToTarget.Normalize();
+
+ // Calculate the angle using the dot product to avoid coordinate system issues
+ var dotProduct = Math.Clamp(playerForward.Dot(directionToTarget), -1f, 1f);
+ var angleInDegrees = Math.Acos(dotProduct) * (180f / Math.PI);
+
+ return angleInDegrees <= halfFov;
+ }
}
\ No newline at end of file
diff --git a/managed/src/SwiftlyS2.Shared/Modules/Commands/ICommandContext.cs b/managed/src/SwiftlyS2.Shared/Modules/Commands/ICommandContext.cs
index c9817146d..e50d11ca3 100644
--- a/managed/src/SwiftlyS2.Shared/Modules/Commands/ICommandContext.cs
+++ b/managed/src/SwiftlyS2.Shared/Modules/Commands/ICommandContext.cs
@@ -2,18 +2,36 @@
namespace SwiftlyS2.Shared.Commands;
-public interface ICommandContext {
-
- public bool IsSentByPlayer { get; }
-
- public IPlayer? Sender { get; }
-
- public string Prefix { get; }
-
- public bool IsSlient { get; }
-
- public string[] Args { get; }
-
- public void Reply(string message);
-
+public interface ICommandContext
+{
+ ///
+ /// Gets a value indicating whether the command was sent by a player.
+ ///
+ public bool IsSentByPlayer { get; }
+
+ ///
+ /// Gets the player who sent the command, or null if the command was not sent by a player.
+ ///
+ public IPlayer? Sender { get; }
+
+ ///
+ /// Gets the command name itself.
+ ///
+ public string Prefix { get; }
+
+ ///
+ /// Gets a value indicating whether the command should be executed silently without broadcasting to other players.
+ ///
+ public bool IsSlient { get; }
+
+ ///
+ /// Gets the array of arguments passed with the command.
+ ///
+ public string[] Args { get; }
+
+ ///
+ /// Sends a reply message to the command sender.
+ ///
+ /// The message to send as a reply.
+ public void Reply( string message );
}
\ No newline at end of file
diff --git a/managed/src/SwiftlyS2.Shared/Modules/Engine/ITraceManager.cs b/managed/src/SwiftlyS2.Shared/Modules/Engine/ITraceManager.cs
index 8df985293..533fa97c9 100644
--- a/managed/src/SwiftlyS2.Shared/Modules/Engine/ITraceManager.cs
+++ b/managed/src/SwiftlyS2.Shared/Modules/Engine/ITraceManager.cs
@@ -1,4 +1,4 @@
-using SwiftlyS2.Shared.Natives;
+using SwiftlyS2.Shared.Natives;
using SwiftlyS2.Shared.SchemaDefinitions;
namespace SwiftlyS2.Shared.Services;
diff --git a/managed/src/SwiftlyS2.Shared/Modules/Schemas/ISchemaClass.cs b/managed/src/SwiftlyS2.Shared/Modules/Schemas/ISchemaClass.cs
index 3892a2038..7ec32d17e 100644
--- a/managed/src/SwiftlyS2.Shared/Modules/Schemas/ISchemaClass.cs
+++ b/managed/src/SwiftlyS2.Shared/Modules/Schemas/ISchemaClass.cs
@@ -4,22 +4,20 @@ namespace SwiftlyS2.Shared.Schemas;
public interface ISchemaClass : INativeHandle
{
-
- ///
- /// Convert this handle to another type.
- ///
- /// The type to convert to.
- /// The converted handle.
- K As() where K : ISchemaClass
- {
- return K.From(Address);
- }
+ ///
+ /// Convert this handle to another type.
+ ///
+ /// The type to convert to.
+ /// The converted handle.
+ public K As() where K : ISchemaClass
+ {
+ return K.From(Address);
+ }
}
public interface ISchemaClass : ISchemaField, ISchemaClass, INativeHandle where T : ISchemaClass
{
-
- internal static abstract T From( nint handle );
- internal static abstract int Size { get; }
- internal static abstract string? ClassName { get; }
+ internal static abstract T From( nint handle );
+ internal static abstract int Size { get; }
+ internal static abstract string? ClassName { get; }
}
\ No newline at end of file
diff --git a/managed/src/SwiftlyS2.Shared/Natives/Structs/CGameTrace.cs b/managed/src/SwiftlyS2.Shared/Natives/Structs/CGameTrace.cs
index 135682d26..94dda90c3 100644
--- a/managed/src/SwiftlyS2.Shared/Natives/Structs/CGameTrace.cs
+++ b/managed/src/SwiftlyS2.Shared/Natives/Structs/CGameTrace.cs
@@ -1,9 +1,9 @@
-using SwiftlyS2.Core.Players;
-using SwiftlyS2.Core.SchemaDefinitions;
+using System.Runtime.InteropServices;
+using SwiftlyS2.Core.Players;
using SwiftlyS2.Shared.Players;
-using SwiftlyS2.Shared.SchemaDefinitions;
using SwiftlyS2.Shared.Schemas;
-using System.Runtime.InteropServices;
+using SwiftlyS2.Core.SchemaDefinitions;
+using SwiftlyS2.Shared.SchemaDefinitions;
namespace SwiftlyS2.Shared.Natives;
@@ -69,23 +69,29 @@ public readonly Vector Direction {
public readonly bool HitEntityByDesignerName( string designerName, out T outEntity, NameMatchType matchType = NameMatchType.StartsWith ) where T : ISchemaClass
{
outEntity = T.From(IntPtr.Zero);
+
if (!DidHit)
+ {
return false;
+ }
- var entity = Entity;
- if (entity.IsValid == false || entity is not T typedEntity)
+ if (Entity == null || !Entity.IsValid)
+ {
return false;
+ }
- var name = entity.DesignerName;
- if (name == null)
+ var typedEntity = Entity.As();
+ if (!typedEntity.IsValid)
+ {
return false;
+ }
var isMatch = matchType switch {
- NameMatchType.Exact => name.Equals(designerName, StringComparison.Ordinal),
- NameMatchType.StartsWith => name.StartsWith(designerName, StringComparison.Ordinal),
- NameMatchType.EndsWith => name.EndsWith(designerName, StringComparison.Ordinal),
- NameMatchType.Contains => name.Contains(designerName, StringComparison.Ordinal),
- _ => false,
+ NameMatchType.Exact => Entity.DesignerName.Equals(designerName, StringComparison.OrdinalIgnoreCase),
+ NameMatchType.StartsWith => Entity.DesignerName.StartsWith(designerName, StringComparison.OrdinalIgnoreCase),
+ NameMatchType.EndsWith => Entity.DesignerName.EndsWith(designerName, StringComparison.OrdinalIgnoreCase),
+ NameMatchType.Contains => Entity.DesignerName.Contains(designerName, StringComparison.OrdinalIgnoreCase),
+ _ => false
};
if (isMatch)
@@ -103,38 +109,47 @@ public readonly bool HitEntityByDesignerName( string designerName, NameMatchT
public readonly bool HitPlayer( out IPlayer? player )
{
- if (HitEntityByDesignerName("player", out var p, NameMatchType.StartsWith))
+ player = null;
+
+ if (!DidHit || Entity == null || !Entity.IsValid)
{
- var controller = p.OriginalController;
- if (!controller.IsValid)
- {
- player = null;
- return false;
- }
-
- player = new Player((int)(controller.Value!.Index - 1));
- return true;
+ return false;
}
- player = null;
- return false;
- }
+ if (!Entity.DesignerName?.StartsWith("player", StringComparison.OrdinalIgnoreCase) == true)
+ {
+ return false;
+ }
- public readonly bool HitEntity( out T entity ) where T : ISchemaClass
- {
- if (T.ClassName == null)
+ var playerPawn = Entity.As();
+ if (!playerPawn.IsValid)
{
- entity = T.From(IntPtr.Zero);
return false;
}
- if (HitEntityByDesignerName(T.ClassName, out var e, NameMatchType.Exact))
+ var controller = playerPawn.OriginalController;
+ if (!controller.IsValid)
{
- entity = e;
- return true;
+ return false;
}
+ player = new Player((int)(controller.Value!.Index - 1));
+ return true;
+ }
+
+ public readonly bool HitPlayer()
+ {
+ return HitPlayer(out _);
+ }
+
+ public readonly bool HitEntity( out T entity ) where T : ISchemaClass
+ {
entity = T.From(IntPtr.Zero);
- return false;
+ return T.ClassName != null && HitEntityByDesignerName(T.ClassName, out entity, NameMatchType.Exact);
+ }
+
+ public readonly bool HitEntity() where T : ISchemaClass
+ {
+ return HitEntity(out _);
}
}
\ No newline at end of file
diff --git a/managed/src/SwiftlyS2.Shared/Natives/Structs/QAngle.cs b/managed/src/SwiftlyS2.Shared/Natives/Structs/QAngle.cs
index 244a16f93..a0b12818f 100644
--- a/managed/src/SwiftlyS2.Shared/Natives/Structs/QAngle.cs
+++ b/managed/src/SwiftlyS2.Shared/Natives/Structs/QAngle.cs
@@ -1,5 +1,5 @@
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
namespace SwiftlyS2.Shared.Natives;
@@ -10,92 +10,124 @@ namespace SwiftlyS2.Shared.Natives;
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 12)]
public struct QAngle
{
- public float Pitch;
- public float Yaw;
- public float Roll;
+ public float Pitch;
+ public float Yaw;
+ public float Roll;
+
+ public QAngle( float pitch, float yaw, float roll )
+ {
+ Pitch = pitch;
+ Yaw = yaw;
+ Roll = roll;
+ }
- public QAngle(float pitch, float yaw, float roll)
- {
- Pitch = pitch;
- Yaw = yaw;
- Roll = roll;
- }
+ public QAngle( QAngle other )
+ {
+ Pitch = other.Pitch;
+ Yaw = other.Yaw;
+ Roll = other.Roll;
+ }
- public QAngle(QAngle other)
- {
- Pitch = other.Pitch;
- Yaw = other.Yaw;
- Roll = other.Roll;
- }
+ ///
+ /// X-axis accessor for Pitch rotation (up/down).
+ ///
+ ///
+ /// This is just a mapped accessor to the Pitch field.
+ ///
+ public float X {
+ readonly get => Pitch;
+ set => Pitch = value;
+ }
- public RadianEuler ToRadianEuler() => new(Pitch * MathF.PI / 180.0f, Yaw * MathF.PI / 180.0f, Roll * MathF.PI / 180.0f);
+ ///
+ /// Y-axis accessor for Yaw rotation (left/right).
+ ///
+ ///
+ /// This is just a mapped accessor to the Yaw field.
+ ///
+ public float Y {
+ readonly get => Yaw;
+ set => Yaw = value;
+ }
+
+ ///
+ /// Z-axis accessor for Roll rotation (roll/tilt).
+ ///
+ ///
+ /// This is just a mapped accessor to the Roll field.
+ ///
+ public float Z {
+ readonly get => Roll;
+ set => Roll = value;
+ }
- public override bool Equals(object? obj) => obj is QAngle angle && this == angle;
- public override int GetHashCode() => HashCode.Combine(Pitch, Yaw, Roll);
- public override string ToString() => $"QAngle({Pitch}, {Yaw}, {Roll})";
+ public readonly RadianEuler ToRadianEuler() => new(Pitch * MathF.PI / 180.0f, Yaw * MathF.PI / 180.0f, Roll * MathF.PI / 180.0f);
+ public override readonly bool Equals( object? obj ) => obj is QAngle angle && this == angle;
+ public override readonly int GetHashCode() => HashCode.Combine(Pitch, Yaw, Roll);
+ public override readonly string ToString() => $"QAngle({Pitch}, {Yaw}, {Roll})";
- public static QAngle Zero => new(0, 0, 0);
+ public static QAngle Zero => new(0, 0, 0);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static QAngle operator +( QAngle a, QAngle b ) => new(a.Pitch + b.Pitch, a.Yaw + b.Yaw, a.Roll + b.Roll);
- public static QAngle operator +(QAngle a, QAngle b) => new(a.Pitch + b.Pitch, a.Yaw + b.Yaw, a.Roll + b.Roll);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static QAngle operator -( QAngle a, QAngle b ) => new(a.Pitch - b.Pitch, a.Yaw - b.Yaw, a.Roll - b.Roll);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static QAngle operator -(QAngle a, QAngle b) => new(a.Pitch - b.Pitch, a.Yaw - b.Yaw, a.Roll - b.Roll);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static QAngle operator *( QAngle a, QAngle b ) => new(a.Pitch * b.Pitch, a.Yaw * b.Yaw, a.Roll * b.Roll);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static QAngle operator *(QAngle a, QAngle b) => new(a.Pitch * b.Pitch, a.Yaw * b.Yaw, a.Roll * b.Roll);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static QAngle operator /( QAngle a, QAngle b ) => new(a.Pitch / b.Pitch, a.Yaw / b.Yaw, a.Roll / b.Roll);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static QAngle operator /(QAngle a, QAngle b) => new(a.Pitch / b.Pitch, a.Yaw / b.Yaw, a.Roll / b.Roll);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static QAngle operator *( QAngle a, float b ) => new(a.Pitch * b, a.Yaw * b, a.Roll * b);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static QAngle operator *(QAngle a, float b) => new(a.Pitch * b, a.Yaw * b, a.Roll * b);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static QAngle operator /( QAngle a, float b )
+ {
+ if (b == 0)
+ {
+ throw new DivideByZeroException();
+ }
+ var oofl = 1.0f / b;
+ return new(a.Pitch * oofl, a.Yaw * oofl, a.Roll * oofl);
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static QAngle operator /(QAngle a, float b) {
- if (b == 0) {
- throw new DivideByZeroException();
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static QAngle operator -( QAngle a ) => new(-a.Pitch, -a.Yaw, -a.Roll);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==( QAngle a, QAngle b ) => a.Pitch == b.Pitch && a.Yaw == b.Yaw && a.Roll == b.Roll;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=( QAngle a, QAngle b ) => a.Pitch != b.Pitch || a.Yaw != b.Yaw || a.Roll != b.Roll;
+
+ private const float Deg2Rad = MathF.PI / 180.0f;
+
+ ///
+ /// Calculates forward, right, and up basis vectors that correspond to this angle.
+ /// Usage: angle.ToDirectionVectors(out var forward, out var right, out var up);
+ ///
+ /// Forward direction (X: north, Z: up).
+ /// Right direction.
+ /// Up direction.
+ public readonly void ToDirectionVectors( out Vector forward, out Vector right, out Vector up )
+ {
+ var yawRad = Yaw * Deg2Rad;
+ var pitchRad = Pitch * Deg2Rad;
+ var rollRad = Roll * Deg2Rad;
+
+ var sy = MathF.Sin(yawRad);
+ var cy = MathF.Cos(yawRad);
+ var sp = MathF.Sin(pitchRad);
+ var cp = MathF.Cos(pitchRad);
+ var sr = MathF.Sin(rollRad);
+ var cr = MathF.Cos(rollRad);
+
+ forward = new Vector(cp * cy, cp * sy, -sp);
+ right = new Vector((-sr * sp * cy) + (cr * sy), (-sr * sp * sy) - (cr * cy), -sr * cp);
+ up = new Vector((cr * sp * cy) + (sr * sy), (cr * sp * sy) - (sr * cy), cr * cp);
}
- var oofl = 1.0f / b;
- return new(a.Pitch * oofl, a.Yaw * oofl, a.Roll * oofl);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static QAngle operator -(QAngle a) => new(-a.Pitch, -a.Yaw, -a.Roll);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool operator ==(QAngle a, QAngle b) => a.Pitch == b.Pitch && a.Yaw == b.Yaw && a.Roll == b.Roll;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool operator !=(QAngle a, QAngle b) => a.Pitch != b.Pitch || a.Yaw != b.Yaw || a.Roll != b.Roll;
-
- private const float Deg2Rad = MathF.PI / 180.0f;
-
- ///
- /// Calculates forward, right, and up basis vectors that correspond to this angle.
- /// Usage: angle.ToDirectionVectors(out var forward, out var right, out var up);
- ///
- /// Forward direction (X: north, Z: up).
- /// Right direction.
- /// Up direction.
- public void ToDirectionVectors(out Vector forward, out Vector right, out Vector up)
- {
- var yawRad = Yaw * Deg2Rad;
- var pitchRad = Pitch * Deg2Rad;
- var rollRad = Roll * Deg2Rad;
-
- var sy = MathF.Sin(yawRad);
- var cy = MathF.Cos(yawRad);
- var sp = MathF.Sin(pitchRad);
- var cp = MathF.Cos(pitchRad);
- var sr = MathF.Sin(rollRad);
- var cr = MathF.Cos(rollRad);
-
- forward = new Vector(cp * cy, cp * sy, -sp);
- right = new Vector(-sr * sp * cy + cr * sy, -sr * sp * sy - cr * cy, -sr * cp);
- up = new Vector(cr * sp * cy + sr * sy, cr * sp * sy - sr * cy, cr * cp);
- }
-
-}
+}
\ No newline at end of file
diff --git a/managed/src/SwiftlyS2.Shared/Natives/Structs/Vector.cs b/managed/src/SwiftlyS2.Shared/Natives/Structs/Vector.cs
index ea30faedb..3dc4d5774 100644
--- a/managed/src/SwiftlyS2.Shared/Natives/Structs/Vector.cs
+++ b/managed/src/SwiftlyS2.Shared/Natives/Structs/Vector.cs
@@ -12,172 +12,160 @@ namespace SwiftlyS2.Shared.Natives;
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 12)]
public struct Vector
{
- public float X;
- public float Y;
- public float Z;
- private const float Rad2Deg = 180.0f / MathF.PI;
-
- public Vector( float x, float y, float z )
- {
- X = x;
- Y = y;
- Z = z;
- }
-
- public Vector( Vector other )
- {
- X = other.X;
- Y = other.Y;
- Z = other.Z;
- }
-
- public Vector3 ToBuiltin()
- {
- return new(X, Y, Z);
- }
-
- public static Vector FromBuiltin( Vector3 vector )
- {
- return new(vector.X, vector.Y, vector.Z);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public float Length() => (float)Math.Sqrt(X * X + Y * Y + Z * Z);
+ public float X;
+ public float Y;
+ public float Z;
+ private const float Rad2Deg = 180.0f / MathF.PI;
+ public Vector( float x, float y, float z )
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public float LengthSquared() => X * X + Y * Y + Z * Z;
+ public Vector( Vector other )
+ {
+ X = other.X;
+ Y = other.Y;
+ Z = other.Z;
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public float Distance( Vector other ) => (this - other).Length();
+ public readonly Vector3 ToBuiltin()
+ {
+ return new(X, Y, Z);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly float Length() => (float)Math.Sqrt((X * X) + (Y * Y) + (Z * Z));
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public float DistanceSquared( Vector other ) => (this - other).LengthSquared();
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly float LengthSquared() => (X * X) + (Y * Y) + (Z * Z);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly float Distance( Vector other ) => (this - other).Length();
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector Cross( Vector other ) => new(Y * other.Z - Z * other.Y, Z * other.X - X * other.Z, X * other.Y - Y * other.X);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly float DistanceSquared( Vector other ) => (this - other).LengthSquared();
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static float Dot( Vector a, Vector b ) => a.X * b.X + a.Y * b.Y + a.Z * b.Z;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly Vector Cross( Vector other ) => new((Y * other.Z) - (Z * other.Y), (Z * other.X) - (X * other.Z), (X * other.Y) - (Y * other.X));
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public float Dot( Vector other ) => Dot(this, other);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly float Dot( Vector other ) => Dot(this, other);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Normalize()
- {
- var len = Length();
- if (len > 0f)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Normalize()
{
- var inv = 1.0f / len;
- X *= inv;
- Y *= inv;
- Z *= inv;
+ var len = Length();
+ if (len > 0f)
+ {
+ var inv = 1.0f / len;
+ X *= inv;
+ Y *= inv;
+ Z *= inv;
+ }
}
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector Normalized()
- {
- var len = Length();
- if (len > 0f)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly Vector Normalized()
{
- var inv = 1.0f / len;
- return new(X * inv, Y * inv, Z * inv);
+ var len = Length();
+ if (len > 0f)
+ {
+ var inv = 1.0f / len;
+ return new(X * inv, Y * inv, Z * inv);
+ }
+ return Zero;
}
- return Zero;
- }
-
- public void Deconstruct( out float x, out float y, out float z )
- {
- x = X;
- y = Y;
- z = Z;
- }
-
- ///
- /// Converts this forward vector into Euler QAngles (pitch, yaw, roll).
- /// Usage: forward.ToQAngles(out var angles);
- ///
- /// Resulting .
- public QAngle ToQAngles()
- {
- float yaw;
- float pitch;
-
- if (X == 0f && Y == 0f)
+
+ public readonly void Deconstruct( out float x, out float y, out float z )
{
- yaw = 0f;
- pitch = Z > 0f ? 270f : 90f;
+ x = X;
+ y = Y;
+ z = Z;
}
- else
+
+ ///
+ /// Converts this forward vector into Euler QAngles (pitch, yaw, roll).
+ /// Usage: forward.ToQAngles(out var angles);
+ ///
+ /// Resulting .
+ public readonly QAngle ToQAngles()
{
- yaw = MathF.Atan2(Y, X) * Rad2Deg;
- if (yaw < 0f)
- {
- yaw += 360f;
- }
-
- var tmp = MathF.Sqrt(X * X + Y * Y);
- pitch = MathF.Atan2(-Z, tmp) * Rad2Deg;
- if (pitch < 0f)
- {
- pitch += 360f;
- }
+ float yaw;
+ float pitch;
+
+ if (X == 0f && Y == 0f)
+ {
+ yaw = 0f;
+ pitch = Z > 0f ? 270f : 90f;
+ }
+ else
+ {
+ yaw = MathF.Atan2(Y, X) * Rad2Deg;
+ if (yaw < 0f)
+ {
+ yaw += 360f;
+ }
+
+ var tmp = MathF.Sqrt((X * X) + (Y * Y));
+ pitch = MathF.Atan2(-Z, tmp) * Rad2Deg;
+ if (pitch < 0f)
+ {
+ pitch += 360f;
+ }
+ }
+
+ return new QAngle(pitch, yaw, 0f);
}
- return new QAngle(pitch, yaw, 0f);
- }
-
+ public override readonly bool Equals( object? obj ) => obj is Vector vector && this == vector;
+ public override readonly int GetHashCode() => HashCode.Combine(X, Y, Z);
+ public override readonly string ToString() => $"Vector({X}, {Y}, {Z})";
- public override bool Equals( object? obj ) => obj is Vector vector && this == vector;
- public override int GetHashCode() => HashCode.Combine(X, Y, Z);
- public override string ToString() => $"Vector({X}, {Y}, {Z})";
+ public static Vector FromBuiltin( Vector3 vector ) => new(vector.X, vector.Y, vector.Z);
+ public static Vector Zero => new(0, 0, 0);
+ public static Vector One => new(1, 1, 1);
- public static Vector Zero => new(0, 0, 0);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Dot( Vector a, Vector b ) => (a.X * b.X) + (a.Y * b.Y) + (a.Z * b.Z);
- public static Vector One => new(1, 1, 1);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector operator +( Vector a, Vector b ) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector operator -( Vector a, Vector b ) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector operator *( Vector a, Vector b ) => new(a.X * b.X, a.Y * b.Y, a.Z * b.Z);
- public static Vector operator +( Vector a, Vector b ) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector operator /( Vector a, Vector b ) => new(a.X / b.X, a.Y / b.Y, a.Z / b.Z);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Vector operator -( Vector a, Vector b ) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector operator *( Vector a, float b ) => new(a.X * b, a.Y * b, a.Z * b);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Vector operator *( Vector a, Vector b ) => new(a.X * b.X, a.Y * b.Y, a.Z * b.Z);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector operator *( float b, Vector a ) => new(a.X * b, a.Y * b, a.Z * b);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Vector operator /( Vector a, Vector b ) => new(a.X / b.X, a.Y / b.Y, a.Z / b.Z);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Vector operator *( Vector a, float b ) => new(a.X * b, a.Y * b, a.Z * b);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Vector operator *( float b, Vector a ) => new(a.X * b, a.Y * b, a.Z * b);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Vector operator /( Vector a, float b )
- {
- if (b == 0)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector operator /( Vector a, float b )
{
- throw new DivideByZeroException();
+ if (b == 0)
+ {
+ throw new DivideByZeroException();
+ }
+ var oofl = 1.0f / b;
+ return new(a.X * oofl, a.Y * oofl, a.Z * oofl);
}
- var oofl = 1.0f / b;
- return new(a.X * oofl, a.Y * oofl, a.Z * oofl);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Vector operator -( Vector a ) => new(-a.X, -a.Y, -a.Z);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool operator ==( Vector a, Vector b ) => a.X == b.X && a.Y == b.Y && a.Z == b.Z;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector operator -( Vector a ) => new(-a.X, -a.Y, -a.Z);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool operator !=( Vector a, Vector b ) => a.X != b.X || a.Y != b.Y || a.Z != b.Z;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==( Vector a, Vector b ) => a.X == b.X && a.Y == b.Y && a.Z == b.Z;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=( Vector a, Vector b ) => a.X != b.X || a.Y != b.Y || a.Z != b.Z;
}
\ No newline at end of file
diff --git a/managed/src/TestPlugin/TestPlugin.cs b/managed/src/TestPlugin/TestPlugin.cs
index f13f066a9..71e5ce69b 100644
--- a/managed/src/TestPlugin/TestPlugin.cs
+++ b/managed/src/TestPlugin/TestPlugin.cs
@@ -1129,6 +1129,16 @@ public void TeleportBotToMeCommand( ICommandContext context )
?.Teleport(player.PlayerPawn!.AbsOrigin!.Value, player.PlayerPawn!.EyeAngles, Vector.Zero);
}
+ [Command("los")]
+ public void LineOfSightCommand( ICommandContext context )
+ {
+ var player = context.Sender!;
+ Core.PlayerManager.GetAlive()
+ .Where(p => p.PlayerID != player.PlayerID)
+ .ToList()
+ .ForEach(targetPlayer => context.Reply($"Line of sight to {targetPlayer.Controller!.PlayerName}: {player.PlayerPawn!.HasLineOfSight(targetPlayer.PlayerPawn!)}"));
+ }
+
public override void Unload()
{
Console.WriteLine("TestPlugin unloaded");