Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions managed/src/SwiftlyS2.Core/Modules/Engine/TraceManager.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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);
}
}
}

Expand All @@ -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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
using SwiftlyS2.Shared.Natives;
using SwiftlyS2.Shared.Natives;

namespace SwiftlyS2.Shared.SchemaDefinitions;

public partial interface CBasePlayerPawn
{
/// <summary>
/// Performs a suicide on the pawn, optionally causing an explosion and allowing forced execution.
/// </summary>
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; }

/// <summary>
/// Performs a suicide on the pawn, optionally causing an explosion and allowing forced execution.
/// </summary>
public void CommitSuicide( bool explode, bool force );

/// <summary>
/// Checks if the target player is within the line of sight of this player.
/// Performs both physical obstruction check and field of view validation.
/// </summary>
/// <param name="targetPlayer">The target player to check visibility for.</param>
/// <param name="fieldOfViewDegrees">Optional field of view in degrees.</param>
/// <returns>True if the target player is visible; otherwise, false.</returns>
public bool HasLineOfSight( CCSPlayerPawn targetPlayer, float? fieldOfViewDegrees = null );
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using SwiftlyS2.Core.Natives;
using SwiftlyS2.Core.Natives;
using SwiftlyS2.Core.Services;
using SwiftlyS2.Shared.Natives;
using SwiftlyS2.Shared.SchemaDefinitions;
Expand All @@ -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);
Expand All @@ -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;
}
}
Expand All @@ -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;
}
}
46 changes: 32 additions & 14 deletions managed/src/SwiftlyS2.Shared/Modules/Commands/ICommandContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
/// <summary>
/// Gets a value indicating whether the command was sent by a player.
/// </summary>
public bool IsSentByPlayer { get; }

/// <summary>
/// Gets the player who sent the command, or null if the command was not sent by a player.
/// </summary>
public IPlayer? Sender { get; }

/// <summary>
/// Gets the command name itself.
/// </summary>
public string Prefix { get; }

/// <summary>
/// Gets a value indicating whether the command should be executed silently without broadcasting to other players.
/// </summary>
public bool IsSlient { get; }

/// <summary>
/// Gets the array of arguments passed with the command.
/// </summary>
public string[] Args { get; }

/// <summary>
/// Sends a reply message to the command sender.
/// </summary>
/// <param name="message">The message to send as a reply.</param>
public void Reply( string message );
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using SwiftlyS2.Shared.Natives;
using SwiftlyS2.Shared.Natives;
using SwiftlyS2.Shared.SchemaDefinitions;

namespace SwiftlyS2.Shared.Services;
Expand Down
26 changes: 12 additions & 14 deletions managed/src/SwiftlyS2.Shared/Modules/Schemas/ISchemaClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,20 @@ namespace SwiftlyS2.Shared.Schemas;

public interface ISchemaClass : INativeHandle
{

/// <summary>
/// Convert this handle to another type.
/// </summary>
/// <typeparam name="K">The type to convert to.</typeparam>
/// <returns>The converted handle.</returns>
K As<K>() where K : ISchemaClass<K>
{
return K.From(Address);
}
/// <summary>
/// Convert this handle to another type.
/// </summary>
/// <typeparam name="K">The type to convert to.</typeparam>
/// <returns>The converted handle.</returns>
public K As<K>() where K : ISchemaClass<K>
{
return K.From(Address);
}
}

public interface ISchemaClass<T> : ISchemaField, ISchemaClass, INativeHandle where T : ISchemaClass<T>
{

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; }
}
83 changes: 49 additions & 34 deletions managed/src/SwiftlyS2.Shared/Natives/Structs/CGameTrace.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -69,23 +69,29 @@ public readonly Vector Direction {
public readonly bool HitEntityByDesignerName<T>( string designerName, out T outEntity, NameMatchType matchType = NameMatchType.StartsWith ) where T : ISchemaClass<T>
{
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<T>();
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)
Expand All @@ -103,38 +109,47 @@ public readonly bool HitEntityByDesignerName<T>( string designerName, NameMatchT

public readonly bool HitPlayer( out IPlayer? player )
{
if (HitEntityByDesignerName<CCSPlayerPawn>("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<T>( out T entity ) where T : ISchemaClass<T>
{
if (T.ClassName == null)
var playerPawn = Entity.As<CCSPlayerPawn>();
if (!playerPawn.IsValid)
{
entity = T.From(IntPtr.Zero);
return false;
}

if (HitEntityByDesignerName<T>(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<T>( out T entity ) where T : ISchemaClass<T>
{
entity = T.From(IntPtr.Zero);
return false;
return T.ClassName != null && HitEntityByDesignerName(T.ClassName, out entity, NameMatchType.Exact);
}

public readonly bool HitEntity<T>() where T : ISchemaClass<T>
{
return HitEntity<T>(out _);
}
}
Loading
Loading