Skip to content

Commit 0c7e6e5

Browse files
NukooooKxnrllaper32
authored
doc: physics query (trace) and clientprefs configuration (#42)
Co-authored-by: Kyle <kyle@kxnrl.com> Co-authored-by: laper32 <laper32@outlook.com>
1 parent 102e8aa commit 0c7e6e5

File tree

20 files changed

+696
-12
lines changed

20 files changed

+696
-12
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<ItemGroup>
3333
<None Include="$(MSBuildThisFileDirectory)icon.png" Pack="true" PackagePath="\" />
3434
<None Include="$(MSBuildThisFileDirectory)README.nuget.md" Pack="true" PackagePath="\" />
35-
<EmbeddedResource Include="$(MSBuildThisFileDirectory)LICENSE" />
35+
<None Include="$(MSBuildThisFileDirectory)LICENSE" Pack="true" PackagePath="\" />
3636
</ItemGroup>
3737

3838
</Project>

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ the copyright holder (Kxnrl) grants the following special exceptions:
2929
itself, its runtime engine, loader, core libraries, generators, and
3030
SDK infrastructure.
3131

32-
b) THIRD-PARTY MODULES (Plugins/Extensions built using ModSharp SDK):
32+
b) THIRD-PARTY MODULES (Modules/Extensions built using ModSharp SDK):
3333
As a special exception, third-party developers creating NEW modules,
3434
plugins, or extensions using the ModSharp SDK, NuGet packages, or
3535
public APIs may choose to license their work under EITHER:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ has served over 4 million players since Sep 2023.
4949
## License
5050

5151
ModSharp is licensed under the AGPL-3.0 License,
52-
Special exceptions are outlined in the [LICENSE](./LICENSE) file inside of the licenses folder.
52+
Special exceptions are outlined in the [LICENSE](./LICENSE) file.

README.nuget.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ has served over 4 million players since Sep 2023.
3131
## License
3232

3333
ModSharp is licensed under the AGPL-3.0 License,
34-
Special exceptions are outlined in the [LICENSE](./LICENSE) file inside of the licenses folder.
34+
Special exceptions are outlined in the [LICENSE](./LICENSE) file.

docfx/docs/codes/native-hook.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
namespace HookExample;
1616

