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");