17-
public class HookExample : IModSharpModule
17+
public sealed class HookExample : IModSharpModule
1818
{
1919
public string DisplayName => "Hook Example";
2020
public string DisplayAuthor => "Modsharp dev team";

docfx/docs/codes/trace-native.cs

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
using Microsoft.Extensions.Configuration;
4+
using Sharp.Shared;
5+
using Sharp.Shared.Definition;
6+
using Sharp.Shared.Enums;
7+
using Sharp.Shared.GameEntities;
8+
using Sharp.Shared.Objects;
9+
using Sharp.Shared.Types;
10+
11+
namespace TraceNativeExample;
12+
13+
public sealed class TraceNativeExample : IModSharpModule
14+
{
15+
public string DisplayName => "Trace (Native) example";
16+
public string DisplayAuthor => "Modsharp dev team";
17+
18+
private readonly ISharedSystem _sharedSystem;
19+
20+
private static TraceNativeExample _instance = null!;
21+
22+
public TraceNativeExample(ISharedSystem sharedSystem,
23+
string dllPath,
24+
string sharpPath,
25+
Version version,
26+
IConfiguration coreConfiguration,
27+
bool hotReload)
28+
{
29+
_sharedSystem = sharedSystem;
30+
_instance = this;
31+
}
32+
33+
public bool Init()
34+
{
35+
// client chat/console
36+
_sharedSystem.GetClientManager().InstallCommandCallback("trace_line", OnCommandTraceLine);
37+
_sharedSystem.GetClientManager().InstallCommandCallback("trace_custom", OnCommandTraceCustom);
38+
39+
return true;
40+
}
41+
42+
public void Shutdown()
43+
{
44+
// must uninstall the callbacks on shutdown
45+
_sharedSystem.GetClientManager().RemoveCommandCallback("trace_line", OnCommandTraceLine);
46+
_sharedSystem.GetClientManager().RemoveCommandCallback("trace_custom", OnCommandTraceCustom);
47+
}
48+
49+
private unsafe ECommandAction OnCommandTraceLine(IGameClient client, StringCommand command)
50+
{
51+
if (client.GetPlayerController() is not { } controller
52+
|| controller.GetPlayerPawn() is not { IsValidEntity: true, IsAlive: true } pawn)
53+
{
54+
return ECommandAction.Handled;
55+
}
56+
57+
var eyeAngles = pawn.GetEyeAngles();
58+
59+
// we start from player's eye position
60+
var start = pawn.GetEyePosition();
61+
var direction = eyeAngles.AnglesToVectorForward();
62+
63+
const float maxDistance = 4096.0f;
64+
65+
var end = start + (direction * maxDistance);
66+
67+
// we use bullet attribute here
68+
var attribute = RnQueryShapeAttr.Bullets();
69+
70+
// make it ignore the player themselves, because we start tracing from player's
71+
// eye position and that is within the player's hitbox
72+
attribute.SetEntityToIgnore(pawn, 0);
73+
74+
// this function is called for every entity the trace HITS, letting you decide if you
75+
// want to IGNORE that hit and continue tracing. we'll only use it if the
76+
// command has an argument (e.g., "trace_line filter_on").
77+
// the `delegate* unmanaged` syntax creates a function pointer that the game engine can call directly,
78+
// without .NET runtime overhead.
79+
nint? filter = command.ArgCount > 0
80+
? (nint) (delegate* unmanaged<CTraceFilter*, nint, bool>) (&CTraceFilter_ShouldHitPlayerOnCT)
81+
: null;
82+
83+
controller.Print(HudPrintChannel.Chat, $"Tracing {(filter == null ? "without" : "with")} ShouldHitPlayerOnCT filter");
84+
85+
var traceResult = _sharedSystem.GetPhysicsQueryManager()
86+
.TraceLine(start,
87+
end,
88+
attribute,
89+
filter);
90+
91+
if (traceResult.DidHit())
92+
{
93+
var hitEntity = _sharedSystem.GetEntityManager().MakeEntityFromPointer<IBaseEntity>(traceResult.Entity);
94+
95+
controller.Print(HudPrintChannel.Chat,
96+
$" Hit entity!!! {ChatColor.NewLine}"
97+
+ $" entity classname: {hitEntity.Classname} {ChatColor.NewLine}"
98+
+ $" hit position: {traceResult.EndPosition} {ChatColor.NewLine}"
99+
+ $" fraction: {traceResult.Fraction}");
100+
}
101+
else
102+
{
103+
controller.Print(HudPrintChannel.Chat, "Failed to hit any entity.");
104+
}
105+
106+
return ECommandAction.Handled;
107+
}
108+
109+
private unsafe ECommandAction OnCommandTraceCustom(IGameClient client, StringCommand command)
110+
{
111+
if (client.GetPlayerController() is not { } controller
112+
|| controller.GetPlayerPawn() is not { IsValidEntity: true, IsAlive: true } pawn)
113+
{
114+
return ECommandAction.Handled;
115+
}
116+
117+
var eyeAngles = pawn.GetEyeAngles();
118+
119+
// we start from player's eye position
120+
var start = pawn.GetEyePosition();
121+
var direction = eyeAngles.AnglesToVectorForward();
122+
123+
const float maxDistance = 4096.0f;
124+
125+
var end = start + (direction * maxDistance);
126+
127+
// we create our custom attribute here.
128+
// if you don't want to create a custom one, modsharp has a few built-in attributes:
129+
// RnQueryShapeAttr.Bullets(),
130+
// RnQueryShapeAttr.PlayerMovement(pawn.InteractsWith),
131+
// RnQueryShapeAttr.Knife()
132+
var attribute = new RnQueryShapeAttr
133+
{
134+
// what layers should this query attribute look for
135+
// you can think of it as a "whitelist", if an entity/object
136+
// doesn't have one of the flags in it, the trace will ignore it
137+
138+
// here it is looking for anything that a bullet can interact with
139+
m_nInteractsWith = UsefulInteractionLayers.FireBullets,
140+
141+
// what layers should this query attribute ignore
142+
// think of it as a "blacklist", if an entity/object has one of
143+
// the flags, the trace will ignore it
144+
// we don't want to hurt chickens, let's ignore it :)
145+
m_nInteractsExclude = InteractionLayers.CStrikeChicken,
146+
147+
// what layers should this query represents as
148+
// here we are representing this layer as a player in T
149+
m_nInteractsAs = InteractionLayers.Player | InteractionLayers.CStrikeTeam1,
150+
151+
// hit game entities and static entities
152+
m_nObjectSetMask = RnQueryObjectSet.All,
153+
m_nCollisionGroup = CollisionGroupType.ConditionallySolid,
154+
155+
// if true, will hit solid geometry and entities
156+
HitSolid = true,
157+
158+
// if true, HitSolid will require the query and shape have contacts
159+
HitSolidRequiresGenerateContacts = true,
160+
161+
// if true, will hit trigger entities
162+
HitTrigger = false,
163+
164+
// if true, then ignores if the query and shape entity IDs are in collision pairs
165+
// in other words, it will respect the entities you set to ignore with SetEntityToIgnore
166+
ShouldIgnoreDisabledPairs = true,
167+
168+
// if true, then ignores if both query and shape interact with InteractionLayers.HitBoxes
169+
IgnoreIfBothInteractWithHitBoxes = false,
170+
171+
// if true, will hit any objects in any conditions
172+
ForceHitEverything = false,
173+
174+
// not sure what it does but the game sets it to true by default
175+
Unknown = true,
176+
};
177+
178+
// ignore ourselves because the query starts from ourselves
179+
// you can only set the index with 0 or 1
180+
var index = 0;
181+
attribute.SetEntityToIgnore(pawn, index);
182+
183+
// we create a hull shape here
184+
// if mins and maxs are identical the game will treat it as ShapeLine
185+
var hull = new TraceShapeHull { Mins = new Vector(-32, -32, -32), Maxs = new Vector(32, 32, 32) };
186+
187+
// or you can create the following shapes
188+
/*
189+
var line = new TraceShapeLine
190+
{
191+
};
192+
193+
var sphere = new TraceShapeSphere
194+
{
195+
};
196+
197+
var capsule = new TraceShapeCapsule
198+
{
199+
};
200+
*/
201+
202+
nint? filter = command.ArgCount > 0
203+
? (nint) (delegate* unmanaged<CTraceFilter*, nint, bool>) (&CTraceFilter_ShouldHitPlayerOnCT)
204+
: null;
205+
206+
controller.Print(HudPrintChannel.Chat, $"Tracing {(filter == null ? "without" : "with")} ShouldHitPlayerOnCT filter");
207+
208+
var traceResult = _sharedSystem.GetPhysicsQueryManager()
209+
.TraceShape(new TraceShapeRay(hull), start, end, attribute, filter);
210+
211+
if (traceResult.DidHit())
212+
{
213+
var hitEntity = _sharedSystem.GetEntityManager().MakeEntityFromPointer<IBaseEntity>(traceResult.Entity);
214+
215+
controller.Print(HudPrintChannel.Chat,
216+
$" Hit entity!!! {ChatColor.NewLine}"
217+
+ $" entity classname: {hitEntity.Classname} {ChatColor.NewLine}"
218+
+ $" hit position: {traceResult.EndPosition} {ChatColor.NewLine}"
219+
+ $" fraction: {traceResult.Fraction}");
220+
}
221+
else
222+
{
223+
controller.Print(HudPrintChannel.Chat, "Failed to hit any entity.");
224+
}
225+
226+
return ECommandAction.Handled;
227+
}
228+
229+
[UnmanagedCallersOnly]
230+
private static unsafe bool CTraceFilter_ShouldHitPlayerOnCT(CTraceFilter* filter, nint entityPtr)
231+
{
232+
// not a valid entity, don't hit, although this should never happen, just a safeguard
233+
if (_instance._sharedSystem.GetEntityManager().MakeEntityFromPointer<IBaseEntity>(entityPtr) is not
234+
{
235+
IsValidEntity: true,
236+
} entity)
237+
{
238+
return false;
239+
}
240+
241+
// custom logic here
242+
// if we hit a player pawn
243+
if (entity is { IsPlayerPawn: true, IsAlive: true })
244+
{
245+
// we only want to accept the hit if they are on the CT team.
246+
// if they are on the T team, this function returns false, and the trace will continue
247+
// through them until it hits a CT player or a wall.
248+
return entity.Team == CStrikeTeam.CT;
249+
}
250+
251+
return true;
252+
}
253+
}

0 commit comments

Comments
 (0)