mirror of
https://github.com/MSWS/TTT.git
synced 2025-12-07 14:56:34 -08:00
Compare commits
133 Commits
0.9.0
...
0.10.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b5563aa8e | ||
|
|
2058d0c780 | ||
|
|
ee7f34b435 | ||
|
|
783f6da6dd | ||
|
|
f245c61d01 | ||
|
|
b4076934d8 | ||
|
|
21b869507b | ||
|
|
fca81c0577 | ||
|
|
db8fe8f069 | ||
|
|
dce6d759a8 | ||
|
|
f028074939 | ||
|
|
1d96be0cb0 | ||
|
|
0cbb931aaa | ||
|
|
6c2bd538a9 | ||
|
|
6b0dcbd42f | ||
|
|
c512b60260 | ||
|
|
295f2bcad0 | ||
|
|
5b46fb1282 | ||
|
|
4b3d9335b5 | ||
|
|
ec48a9b243 | ||
|
|
254e5539b7 | ||
|
|
547228d09a | ||
|
|
99e4c6fc07 | ||
|
|
52b8d1d2ff | ||
|
|
e901d82153 | ||
|
|
f56106f125 | ||
|
|
57660e0957 | ||
|
|
1ecef5767f | ||
|
|
d1359b79c0 | ||
|
|
216d5a9d5a | ||
|
|
5d2bead09c | ||
|
|
a0ce6aa53e | ||
|
|
cb626d7cfa | ||
|
|
d83ac95245 | ||
|
|
8db9b3154d | ||
|
|
3abe6153e3 | ||
|
|
fc25405a81 | ||
|
|
da3a94f3b7 | ||
|
|
4bf7b0cd13 | ||
|
|
758d3fe13c | ||
|
|
9045913074 | ||
|
|
8748401b6b | ||
|
|
7d5218914f | ||
|
|
137144a052 | ||
|
|
61d7f667ff | ||
|
|
f53c00c4d8 | ||
|
|
3f5a675af1 | ||
|
|
48ec384b38 | ||
|
|
2edee31419 | ||
|
|
d4006e5750 | ||
|
|
a1a37452ef | ||
|
|
d9f002febe | ||
|
|
26cca670ee | ||
|
|
cafd050a85 | ||
|
|
a28c3aa0dd | ||
|
|
2918a9965d | ||
|
|
b57630b899 | ||
|
|
65c12696ed | ||
|
|
3206c30078 | ||
|
|
f9f0c4e954 | ||
|
|
45f492e44e | ||
|
|
c6f45276c3 | ||
|
|
9eef949501 | ||
|
|
f1c6a784d1 | ||
|
|
112422e479 | ||
|
|
6abdce7246 | ||
|
|
d3a021f40b | ||
|
|
f657599a0e | ||
|
|
4d66afaec9 | ||
|
|
0667652ed4 | ||
|
|
b41092fd69 | ||
|
|
cc345ac010 | ||
|
|
fa1d732724 | ||
|
|
4d5109b6be | ||
|
|
00970f6789 | ||
|
|
29d2e8a46c | ||
|
|
1d6526730a | ||
|
|
98dc08c667 | ||
|
|
4a50d662af | ||
|
|
45727da462 | ||
|
|
fc2104e71a | ||
|
|
375108dd85 | ||
|
|
aded3fb6a2 | ||
|
|
519578dcd9 | ||
|
|
2ebc25a692 | ||
|
|
1810bd1473 | ||
|
|
8ea14e7960 | ||
|
|
3dc3cd08f4 | ||
|
|
78e0b64bb3 | ||
|
|
5977d87216 | ||
|
|
8cdb19f23c | ||
|
|
a44b5b00a2 | ||
|
|
352de1f667 | ||
|
|
1b8d201567 | ||
|
|
f753cb01c4 | ||
|
|
529af8c776 | ||
|
|
6016f62931 | ||
|
|
50f5a835de | ||
|
|
46104f142f | ||
|
|
6b7d89dbf0 | ||
|
|
332b6e6501 | ||
|
|
8c385934b3 | ||
|
|
04f79551f7 | ||
|
|
b08f9234ff | ||
|
|
87b83edb55 | ||
|
|
de7639d986 | ||
|
|
a90e33b69d | ||
|
|
700074b130 | ||
|
|
6c06071d8a | ||
|
|
d89c70c41e | ||
|
|
58012cb112 | ||
|
|
febf34fcda | ||
|
|
44dafdd606 | ||
|
|
330db6aef1 | ||
|
|
cddfdf3ebc | ||
|
|
deb0d2e825 | ||
|
|
089564c4d6 | ||
|
|
871f47376a | ||
|
|
7621d15bcc | ||
|
|
a0111b5bea | ||
|
|
14c4c85cfd | ||
|
|
e2383c90a8 | ||
|
|
9db57d726d | ||
|
|
47e63bf686 | ||
|
|
8835a76e67 | ||
|
|
731f58755d | ||
|
|
824993fb16 | ||
|
|
6a67eb1141 | ||
|
|
670643998e | ||
|
|
70e2b44941 | ||
|
|
c5cd646bee | ||
|
|
fbda4fe38b | ||
|
|
49d45a12b9 |
14
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report an unintended behavior
|
||||
title: ''
|
||||
labels: 'Type: Bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Summary**
|
||||
|
||||
**Reproduction**
|
||||
|
||||
**Expected Behavior**
|
||||
8
.github/workflows/dotnet.yml
vendored
8
.github/workflows/dotnet.yml
vendored
@@ -13,14 +13,14 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
show-progress: true
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
@@ -51,10 +51,10 @@ jobs:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
|
||||
27
.github/workflows/nightly.yml
vendored
27
.github/workflows/nightly.yml
vendored
@@ -48,11 +48,23 @@ jobs:
|
||||
dotnet restore Locale/Locale.csproj
|
||||
dotnet build Locale/Locale.csproj --no-restore -c Release
|
||||
cp lang/*.json build/TTT/lang
|
||||
|
||||
- name: Copy Gamedata
|
||||
run: |
|
||||
mkdir -p build/TTT/gamedata
|
||||
cp -r TTT/CS2/gamedata/* build/TTT/gamedata
|
||||
|
||||
- name: Publish Plugin
|
||||
run: |
|
||||
dotnet restore TTT/Plugin/Plugin.csproj
|
||||
dotnet publish TTT/Plugin/Plugin.csproj --no-restore -c Release -o build/TTT
|
||||
if [ "${GITHUB_REF##*/}" = "dev" ]; then
|
||||
dotnet publish TTT/Plugin/Plugin.csproj --no-restore -c Debug -o build/TTT
|
||||
elif [ "${GITHUB_REF##*/}" = "main" ]; then
|
||||
dotnet publish TTT/Plugin/Plugin.csproj --no-restore -c Release -o build/TTT
|
||||
else
|
||||
echo "Branch not recognized, skipping publish."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -61,3 +73,16 @@ jobs:
|
||||
path: build/TTT
|
||||
if-no-files-found: error
|
||||
|
||||
post_webhook:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
|
||||
steps:
|
||||
- name: POST Webhook
|
||||
run: |
|
||||
curl -X POST \
|
||||
--fail \
|
||||
-F token=${{ secrets.GITLAB_SECRET_TOKEN }} \
|
||||
-F ref=dev \
|
||||
https://gitlab.edgegamers.io/api/v4/projects/2640/trigger/pipeline
|
||||
23
.github/workflows/release.yml
vendored
23
.github/workflows/release.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
auto-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -34,6 +34,11 @@ jobs:
|
||||
dotnet build Locale/Locale.csproj --no-restore -c Release
|
||||
cp lang/*.json build/TTT/lang
|
||||
|
||||
- name: Copy Gamedata
|
||||
run: |
|
||||
mkdir -p build/TTT/gamedata
|
||||
cp -r TTT/CS2/gamedata/* build/TTT/gamedata
|
||||
|
||||
- name: Publish Plugin
|
||||
run: |
|
||||
dotnet restore TTT/Plugin/Plugin.csproj
|
||||
@@ -42,8 +47,8 @@ jobs:
|
||||
- name: Zip Artifacts
|
||||
run: |
|
||||
cd build/TTT
|
||||
zip -r TTT-${{ steps.gitversion.outputs.MajorMinorPatch }}.zip *
|
||||
|
||||
zip -r TTT-${{ steps.gitversion.outputs.fullSemVer }}.zip *
|
||||
|
||||
# 2. Get latest tag
|
||||
- name: Get latest tag
|
||||
id: latest_tag
|
||||
@@ -56,12 +61,12 @@ jobs:
|
||||
|
||||
# 3. Tag if new version
|
||||
- name: Create and push new tag
|
||||
if: steps.gitversion.outputs.MajorMinorPatch != steps.latest_tag.outputs.tag
|
||||
if: steps.gitversion.outputs.fullSemVer != steps.latest_tag.outputs.tag
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git tag ${{ steps.gitversion.outputs.MajorMinorPatch }}
|
||||
git push origin ${{ steps.gitversion.outputs.MajorMinorPatch }}
|
||||
git tag ${{ steps.gitversion.outputs.fullSemVer }}
|
||||
git push origin ${{ steps.gitversion.outputs.fullSemVer }}
|
||||
|
||||
# 4. Determine previous tag for changelog
|
||||
- name: Determine previous relevant tag
|
||||
@@ -78,16 +83,16 @@ jobs:
|
||||
# 5. Generate changelog
|
||||
- name: Generate changelog
|
||||
run: |
|
||||
gh api repos/${{ github.repository }}/compare/${{ steps.prev_tag.outputs.tag }}...${{ steps.gitversion.outputs.MajorMinorPatch }} \
|
||||
gh api repos/${{ github.repository }}/compare/${{ steps.prev_tag.outputs.tag }}...${{ steps.gitversion.outputs.fullSemVer }} \
|
||||
--jq '.commits[].commit.message' > CHANGELOG.md
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
# 6. Create release
|
||||
- name: Create GitHub release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ steps.gitversion.outputs.MajorMinorPatch }}
|
||||
tag_name: ${{ steps.gitversion.outputs.fullSemVer }}
|
||||
body_path: CHANGELOG.md
|
||||
prerelease: ${{ github.ref_name != 'main' }}
|
||||
files: build/TTT/*.zip
|
||||
|
||||
42
GitVersion.yml
Normal file
42
GitVersion.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
mode: ContinuousDeployment
|
||||
tag-prefix: "" # allow bare numeric tags like 2.0.0
|
||||
commit-message-incrementing: Enabled
|
||||
|
||||
assembly-versioning-scheme: MajorMinorPatch
|
||||
|
||||
# Never allow commit messages to trigger a major bump
|
||||
major-version-bump-message: "(?!)"
|
||||
|
||||
# Opt-in bumps via tokens
|
||||
minor-version-bump-message: \+semver:\s?minor
|
||||
patch-version-bump-message: \+semver:\s?patch
|
||||
|
||||
branches:
|
||||
main:
|
||||
label: "" # clears the default prerelease label
|
||||
increment: None # no automatic bump unless +semver token
|
||||
prevent-increment:
|
||||
of-merged-branch: true
|
||||
when-branch-merged: true
|
||||
when-current-commit-tagged: true
|
||||
develop:
|
||||
label: "dev"
|
||||
increment: None
|
||||
prevent-increment:
|
||||
of-merged-branch: true
|
||||
when-branch-merged: true
|
||||
when-current-commit-tagged: true
|
||||
feature:
|
||||
label: "feat"
|
||||
increment: None
|
||||
release:
|
||||
label: "rc"
|
||||
increment: None
|
||||
hotfix:
|
||||
label: "hotfix"
|
||||
increment: None
|
||||
pull-request:
|
||||
label: "pr"
|
||||
increment: None
|
||||
|
||||
assembly-informational-format: "{FullSemVer}+Branch.{BranchName}.Sha.{ShortSha}"
|
||||
@@ -1,6 +1,7 @@
|
||||
| Package | Version | License Information Origin | License Expression | License Url | Copyright | Authors | Package Project Url |
|
||||
|-------------------------------------------------------|----------|----------------------------|--------------------|-----------------------------------------|-------------------------------------------------|----------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| ----------------------------------------------------- | -------- | -------------------------- | ------------------ | --------------------------------------- | ----------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| CounterStrikeSharp.API | 1.0.332 | Expression | GPL-3.0-only | https://licenses.nuget.org/GPL-3.0-only | | Roflmuffin | http://docs.cssharp.dev/ |
|
||||
| CounterStrikeSharp.API | 1.0.340 | Expression | GPL-3.0-only | https://licenses.nuget.org/GPL-3.0-only | | Roflmuffin | http://docs.cssharp.dev/ |
|
||||
| JetBrains.Annotations | 2025.2.0 | Expression | MIT | https://licenses.nuget.org/MIT | Copyright (c) 2016-2025 JetBrains s.r.o. | JetBrains | https://www.jetbrains.com/help/resharper/Code_Analysis__Code_Annotations.html |
|
||||
| Microsoft.Extensions.DependencyInjection.Abstractions | 9.0.7 | Expression | MIT | https://licenses.nuget.org/MIT | © Microsoft Corporation. All rights reserved. | Microsoft | https://dot.net/ |
|
||||
| Microsoft.Extensions.Localization.Abstractions | 8.0.3 | Expression | MIT | https://licenses.nuget.org/MIT | © Microsoft Corporation. All rights reserved. | Microsoft | https://asp.net/ |
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.332"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.3"/>
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
<PackageReference Include="YamlDotNet" Version="16.3.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -42,7 +42,10 @@ public partial class StringLocalizer : IMsgLocalizer {
|
||||
|
||||
private LocalizedString getString(string name, params object[] arguments) {
|
||||
// Get the localized value
|
||||
var value = localizer[name].Value;
|
||||
string value;
|
||||
try { value = localizer[name].Value; } catch (NullReferenceException e) {
|
||||
return new LocalizedString(name, name, true);
|
||||
}
|
||||
|
||||
// Replace placeholders like %key% with their respective values
|
||||
var matches = percentRegex().Matches(value);
|
||||
@@ -114,10 +117,14 @@ public partial class StringLocalizer : IMsgLocalizer {
|
||||
|
||||
value = value.Replace("%s%", "s");
|
||||
|
||||
// We have to do this chicanery due to support colors in the string
|
||||
value = handleTrailingS(value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static string handleTrailingS(string value) {
|
||||
var trailingIndex = -1;
|
||||
|
||||
// We have to do this chicanery due to supporting colors in the string
|
||||
|
||||
while ((trailingIndex =
|
||||
value.IndexOf("'s", trailingIndex + 1, StringComparison.Ordinal)) != -1) {
|
||||
var startingWordBoundary = value[..trailingIndex].LastIndexOf(' ');
|
||||
|
||||
12
README.md
12
README.md
@@ -10,10 +10,10 @@ survive while eliminating the traitors among them.
|
||||
## Features
|
||||
|
||||
- [X] Unit Testing
|
||||
- [ ] Basic Gameplay
|
||||
- [ ] Traitors
|
||||
- [ ] Detectives
|
||||
- [ ] Innocents
|
||||
- [X] Basic Gameplay
|
||||
- [X] Traitors
|
||||
- [X] Detectives
|
||||
- [X] Innocents
|
||||
- [ ] Shop
|
||||
- [ ] Karma
|
||||
- [ ] Statistics
|
||||
@@ -44,8 +44,8 @@ Due to this project being primarily developed with Counter-Strike 2 (and more
|
||||
specifically, [CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp)) in mind, localization has been
|
||||
built with flat-file storage based around YML/JSON.
|
||||
|
||||
In short, we write our locales in `en.yml`, run `Locale.csproj` to convert and combine all `**/Lang/en.yml` -> a master
|
||||
`lang/en.json`, and then run our tests / release pipeliens with it.
|
||||
In short, we write our locales in `en.yml`, run `Locale.csproj` to convert and combine all `**/Lang/en.yml` into a master
|
||||
`lang/en.json`, and then run our tests / release pipelines with it.
|
||||
|
||||
It is recommend to read the [Locale README](./Locale/README.md) for more information on how to use it.
|
||||
|
||||
|
||||
6
TTT.sln
6
TTT.sln
@@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Locale", "Locale\Locale.csp
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shop", "TTT\Shop\Shop.csproj", "{478416D7-4996-41CC-BDDF-5BF50B505D0F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Karma", "TTT\Karma\Karma.csproj", "{AFC791EC-750C-423F-9F35-87636657E990}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -60,6 +62,10 @@ Global
|
||||
{478416D7-4996-41CC-BDDF-5BF50B505D0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{478416D7-4996-41CC-BDDF-5BF50B505D0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{478416D7-4996-41CC-BDDF-5BF50B505D0F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AFC791EC-750C-423F-9F35-87636657E990}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AFC791EC-750C-423F-9F35-87636657E990}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AFC791EC-750C-423F-9F35-87636657E990}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AFC791EC-750C-423F-9F35-87636657E990}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
EndGlobalSection
|
||||
|
||||
@@ -5,4 +5,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>TTT.API</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -7,7 +7,8 @@ public interface ICommandInfo {
|
||||
string[] Args { get; }
|
||||
IOnlinePlayer? CallingPlayer { get; }
|
||||
CommandCallingContext CallingContext { get; set; }
|
||||
string GetCommandString => string.Join(' ', Args);
|
||||
string CommandString => string.Join(' ', Args);
|
||||
int ArgCount => Args.Length;
|
||||
void ReplySync(string message);
|
||||
ICommandInfo Skip(int count = 1);
|
||||
}
|
||||
@@ -6,10 +6,13 @@ namespace TTT.API.Command;
|
||||
/// An interface that allows for registering and processing commands.
|
||||
/// </summary>
|
||||
public interface ICommandManager {
|
||||
ISet<ICommand> Commands { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Registers a command with the manager.
|
||||
/// </summary>
|
||||
/// <param name="command">True if the command was successfully registered.</param>
|
||||
[Obsolete("Registration is done via the ServiceProvider now.")]
|
||||
bool RegisterCommand(ICommand command);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
namespace TTT.API.Events;
|
||||
|
||||
public interface IEventBus {
|
||||
[Obsolete("Registration should be done via the ServiceProvider")]
|
||||
void RegisterListener(IListener listener);
|
||||
|
||||
void UnregisterListener(IListener listener);
|
||||
|
||||
void Dispatch(Event ev);
|
||||
Task Dispatch(Event ev);
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
namespace TTT.API.Events;
|
||||
|
||||
public interface IListener : IDisposable;
|
||||
public interface IListener : ITerrorModule {
|
||||
void ITerrorModule.Start() { }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Events;
|
||||
|
||||
namespace TTT.API.Extensions;
|
||||
@@ -8,60 +9,35 @@ namespace TTT.API.Extensions;
|
||||
/// its interface, allowing us to get the list of all modules simply using ITerrorModule.
|
||||
/// </summary>
|
||||
public static class ServiceCollectionExtensions {
|
||||
public static void AddPluginBehavior<TExtension>(
|
||||
this IServiceCollection collection)
|
||||
where TExtension : class, IPluginModule {
|
||||
// Add the root extension itself as a scoped service.
|
||||
// This means every time Load is called in the main Jailbreak loader,
|
||||
// the extension will be fetched and kept as a singleton for the duration
|
||||
// until "Unload" is called.
|
||||
// collection.AddScoped<IPluginBehavior, PluginBehavior>();
|
||||
// collection.AddScoped<TExtension>();
|
||||
// collection.AddTransient<IPluginModule, TExtension>(provider
|
||||
// => provider.GetRequiredService<TExtension>());
|
||||
// collection.AddModBehavior<TExtension>();
|
||||
collection.AddScoped<TExtension>();
|
||||
collection.AddTransient<ITerrorModule>(p
|
||||
=> p.GetRequiredService<TExtension>());
|
||||
collection.AddTransient<IPluginModule, TExtension>(p
|
||||
=> p.GetRequiredService<TExtension>());
|
||||
}
|
||||
|
||||
public static void AddPluginBehavior<TInterface, TExtension>(
|
||||
this IServiceCollection collection)
|
||||
where TExtension : class, TInterface, IPluginModule
|
||||
where TInterface : class {
|
||||
// Add the root extension itself as a scoped service.
|
||||
// This means every time Load is called in the main Jailbreak loader,
|
||||
// the extension will be fetched and kept as a singleton for the duration
|
||||
// until "Unload" is called.
|
||||
// collection.AddScoped<IPluginBehavior, PluginBehavior>();
|
||||
// collection.AddScoped<TExtension>();
|
||||
// collection.AddTransient<IPluginModule, TExtension>(provider
|
||||
// => provider.GetRequiredService<TExtension>());
|
||||
// collection.AddModBehavior<TExtension>();
|
||||
collection.AddPluginBehavior<TExtension>();
|
||||
collection.AddTransient<TInterface, TExtension>(p
|
||||
=> p.GetRequiredService<TExtension>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a <see cref="ITerrorModule" /> to the global service collection
|
||||
/// </summary>
|
||||
/// <param name="collection"></param>
|
||||
/// <typeparam name="TExtension"></typeparam>
|
||||
public static void AddModBehavior<TExtension>(
|
||||
this IServiceCollection collection)
|
||||
where TExtension : class, ITerrorModule {
|
||||
// Add the root extension itself as a scoped service.
|
||||
// This means every time Load is called in the main Jailbreak loader,
|
||||
// the extension will be fetched and kept as a singleton for the duration
|
||||
// until "Unload" is called.
|
||||
|
||||
if (typeof(IPluginModule).IsAssignableFrom(typeof(TExtension)))
|
||||
if (typeof(TExtension).IsAssignableTo(typeof(IPluginModule))) {
|
||||
# if DEBUG
|
||||
Console.WriteLine(
|
||||
$"[DEBUG] Registering {typeof(TExtension).Name} as IPluginModule");
|
||||
# endif
|
||||
collection.AddTransient<IPluginModule>(provider
|
||||
=> (provider.GetRequiredService<TExtension>() as IPluginModule)!);
|
||||
}
|
||||
|
||||
if (typeof(TExtension).IsAssignableTo(typeof(IListener))) {
|
||||
#if DEBUG
|
||||
Console.WriteLine(
|
||||
$"[DEBUG] Registering {typeof(TExtension).Name} as IListener");
|
||||
# endif
|
||||
collection.AddTransient<IListener>(provider
|
||||
=> (provider.GetRequiredService<TExtension>() as IListener)!);
|
||||
}
|
||||
|
||||
if (typeof(TExtension).IsAssignableTo(typeof(ICommand))) {
|
||||
#if DEBUG
|
||||
Console.WriteLine(
|
||||
$"[DEBUG] Registering {typeof(TExtension).Name} as ICommand");
|
||||
#endif
|
||||
collection.AddTransient<ICommand>(provider
|
||||
=> (provider.GetRequiredService<TExtension>() as ICommand)!);
|
||||
}
|
||||
|
||||
collection.AddScoped<TExtension>();
|
||||
|
||||
@@ -87,9 +63,4 @@ public static class ServiceCollectionExtensions {
|
||||
collection.AddTransient<TInterface, TExtension>(p
|
||||
=> p.GetRequiredService<TExtension>());
|
||||
}
|
||||
|
||||
public static void AddListener<TListener>(this IServiceCollection collection)
|
||||
where TListener : class, IListener {
|
||||
collection.AddScoped<IListener, TListener>();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,26 @@
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Role;
|
||||
|
||||
namespace TTT.API.Game;
|
||||
|
||||
public interface IAction {
|
||||
IPlayer Player { get; }
|
||||
IPlayer? Other { get; }
|
||||
IRole? PlayerRole { get; }
|
||||
IRole? OtherRole { get; }
|
||||
string Id { get; }
|
||||
string Verb { get; }
|
||||
string Details { get; }
|
||||
|
||||
public string Format() {
|
||||
var pRole = PlayerRole != null ?
|
||||
$" [{PlayerRole.Name.First(char.IsAsciiLetter)}]" :
|
||||
"";
|
||||
var oRole = OtherRole != null ?
|
||||
$" [{OtherRole.Name.First(char.IsAsciiLetter)}]" :
|
||||
"";
|
||||
return Other is not null ?
|
||||
$"{Player} {Verb} {Other} {Details}" :
|
||||
$"{Player} {Verb} {Details}";
|
||||
$"{Player}{pRole} {Verb} {Other}{oRole} {Details}" :
|
||||
$"{Player}{pRole} {Verb} {Details}";
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,14 @@ public interface IGame : IDisposable {
|
||||
/// Attempts to start a game.
|
||||
/// Depending on implementation, this may start a countdown or immediately start the game.
|
||||
/// </summary>
|
||||
/// <param name="countdown"></param>
|
||||
/// <param name="countdown">TimeSpan for countdown, null means start immediately</param>
|
||||
IObservable<long>? Start(TimeSpan? countdown = null);
|
||||
|
||||
void EndGame(EndReason? reason = null);
|
||||
|
||||
bool CheckEndConditions();
|
||||
|
||||
[Obsolete("This method is ambiguous, check the game state directly.")]
|
||||
bool IsInProgress() { return State is State.COUNTDOWN or State.IN_PROGRESS; }
|
||||
|
||||
ISet<IOnlinePlayer> GetAlive() {
|
||||
|
||||
@@ -10,6 +10,7 @@ public interface IGameManager : IDisposable {
|
||||
|
||||
IGame? CreateGame();
|
||||
|
||||
[Obsolete("This method is ambiguous, check the game state directly.")]
|
||||
bool IsGameActive() {
|
||||
return ActiveGame is not null && ActiveGame.IsInProgress();
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace TTT.API;
|
||||
|
||||
public interface ITerrorApi : ITerrorModule {
|
||||
IServiceProvider Services { get; }
|
||||
string ITerrorModule.Name => "Core";
|
||||
string ITerrorModule.Version => GitVersionInformation.FullSemVer;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
namespace TTT.API;
|
||||
|
||||
public interface ITerrorModule : IDisposable {
|
||||
string Name { get; }
|
||||
string Version { get; }
|
||||
string Name => GetType().Name;
|
||||
string Version => GitVersionInformation.FullSemVer;
|
||||
|
||||
void Start();
|
||||
}
|
||||
@@ -2,6 +2,11 @@ namespace TTT.API.Player;
|
||||
|
||||
public interface IPlayerFinder {
|
||||
public IOnlinePlayer AddPlayer(IOnlinePlayer player);
|
||||
|
||||
public void AddPlayers(params IOnlinePlayer[] players) {
|
||||
foreach (var p in players) AddPlayer(p);
|
||||
}
|
||||
|
||||
public IPlayer RemovePlayer(IPlayer player);
|
||||
|
||||
ISet<IOnlinePlayer> GetOnline();
|
||||
|
||||
@@ -19,7 +19,11 @@ public interface IRoleAssigner : IKeyedStorage<IPlayer, ICollection<IRole>>,
|
||||
/// <param name="roles"></param>
|
||||
public void AssignRoles(ISet<IOnlinePlayer> players, IList<IRole> roles);
|
||||
|
||||
public void SetRole(IOnlinePlayer player, IRole role) {
|
||||
Write(player, new List<IRole> { role }).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public ICollection<IRole> GetRoles(IPlayer player) {
|
||||
return Load(player).GetAwaiter().GetResult() ?? Array.Empty<IRole>();
|
||||
return Load(player).GetAwaiter().GetResult() ?? [];
|
||||
}
|
||||
}
|
||||
9
TTT/CS2/API/IAliveSpoofer.cs
Normal file
9
TTT/CS2/API/IAliveSpoofer.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
|
||||
namespace TTT.CS2.API;
|
||||
|
||||
public interface IAliveSpoofer {
|
||||
ISet<CCSPlayerController> FakeAlivePlayers { get; }
|
||||
void SpoofAlive(CCSPlayerController player);
|
||||
void UnspoofAlive(CCSPlayerController player);
|
||||
}
|
||||
24
TTT/CS2/API/IBodyTracker.cs
Normal file
24
TTT/CS2/API/IBodyTracker.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using TTT.Game;
|
||||
|
||||
namespace TTT.CS2.API;
|
||||
|
||||
public interface IBodyTracker {
|
||||
public IDictionary<IBody, CRagdollProp> Bodies { get; }
|
||||
|
||||
public IBody? ReverseLookup(CRagdollProp ragdoll) {
|
||||
return Bodies.FirstOrDefault(x => x.Value == ragdoll).Key;
|
||||
}
|
||||
|
||||
public bool TryReverseLookup(CRagdollProp ragdoll,
|
||||
[MaybeNullWhen(false)] out IBody body) {
|
||||
body = ReverseLookup(ragdoll);
|
||||
return body != null;
|
||||
}
|
||||
|
||||
public bool TryLookup(string id, out IBody? body) {
|
||||
body = Bodies.Keys.FirstOrDefault(x => x.Id == id);
|
||||
return body != null;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,12 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\API\API.csproj"/>
|
||||
<ProjectReference Include="..\Game\Game.csproj"/>
|
||||
<ProjectReference Include="..\Karma\Karma.csproj"/>
|
||||
<ProjectReference Include="..\Shop\Shop.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="RayTrace\"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Messages;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Role;
|
||||
using TTT.Game;
|
||||
using TTT.Game.Roles;
|
||||
using TTT.Locale;
|
||||
|
||||
namespace TTT.CS2;
|
||||
|
||||
public class CS2Body(CRagdollProp ragdoll, IPlayer player) : IBody {
|
||||
public class CS2Body(IServiceProvider provider, CRagdollProp ragdoll,
|
||||
IPlayer player) : IBody {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
private readonly IMsgLocalizer locale =
|
||||
provider.GetRequiredService<IMsgLocalizer>();
|
||||
|
||||
private readonly IMessenger messenger =
|
||||
provider.GetRequiredService<IMessenger>();
|
||||
|
||||
private readonly IRoleAssigner roles =
|
||||
provider.GetRequiredService<IRoleAssigner>();
|
||||
|
||||
public CRagdollProp Ragdoll { get; } = ragdoll;
|
||||
public IPlayer OfPlayer { get; } = player;
|
||||
public bool IsIdentified { get; set; }
|
||||
@@ -28,9 +45,4 @@ public class CS2Body(CRagdollProp ragdoll, IPlayer player) : IBody {
|
||||
Killer = killer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CS2Body Identified(bool identified = true) {
|
||||
IsIdentified = identified;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
15
TTT/CS2/CS2Logger.cs
Normal file
15
TTT/CS2/CS2Logger.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using TTT.API.Player;
|
||||
using TTT.Game.Loggers;
|
||||
|
||||
namespace TTT.CS2;
|
||||
|
||||
public class CS2Logger(IServiceProvider provider) : SimpleLogger(provider) {
|
||||
public override void PrintLogs() {
|
||||
Server.NextWorldUpdate(() => base.PrintLogs());
|
||||
}
|
||||
|
||||
public override void PrintLogs(IOnlinePlayer? player) {
|
||||
Server.NextWorldUpdate(() => base.PrintLogs(player));
|
||||
}
|
||||
}
|
||||
@@ -6,51 +6,74 @@ using TTT.API.Game;
|
||||
using TTT.API.Messages;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Storage;
|
||||
using TTT.CS2.API;
|
||||
using TTT.CS2.Command;
|
||||
using TTT.CS2.Command.Test;
|
||||
using TTT.CS2.Configs;
|
||||
using TTT.CS2.Configs.ShopItems;
|
||||
using TTT.CS2.Game;
|
||||
using TTT.CS2.GameHandlers;
|
||||
using TTT.CS2.GameHandlers.DamageCancelers;
|
||||
using TTT.CS2.Hats;
|
||||
using TTT.CS2.lang;
|
||||
using TTT.CS2.Listeners;
|
||||
using TTT.CS2.Player;
|
||||
using TTT.Game;
|
||||
using TTT.Locale;
|
||||
using TTT.Shop;
|
||||
using TTT.Shop.Items;
|
||||
|
||||
namespace TTT.CS2;
|
||||
|
||||
public static class CS2ServiceCollection {
|
||||
public static void AddCS2Services(this IServiceCollection collection) {
|
||||
// Base Requirements
|
||||
collection.AddScoped<IGameManager, CS2GameManager>();
|
||||
|
||||
// TTT - CS2 Specific requirements
|
||||
collection
|
||||
.AddModBehavior<IPlayerConverter<CCSPlayerController>,
|
||||
CCPlayerConverter>();
|
||||
collection.AddScoped<IPlayerFinder, CS2PlayerFinder>();
|
||||
collection.AddModBehavior<ICommandManager, CS2CommandManager>();
|
||||
collection.AddModBehavior<IAliveSpoofer, CS2AliveSpoofer>();
|
||||
|
||||
// Configs
|
||||
collection.AddModBehavior<IStorage<TTTConfig>, CS2GameConfig>();
|
||||
collection.AddPluginBehavior<ICommandManager, CS2CommandManager>();
|
||||
collection.AddScoped<IMessenger, CS2Messenger>();
|
||||
collection.AddScoped<IInventoryManager, CS2InventoryManager>();
|
||||
collection.AddModBehavior<IStorage<ShopConfig>, CS2ShopConfig>();
|
||||
collection
|
||||
.AddModBehavior<IStorage<OneShotDeagleConfig>, CS2OneShotDeagleConfig>();
|
||||
|
||||
// TTT - CS2 Specific optionals
|
||||
collection.AddScoped<ITextSpawner, TextSpawner>();
|
||||
|
||||
// GameHandlers
|
||||
collection.AddPluginBehavior<PlayerConnectionsHandler>();
|
||||
collection.AddPluginBehavior<RoundEndHandler>();
|
||||
collection.AddPluginBehavior<RoundStartHandler>();
|
||||
collection.AddPluginBehavior<CombatHandler>();
|
||||
collection.AddPluginBehavior<PropMover>();
|
||||
collection.AddPluginBehavior<BodySpawner>();
|
||||
collection.AddPluginBehavior<RoleIconsHandler>();
|
||||
collection.AddModBehavior<BodySpawner>();
|
||||
collection.AddModBehavior<CombatHandler>();
|
||||
collection.AddModBehavior<DamageCanceler>();
|
||||
collection.AddModBehavior<PlayerConnectionsHandler>();
|
||||
collection.AddModBehavior<PropMover>();
|
||||
collection.AddModBehavior<RoleIconsHandler>();
|
||||
collection.AddModBehavior<RoundEnd_GameEndHandler>();
|
||||
collection.AddModBehavior<RoundStart_GameStartHandler>();
|
||||
|
||||
// Damage Cancelers
|
||||
collection.AddModBehavior<OutOfRoundCanceler>();
|
||||
collection.AddModBehavior<TaserListenCanceler>();
|
||||
|
||||
// Listeners
|
||||
collection.AddListener<RoundTimerListener>();
|
||||
collection.AddListener<BodyPickupListener>();
|
||||
collection.AddListener<PlayerStatsTracker>();
|
||||
collection.AddModBehavior<BodyPickupListener>();
|
||||
collection.AddModBehavior<IBodyTracker, BodyTracker>();
|
||||
collection.AddModBehavior<LateSpawnListener>();
|
||||
collection.AddModBehavior<PlayerStatsTracker>();
|
||||
collection.AddModBehavior<RoundTimerListener>();
|
||||
|
||||
// Commands
|
||||
#if DEBUG
|
||||
collection.AddModBehavior<TestCommand>();
|
||||
#endif
|
||||
|
||||
collection.AddScoped<IGameManager, CS2GameManager>();
|
||||
collection.AddScoped<IInventoryManager, CS2InventoryManager>();
|
||||
collection.AddScoped<IMessenger, CS2Messenger>();
|
||||
collection.AddScoped<IMsgLocalizer, StringLocalizer>();
|
||||
collection.AddScoped<IPermissionManager, CS2PermManager>();
|
||||
collection.AddScoped<IPlayerFinder, CS2PlayerFinder>();
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ public class CS2CommandInfo : ICommandInfo {
|
||||
|
||||
public int ArgCount => Args.Length;
|
||||
|
||||
public string GetCommandString => string.Join(' ', Args);
|
||||
public string CommandString => string.Join(' ', Args);
|
||||
|
||||
public void ReplySync(string message) {
|
||||
switch (CallingContext) {
|
||||
@@ -80,4 +80,8 @@ public class CS2CommandInfo : ICommandInfo {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public ICommandInfo Skip(int count = 1) {
|
||||
return new CS2CommandInfo(provider, this, count);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.Command.Test;
|
||||
using TTT.Game;
|
||||
using TTT.Game.Commands;
|
||||
|
||||
@@ -23,21 +22,12 @@ public class CS2CommandManager(IServiceProvider provider)
|
||||
public void Start(BasePlugin? basePlugin, bool hotReload) {
|
||||
plugin = basePlugin;
|
||||
base.Start();
|
||||
|
||||
RegisterCommand(new TTTCommand(Provider));
|
||||
RegisterCommand(new TestCommand(Provider));
|
||||
|
||||
foreach (var command in Provider.GetServices<ICommand>()) command.Start();
|
||||
}
|
||||
|
||||
public override string Name => "CommandManager";
|
||||
public override string Version => GitVersionInformation.FullSemVer;
|
||||
|
||||
public override bool RegisterCommand(ICommand command) {
|
||||
command.Start();
|
||||
var registration = command.Aliases.All(alias
|
||||
=> Commands.TryAdd(COMMAND_PREFIX + alias, command));
|
||||
if (registration == false) return false;
|
||||
=> cmdMap.TryAdd(COMMAND_PREFIX + alias, command));
|
||||
if (!registration) return false;
|
||||
foreach (var alias in command.Aliases)
|
||||
plugin?.AddCommand(COMMAND_PREFIX + alias,
|
||||
command.Description ?? string.Empty, processInternal);
|
||||
@@ -52,14 +42,14 @@ public class CS2CommandManager(IServiceProvider provider)
|
||||
converter.GetPlayer(executor) as IOnlinePlayer;
|
||||
Task.Run(async () => {
|
||||
try {
|
||||
Console.WriteLine($"Processing command: {cs2Info.GetCommandString}");
|
||||
Console.WriteLine($"Processing command: {cs2Info.CommandString}");
|
||||
return await ProcessCommand(cs2Info);
|
||||
} catch (Exception e) {
|
||||
var msg = e.Message;
|
||||
cs2Info.ReplySync(Localizer[GameMsgs.GENERIC_ERROR(msg)]);
|
||||
await Server.NextWorldUpdateAsync(() => {
|
||||
Console.WriteLine(
|
||||
$"Encountered an error when processing command: \"{cs2Info.GetCommandString}\" by {wrapper?.Id}");
|
||||
$"Encountered an error when processing command: \"{cs2Info.CommandString}\" by {wrapper?.Id}");
|
||||
Console.WriteLine(e);
|
||||
});
|
||||
return CommandResult.ERROR;
|
||||
|
||||
29
TTT/CS2/Command/Test/ForceAliveCommand.cs
Normal file
29
TTT/CS2/Command/Test/ForceAliveCommand.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.API;
|
||||
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class ForceAliveCommand(IServiceProvider provider) : ICommand {
|
||||
private readonly IAliveSpoofer spoofer =
|
||||
provider.GetRequiredService<IAliveSpoofer>();
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public string Name => "forcealive";
|
||||
|
||||
public void Start() { }
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
Server.NextWorldUpdate(() => {
|
||||
foreach (var player in Utilities.GetPlayers()) spoofer.SpoofAlive(player);
|
||||
});
|
||||
|
||||
info.ReplySync("Attempted to force alive.");
|
||||
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
}
|
||||
33
TTT/CS2/Command/Test/IdentifyAllCommand.cs
Normal file
33
TTT/CS2/Command/Test/IdentifyAllCommand.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.API;
|
||||
using TTT.Game.Events.Body;
|
||||
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class IdentifyAllCommand(IServiceProvider provider) : ICommand {
|
||||
private readonly IBodyTracker bodies =
|
||||
provider.GetRequiredService<IBodyTracker>();
|
||||
|
||||
private readonly IEventBus bus = provider.GetRequiredService<IEventBus>();
|
||||
|
||||
public string Name => "identifyall";
|
||||
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
foreach (var body in bodies.Bodies.Keys) {
|
||||
if (body.IsIdentified) continue;
|
||||
var bodyIdentifyEvent = new BodyIdentifyEvent(body, executor);
|
||||
Server.NextWorldUpdate(() => bus.Dispatch(bodyIdentifyEvent));
|
||||
}
|
||||
|
||||
info.ReplySync("Identified all bodies.");
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
}
|
||||
41
TTT/CS2/Command/Test/ScreenTextCommand.cs
Normal file
41
TTT/CS2/Command/Test/ScreenTextCommand.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.Hats;
|
||||
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class ScreenTextCommand(IServiceProvider provider) : ICommand {
|
||||
public string Name => "screentext";
|
||||
|
||||
private readonly ITextSpawner spawner =
|
||||
provider.GetRequiredService<ITextSpawner>();
|
||||
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
var textSetting = new TextSetting { msg = "Foo" };
|
||||
|
||||
if (executor == null) return Task.FromResult(CommandResult.PLAYER_ONLY);
|
||||
|
||||
Server.NextWorldUpdate(() => {
|
||||
var ents =
|
||||
Utilities.FindAllEntitiesByDesignerName<CPointWorldText>(
|
||||
"point_worldtext");
|
||||
foreach (var ent in ents) ent.AcceptInput("Kill");
|
||||
var player = converter.GetPlayer(executor);
|
||||
if (player == null || !player.IsValid) return;
|
||||
spawner.CreateTextScreen(textSetting, player);
|
||||
info.ReplySync("Spawned screen text.");
|
||||
});
|
||||
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using TTT.API.Command;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Role;
|
||||
using TTT.CS2.Roles;
|
||||
using TTT.Game.Events.Player;
|
||||
using TTT.Game.Roles;
|
||||
|
||||
@@ -19,7 +18,6 @@ public class SetRoleCommand(IServiceProvider provider) : ICommand {
|
||||
public void Dispose() { }
|
||||
|
||||
public string Name => "setrole";
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
public void Start() { }
|
||||
|
||||
public Task<CommandResult>
|
||||
|
||||
@@ -7,19 +7,20 @@ using TTT.API.Player;
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class StopCommand(IServiceProvider provider) : ICommand {
|
||||
public void Dispose() { }
|
||||
public string Name => "stop";
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
|
||||
private readonly IGameManager games =
|
||||
provider.GetRequiredService<IGameManager>();
|
||||
|
||||
public void Dispose() { }
|
||||
public string Name => "stop";
|
||||
|
||||
public void Start() { }
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
Server.NextWorldUpdate(() => {
|
||||
if (!games.IsGameActive()) {
|
||||
if (games.ActiveGame is not {
|
||||
State: State.COUNTDOWN or State.IN_PROGRESS
|
||||
}) {
|
||||
info.ReplySync("No game is currently running.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Player;
|
||||
@@ -7,23 +6,19 @@ using TTT.API.Player;
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class TestCommand(IServiceProvider provider) : ICommand, IPluginModule {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
private readonly IPlayerFinder finder =
|
||||
provider.GetRequiredService<IPlayerFinder>();
|
||||
|
||||
private readonly IDictionary<string, ICommand> subCommands =
|
||||
new Dictionary<string, ICommand>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public string Name => "test";
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
|
||||
public void Start() {
|
||||
subCommands.Add("setrole", new SetRoleCommand(provider));
|
||||
subCommands.Add("stop", new StopCommand(provider));
|
||||
subCommands.Add("forcealive", new ForceAliveCommand(provider));
|
||||
subCommands.Add("identifyall", new IdentifyAllCommand(provider));
|
||||
subCommands.Add("screentext", new ScreenTextCommand(provider));
|
||||
}
|
||||
|
||||
public Task<CommandResult>
|
||||
@@ -43,6 +38,12 @@ public class TestCommand(IServiceProvider provider) : ICommand, IPluginModule {
|
||||
return Task.FromResult(CommandResult.INVALID_ARGS);
|
||||
}
|
||||
|
||||
return cmd.Execute(executor, new CS2CommandInfo(provider, info, 1));
|
||||
return cmd.Execute(executor, info.Skip());
|
||||
}
|
||||
|
||||
public void Start(BasePlugin? plugin, bool hotload) {
|
||||
((IPluginModule)this).Start();
|
||||
foreach (var cmd in subCommands.Values.OfType<IPluginModule>())
|
||||
cmd.Start(plugin, hotload);
|
||||
}
|
||||
}
|
||||
@@ -84,8 +84,6 @@ public class CS2GameConfig : IStorage<TTTConfig>, IPluginModule {
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(1, 60));
|
||||
|
||||
public void Dispose() { }
|
||||
public string Name => "CS2GameConfig";
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
|
||||
public void Start() { }
|
||||
|
||||
114
TTT/CS2/Configs/CS2ShopConfig.cs
Normal file
114
TTT/CS2/Configs/CS2ShopConfig.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Cvars;
|
||||
using CounterStrikeSharp.API.Modules.Cvars.Validators;
|
||||
using TTT.API;
|
||||
using TTT.API.Storage;
|
||||
using TTT.Shop;
|
||||
|
||||
namespace TTT.CS2.Configs;
|
||||
|
||||
public class CS2ShopConfig : IStorage<ShopConfig>, IPluginModule {
|
||||
public static readonly FakeConVar<int> CV_STARTING_INNOCENT_CREDITS = new(
|
||||
"css_ttt_shop_start_innocent", "Starting credits for Innocents", 100,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 10000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_STARTING_TRAITOR_CREDITS = new(
|
||||
"css_ttt_shop_start_traitor", "Starting credits for Traitors", 120,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 10000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_STARTING_DETECTIVE_CREDITS = new(
|
||||
"css_ttt_shop_start_detective", "Starting credits for Detectives", 150,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 10000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_INNO_V_INNO = new(
|
||||
"css_ttt_shop_inno_v_inno", "Credits change when Innocent kills Innocent",
|
||||
-4, ConVarFlags.FCVAR_NONE, new RangeValidator<int>(-10000, 10000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_INNO_V_TRAITOR = new(
|
||||
"css_ttt_shop_inno_v_traitor", "Credits change when Innocent kills Traitor",
|
||||
8, ConVarFlags.FCVAR_NONE, new RangeValidator<int>(-10000, 10000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_INNO_V_DETECTIVE = new(
|
||||
"css_ttt_shop_inno_v_detective",
|
||||
"Credits change when Innocent kills Detective", -6, ConVarFlags.FCVAR_NONE,
|
||||
new RangeValidator<int>(-10000, 10000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_TRAITOR_V_TRAITOR = new(
|
||||
"css_ttt_shop_traitor_v_traitor",
|
||||
"Credits change when Traitor kills Traitor", -5, ConVarFlags.FCVAR_NONE,
|
||||
new RangeValidator<int>(-10000, 10000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_TRAITOR_V_INNO = new(
|
||||
"css_ttt_shop_traitor_v_inno", "Credits change when Traitor kills Innocent",
|
||||
4, ConVarFlags.FCVAR_NONE, new RangeValidator<int>(-10000, 10000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_TRAITOR_V_DETECTIVE = new(
|
||||
"css_ttt_shop_traitor_v_detective",
|
||||
"Credits change when Traitor kills Detective", 6, ConVarFlags.FCVAR_NONE,
|
||||
new RangeValidator<int>(-10000, 10000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_DETECTIVE_V_DETECTIVE = new(
|
||||
"css_ttt_shop_detective_v_detective",
|
||||
"Credits change when Detective kills Detective", -8, ConVarFlags.FCVAR_NONE,
|
||||
new RangeValidator<int>(-10000, 10000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_DETECTIVE_V_INNO = new(
|
||||
"css_ttt_shop_detective_v_inno",
|
||||
"Credits change when Detective kills Innocent", -6, ConVarFlags.FCVAR_NONE,
|
||||
new RangeValidator<int>(-10000, 10000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_DETECTIVE_V_TRAITOR = new(
|
||||
"css_ttt_shop_detective_v_traitor",
|
||||
"Credits change when Detective kills Traitor", 8, ConVarFlags.FCVAR_NONE,
|
||||
new RangeValidator<int>(-10000, 10000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_ANY_KILL = new(
|
||||
"css_ttt_shop_any_kill",
|
||||
"Credits granted for any kill when roles are unknown", 2,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(-10000, 10000));
|
||||
|
||||
public static readonly FakeConVar<float> CV_ASSIST_MULTIPLIER = new(
|
||||
"css_ttt_shop_assist_multiplier", "Multiplier applied to assister credits",
|
||||
0.5f, ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 10f));
|
||||
|
||||
public static readonly FakeConVar<float> CV_SOLO_KILL_MULTIPLIER = new(
|
||||
"css_ttt_shop_solo_kill_multiplier",
|
||||
"Multiplier applied to killer credits when there is no assist", 1.5f,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 10f));
|
||||
|
||||
private readonly IServiceProvider _provider;
|
||||
|
||||
public CS2ShopConfig(IServiceProvider provider) { _provider = provider; }
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public void Start() { }
|
||||
|
||||
public void Start(BasePlugin? plugin) {
|
||||
ArgumentNullException.ThrowIfNull(plugin, nameof(plugin));
|
||||
plugin.RegisterFakeConVars(this);
|
||||
}
|
||||
|
||||
public Task<ShopConfig?> Load() {
|
||||
var cfg = new ShopConfig(_provider) {
|
||||
StartingInnocentCredits = CV_STARTING_INNOCENT_CREDITS.Value,
|
||||
StartingTraitorCredits = CV_STARTING_TRAITOR_CREDITS.Value,
|
||||
StartingDetectiveCredits = CV_STARTING_DETECTIVE_CREDITS.Value,
|
||||
CreditsForInnoVInnoKill = CV_INNO_V_INNO.Value,
|
||||
CreditsForInnoVTraitorKill = CV_INNO_V_TRAITOR.Value,
|
||||
CreditsForInnoVDetectiveKill = CV_INNO_V_DETECTIVE.Value,
|
||||
CreditsForTraitorVTraitorKill = CV_TRAITOR_V_TRAITOR.Value,
|
||||
CreditsForTraitorVInnoKill = CV_TRAITOR_V_INNO.Value,
|
||||
CreditsForTraitorVDetectiveKill = CV_TRAITOR_V_DETECTIVE.Value,
|
||||
CreditsForDetectiveVDetectiveKill = CV_DETECTIVE_V_DETECTIVE.Value,
|
||||
CreditsForDetectiveVInnoKill = CV_DETECTIVE_V_INNO.Value,
|
||||
CreditsForDetectiveVTraitorKill = CV_DETECTIVE_V_TRAITOR.Value,
|
||||
CreditsForAnyKill = CV_ANY_KILL.Value,
|
||||
CreditMultiplierForAssisting = CV_ASSIST_MULTIPLIER.Value,
|
||||
CreditsMultiplierForNotAssisted = CV_SOLO_KILL_MULTIPLIER.Value
|
||||
};
|
||||
|
||||
return Task.FromResult<ShopConfig?>(cfg);
|
||||
}
|
||||
}
|
||||
45
TTT/CS2/Configs/ShopItems/CS2OneShotDeagleConfig.cs
Normal file
45
TTT/CS2/Configs/ShopItems/CS2OneShotDeagleConfig.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Cvars;
|
||||
using CounterStrikeSharp.API.Modules.Cvars.Validators;
|
||||
using TTT.API;
|
||||
using TTT.API.Storage;
|
||||
using TTT.CS2.Validators;
|
||||
using TTT.Shop.Items;
|
||||
|
||||
namespace TTT.CS2.Configs.ShopItems;
|
||||
|
||||
public class CS2OneShotDeagleConfig : IStorage<OneShotDeagleConfig>,
|
||||
IPluginModule {
|
||||
public static readonly FakeConVar<int> CV_PRICE = new(
|
||||
"css_ttt_shop_onedeagle_price", "Price of the One-Shot Deagle item", 100,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 10000));
|
||||
|
||||
public static readonly FakeConVar<bool> CV_FRIENDLY_FIRE = new(
|
||||
"css_ttt_shop_onedeagle_ff",
|
||||
"Whether the One-Shot Deagle damages teammates", true);
|
||||
|
||||
public static readonly FakeConVar<string> CV_WEAPON = new(
|
||||
"css_ttt_shop_onedeagle_weapon",
|
||||
"Weapon entity name used for the One-Shot Deagle", "weapon_revolver",
|
||||
ConVarFlags.FCVAR_NONE, new ItemValidator(allowMultiple: false));
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public void Start() { }
|
||||
|
||||
public void Start(BasePlugin? plugin) {
|
||||
ArgumentNullException.ThrowIfNull(plugin, nameof(plugin));
|
||||
plugin.RegisterFakeConVars(this);
|
||||
}
|
||||
|
||||
public Task<OneShotDeagleConfig?> Load() {
|
||||
var cfg = new OneShotDeagleConfig {
|
||||
Price = CV_PRICE.Value,
|
||||
DoesFriendlyFire = CV_FRIENDLY_FIRE.Value,
|
||||
Weapon = CV_WEAPON.Value
|
||||
};
|
||||
|
||||
return Task.FromResult<OneShotDeagleConfig?>(cfg);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Drawing;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.UserMessages;
|
||||
|
||||
namespace TTT.CS2.Extensions;
|
||||
|
||||
@@ -8,7 +9,7 @@ public static class PlayerExtensions {
|
||||
public static CBasePlayerWeapon? GetWeaponBase(
|
||||
this CCSPlayerController player, string designerName) {
|
||||
if (!player.IsValid) return null;
|
||||
var pawn = player.PlayerPawn.Value;
|
||||
var pawn = player.Pawn.Value;
|
||||
if (pawn == null || !pawn.IsValid) return null;
|
||||
|
||||
return pawn.WeaponServices?.MyWeapons
|
||||
@@ -27,11 +28,38 @@ public static class PlayerExtensions {
|
||||
|
||||
public static void SetColor(this CCSPlayerController player, Color color) {
|
||||
if (!player.IsValid) return;
|
||||
var pawn = player.PlayerPawn.Value;
|
||||
var pawn = player.Pawn.Value;
|
||||
if (!player.IsValid || pawn == null || !pawn.IsValid) return;
|
||||
|
||||
if (color.A == 255)
|
||||
color = Color.FromArgb(pawn.Render.A, color.R, color.G, color.B);
|
||||
player.PlayerPawn.Value.SetColor(color);
|
||||
pawn.SetColor(color);
|
||||
}
|
||||
|
||||
public enum FadeFlags {
|
||||
FADE_IN, FADE_OUT, FADE_STAYOUT
|
||||
}
|
||||
|
||||
public static void ColorScreen(this CCSPlayerController player, Color color,
|
||||
float hold = 0.1f, float fade = 0.2f, FadeFlags flags = FadeFlags.FADE_IN,
|
||||
bool withPurge = true) {
|
||||
var fadeMsg = UserMessage.FromId(106);
|
||||
|
||||
fadeMsg.SetInt("duration", Convert.ToInt32(fade * 512));
|
||||
fadeMsg.SetInt("hold_time", Convert.ToInt32(hold * 512));
|
||||
|
||||
var flag = flags switch {
|
||||
FadeFlags.FADE_IN => 0x0001,
|
||||
FadeFlags.FADE_OUT => 0x0002,
|
||||
FadeFlags.FADE_STAYOUT => 0x0008,
|
||||
_ => 0x0001
|
||||
};
|
||||
|
||||
if (withPurge) flag |= 0x0010;
|
||||
|
||||
fadeMsg.SetInt("flags", flag);
|
||||
fadeMsg.SetInt("color",
|
||||
color.R | color.G << 8 | color.B << 16 | color.A << 24);
|
||||
fadeMsg.Send(player);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using System.Numerics;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
|
||||
|
||||
namespace TTT.CS2.Extensions;
|
||||
|
||||
@@ -53,8 +55,12 @@ public static class VectorExtensions {
|
||||
float maxDelta) {
|
||||
var toVector = target - current;
|
||||
var dist = toVector.Length();
|
||||
if (dist <= maxDelta || dist == 0f) { return target; }
|
||||
if (dist <= maxDelta || dist == 0f) return target;
|
||||
|
||||
return current + toVector / dist * maxDelta;
|
||||
}
|
||||
|
||||
public static Vector toVector(this Vector3 vec) {
|
||||
return new Vector(vec.X, vec.Y, vec.Z);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ using TTT.Game.Roles;
|
||||
namespace TTT.CS2.Game;
|
||||
|
||||
public class CS2Game(IServiceProvider provider) : RoundBasedGame(provider) {
|
||||
public override IActionLogger Logger { get; } = new CS2Logger(provider);
|
||||
|
||||
public override IList<IRole> Roles { get; } = [
|
||||
new SpectatorRole(provider), new InnocentRole(provider),
|
||||
new TraitorRole(provider), new DetectiveRole(provider)
|
||||
@@ -40,6 +42,10 @@ public class CS2Game(IServiceProvider provider) : RoundBasedGame(provider) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (countdown != null)
|
||||
Messenger?.MessageAll(
|
||||
Locale[GameMsgs.GAME_STATE_STARTING(countdown.Value)]);
|
||||
|
||||
timer.Subscribe(_ => {
|
||||
Server.NextWorldUpdate(() => {
|
||||
if (RoundUtil.IsWarmup()) return;
|
||||
|
||||
@@ -6,9 +6,13 @@ namespace TTT.CS2.Game;
|
||||
|
||||
public class CS2GameManager(IServiceProvider provider) : GameManager(provider) {
|
||||
public override IGame CreateGame() {
|
||||
if (((IGameManager)this).IsGameActive())
|
||||
throw new InvalidOperationException(
|
||||
"A game is already active. Please end the current game before starting a new one.");
|
||||
switch (ActiveGame) {
|
||||
case { State: State.IN_PROGRESS or State.COUNTDOWN }:
|
||||
throw new InvalidOperationException(
|
||||
"A game is already active. End the current game before starting a new one.");
|
||||
case { State: State.WAITING }:
|
||||
return ActiveGame;
|
||||
}
|
||||
|
||||
ActiveGame = new CS2Game(Provider);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.Game.Events.Body;
|
||||
@@ -19,21 +20,22 @@ public class BodySpawner(IServiceProvider provider) : IPluginModule {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
private readonly PropMover mover = provider.GetRequiredService<PropMover>();
|
||||
private readonly IGameManager games =
|
||||
provider.GetRequiredService<IGameManager>();
|
||||
|
||||
public void Dispose() { }
|
||||
public string Name => nameof(BodySpawner);
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
public void Start() { }
|
||||
|
||||
[GameEventHandler]
|
||||
public HookResult OnDeath(EventPlayerDeath ev, GameEventInfo _) {
|
||||
if (games.ActiveGame is not { State: State.IN_PROGRESS })
|
||||
return HookResult.Continue;
|
||||
var player = ev.Userid;
|
||||
if (player == null || !player.IsValid) return HookResult.Continue;
|
||||
player.SetColor(Color.FromArgb(0, 0, 0, 0));
|
||||
|
||||
var ragdollBody = makeGameRagdoll(player);
|
||||
var body = new CS2Body(ragdollBody, converter.GetPlayer(player));
|
||||
var body = new CS2Body(provider, ragdollBody, converter.GetPlayer(player));
|
||||
|
||||
if (ev.Attacker != null && ev.Attacker.IsValid)
|
||||
body.WithKiller(converter.GetPlayer(ev.Attacker));
|
||||
@@ -43,12 +45,7 @@ public class BodySpawner(IServiceProvider provider) : IPluginModule {
|
||||
var bodyCreatedEvent = new BodyCreateEvent(body);
|
||||
bus.Dispatch(bodyCreatedEvent);
|
||||
|
||||
if (bodyCreatedEvent.IsCanceled) {
|
||||
ragdollBody.AcceptInput("Kill");
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
mover.MapEntities.Add(ragdollBody);
|
||||
if (bodyCreatedEvent.IsCanceled) ragdollBody.AcceptInput("Kill");
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
@@ -63,20 +60,20 @@ public class BodySpawner(IServiceProvider provider) : IPluginModule {
|
||||
|
||||
private CRagdollProp makeGameRagdoll(CCSPlayerController playerController) {
|
||||
var ragdoll = Utilities.CreateEntityByName<CRagdollProp>("prop_ragdoll");
|
||||
var pawn = playerController.PlayerPawn.Value;
|
||||
var pawn = playerController.Pawn.Value;
|
||||
|
||||
if (ragdoll == null || !ragdoll.IsValid || playerController == null)
|
||||
throw new ArgumentNullException(nameof(ragdoll));
|
||||
|
||||
if (pawn == null || !pawn.IsValid)
|
||||
throw new ArgumentException("PlayerPawn is not valid",
|
||||
throw new ArgumentException("Pawn is not valid",
|
||||
nameof(playerController));
|
||||
|
||||
var origin = pawn.AbsOrigin.Clone();
|
||||
var rotation = pawn.AbsRotation.Clone();
|
||||
|
||||
if (origin == null)
|
||||
throw new ArgumentException("PlayerPawn AbsOrigin is null",
|
||||
throw new ArgumentException("Pawn AbsOrigin is null",
|
||||
nameof(playerController));
|
||||
|
||||
origin.Z += 30;
|
||||
|
||||
@@ -7,6 +7,7 @@ using TTT.API;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.API;
|
||||
using TTT.Game.Events.Player;
|
||||
|
||||
namespace TTT.CS2.GameHandlers;
|
||||
@@ -20,8 +21,8 @@ public class CombatHandler(IServiceProvider provider) : IPluginModule {
|
||||
private readonly IGameManager games =
|
||||
provider.GetRequiredService<IGameManager>();
|
||||
|
||||
public string Name => "CombatListeners";
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
private readonly IAliveSpoofer spoofer =
|
||||
provider.GetRequiredService<IAliveSpoofer>();
|
||||
|
||||
public void Start() { }
|
||||
|
||||
@@ -35,7 +36,8 @@ public class CombatHandler(IServiceProvider provider) : IPluginModule {
|
||||
[UsedImplicitly]
|
||||
[GameEventHandler(HookMode.Pre)]
|
||||
public HookResult OnPlayerDeath_Pre(EventPlayerDeath ev, GameEventInfo info) {
|
||||
if (!games.IsGameActive()) return HookResult.Continue;
|
||||
if (games.ActiveGame is not { State: State.IN_PROGRESS })
|
||||
return HookResult.Continue;
|
||||
var player = ev.Userid;
|
||||
if (player == null) return HookResult.Continue;
|
||||
var deathEvent = new PlayerDeathEvent(converter, ev);
|
||||
@@ -43,6 +45,15 @@ public class CombatHandler(IServiceProvider provider) : IPluginModule {
|
||||
Server.NextWorldUpdateAsync(() => bus.Dispatch(deathEvent));
|
||||
|
||||
info.DontBroadcast = true;
|
||||
|
||||
hideAndTrackStats(ev, player);
|
||||
|
||||
spoofer.SpoofAlive(player);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private void hideAndTrackStats(EventPlayerDeath ev,
|
||||
CCSPlayerController player) {
|
||||
var victimStats = player.ActionTrackingServices?.MatchStats;
|
||||
if (victimStats != null) {
|
||||
victimStats.Deaths -= 1;
|
||||
@@ -51,42 +62,30 @@ public class CombatHandler(IServiceProvider provider) : IPluginModule {
|
||||
}
|
||||
|
||||
var killerStats = ev.Attacker?.ActionTrackingServices?.MatchStats;
|
||||
if (killerStats != null) {
|
||||
killerStats.Kills -= 1;
|
||||
killerStats.Damage -= ev.DmgHealth;
|
||||
if (killerStats == null) return;
|
||||
killerStats.Kills -= 1;
|
||||
killerStats.Damage -= ev.DmgHealth;
|
||||
|
||||
if (ev.Attacker != null)
|
||||
Utilities.SetStateChanged(ev.Attacker, "CCSPlayerController",
|
||||
"m_pActionTrackingServices");
|
||||
|
||||
var assisterStats = ev.Assister?.ActionTrackingServices?.MatchStats;
|
||||
if (assisterStats != null && assisterStats != killerStats)
|
||||
assisterStats.Assists -= 1;
|
||||
|
||||
if (ev.Assister != null)
|
||||
Utilities.SetStateChanged(ev.Assister, "CCSPlayerController",
|
||||
"m_pActionTrackingServices");
|
||||
if (ev.Attacker != null) {
|
||||
Utilities.SetStateChanged(ev.Attacker, "CCSPlayerController",
|
||||
"m_pActionTrackingServices");
|
||||
ev.FireEventToClient(ev.Attacker);
|
||||
}
|
||||
|
||||
// These delays are necessary for the game engine
|
||||
Server.NextWorldUpdate(() => {
|
||||
var pawn = player.PlayerPawn.Value;
|
||||
if (pawn == null || !pawn.IsValid) return;
|
||||
pawn.DeathTime = 0;
|
||||
Utilities.SetStateChanged(pawn, "CBasePlayerPawn", "m_flDeathTime");
|
||||
var assisterStats = ev.Assister?.ActionTrackingServices?.MatchStats;
|
||||
if (assisterStats != null && assisterStats != killerStats)
|
||||
assisterStats.Assists -= 1;
|
||||
|
||||
Server.NextWorldUpdate(() => {
|
||||
player.PawnIsAlive = true;
|
||||
Utilities.SetStateChanged(player, "CCSPlayerController",
|
||||
"m_bPawnIsAlive");
|
||||
});
|
||||
});
|
||||
return HookResult.Continue;
|
||||
if (ev.Assister != null)
|
||||
Utilities.SetStateChanged(ev.Assister, "CCSPlayerController",
|
||||
"m_pActionTrackingServices");
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[GameEventHandler]
|
||||
public HookResult OnPlayerHurt(EventPlayerHurt ev, GameEventInfo _) {
|
||||
if (!games.IsGameActive()) return HookResult.Continue;
|
||||
// DamageCanceler already handles this on non-Windows platforms
|
||||
if (!OperatingSystem.IsWindows()) return HookResult.Continue;
|
||||
var player = ev.Userid;
|
||||
if (player == null) return HookResult.Continue;
|
||||
|
||||
@@ -94,15 +93,9 @@ public class CombatHandler(IServiceProvider provider) : IPluginModule {
|
||||
|
||||
bus.Dispatch(dmgEvent);
|
||||
|
||||
var pawn = player.Pawn.Value;
|
||||
if (pawn != null && pawn.IsValid) {
|
||||
pawn.Health = dmgEvent.HpLeft;
|
||||
if (player.PlayerPawn.Value != null && player.PlayerPawn.Value.IsValid)
|
||||
player.PlayerPawn.Value.ArmorValue = dmgEvent.ArmorRemaining;
|
||||
}
|
||||
ev.Health = dmgEvent.HpLeft;
|
||||
ev.Armor = dmgEvent.ArmorRemaining;
|
||||
|
||||
if (dmgEvent.IsCanceled) return HookResult.Handled;
|
||||
|
||||
return HookResult.Continue;
|
||||
return dmgEvent.IsCanceled ? HookResult.Handled : HookResult.Continue;
|
||||
}
|
||||
}
|
||||
42
TTT/CS2/GameHandlers/DamageCanceler.cs
Normal file
42
TTT/CS2/GameHandlers/DamageCanceler.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Player;
|
||||
using TTT.Game.Events.Player;
|
||||
|
||||
namespace TTT.CS2.GameHandlers;
|
||||
|
||||
public class DamageCanceler(IServiceProvider provider) : IPluginModule {
|
||||
private readonly IEventBus bus = provider.GetRequiredService<IEventBus>();
|
||||
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public void Start() { }
|
||||
|
||||
public void Start(BasePlugin? plugin) {
|
||||
if (OperatingSystem.IsWindows()) return;
|
||||
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(onTakeDamage,
|
||||
HookMode.Pre);
|
||||
}
|
||||
|
||||
private HookResult onTakeDamage(DynamicHook hook) {
|
||||
var damagedEvent = new PlayerDamagedEvent(converter, hook);
|
||||
|
||||
bus.Dispatch(damagedEvent);
|
||||
|
||||
if (damagedEvent.IsCanceled) return HookResult.Handled;
|
||||
|
||||
if (!damagedEvent.HpModified
|
||||
|| damagedEvent.Player is not IOnlinePlayer onlinePlayer)
|
||||
return HookResult.Continue;
|
||||
|
||||
onlinePlayer.Health = damagedEvent.HpLeft;
|
||||
return HookResult.Handled;
|
||||
}
|
||||
}
|
||||
17
TTT/CS2/GameHandlers/DamageCancelers/OutOfRoundCanceler.cs
Normal file
17
TTT/CS2/GameHandlers/DamageCancelers/OutOfRoundCanceler.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.CS2.Utils;
|
||||
using TTT.Game.Events.Player;
|
||||
using TTT.Game.Listeners;
|
||||
|
||||
namespace TTT.CS2.GameHandlers.DamageCancelers;
|
||||
|
||||
public class OutOfRoundCanceler(IServiceProvider provider)
|
||||
: BaseListener(provider) {
|
||||
[EventHandler]
|
||||
public void OnHurt(PlayerDamagedEvent ev) {
|
||||
if (RoundUtil.IsWarmup()) return;
|
||||
if (Games.ActiveGame is not { State: State.IN_PROGRESS })
|
||||
ev.IsCanceled = true;
|
||||
}
|
||||
}
|
||||
27
TTT/CS2/GameHandlers/DamageCancelers/TaserListenCanceler.cs
Normal file
27
TTT/CS2/GameHandlers/DamageCancelers/TaserListenCanceler.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.CS2.lang;
|
||||
using TTT.Game.Events.Player;
|
||||
using TTT.Game.Listeners;
|
||||
|
||||
namespace TTT.CS2.GameHandlers.DamageCancelers;
|
||||
|
||||
public class TaserListenCanceler(IServiceProvider provider)
|
||||
: BaseListener(provider) {
|
||||
[EventHandler]
|
||||
public void OnHurt(PlayerDamagedEvent ev) {
|
||||
if (Games.ActiveGame is not { State: State.IN_PROGRESS }) return;
|
||||
if (ev.Weapon == null) return;
|
||||
if (!ev.Weapon.Contains("taser", StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
ev.IsCanceled = true;
|
||||
|
||||
var victim = ev.Player;
|
||||
var attacker = ev.Attacker;
|
||||
|
||||
if (attacker == null) return;
|
||||
|
||||
Messenger.Message(attacker,
|
||||
Locale[CS2Msgs.TASER_SCANNED(victim, Roles.GetRoles(victim).First())]);
|
||||
}
|
||||
}
|
||||
47
TTT/CS2/GameHandlers/KarmaSyncer.cs
Normal file
47
TTT/CS2/GameHandlers/KarmaSyncer.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Player;
|
||||
using TTT.Karma;
|
||||
|
||||
namespace TTT.CS2.GameHandlers;
|
||||
|
||||
public class KarmaSyncer(IServiceProvider provider) : IPluginModule {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
private readonly IKarmaService? karma = provider.GetService<IKarmaService>();
|
||||
|
||||
private readonly IPlayerFinder players =
|
||||
provider.GetRequiredService<IPlayerFinder>();
|
||||
|
||||
public void Dispose() { }
|
||||
public string Name => nameof(KarmaSyncer);
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
|
||||
public void Start() { }
|
||||
|
||||
[GameEventHandler]
|
||||
public HookResult OnRoundStart(EventRoundStart _, GameEventInfo _1) {
|
||||
if (karma == null) return HookResult.Continue;
|
||||
|
||||
foreach (var p in Utilities.GetPlayers()) {
|
||||
if (!p.IsValid || p.IsBot) continue;
|
||||
|
||||
var apiPlayer = converter.GetPlayer(p);
|
||||
Task.Run(async () => {
|
||||
var pk = await karma.Load(apiPlayer);
|
||||
|
||||
await Server.NextFrameAsync(() => {
|
||||
p.Score = pk;
|
||||
Utilities.SetStateChanged(p, "CCSPlayerController",
|
||||
"m_pActionTrackingServices");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Messages;
|
||||
using TTT.API.Player;
|
||||
using TTT.Game.Events.Player;
|
||||
|
||||
@@ -15,8 +17,11 @@ public class PlayerConnectionsHandler(IServiceProvider provider)
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
public string Name => nameof(PlayerConnectionsHandler);
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
private readonly IGameManager games =
|
||||
provider.GetRequiredService<IGameManager>();
|
||||
|
||||
private readonly IMessenger messenger =
|
||||
provider.GetRequiredService<IMessenger>();
|
||||
|
||||
public void Start() { }
|
||||
|
||||
@@ -30,11 +35,10 @@ public class PlayerConnectionsHandler(IServiceProvider provider)
|
||||
disconnectFromServer);
|
||||
|
||||
Server.NextWorldUpdate(() => {
|
||||
foreach (var player in Utilities.GetPlayers()) {
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
var ev = new PlayerJoinEvent(gamePlayer);
|
||||
foreach (var ev in Utilities.GetPlayers()
|
||||
.Select(player => converter.GetPlayer(player))
|
||||
.Select(gamePlayer => new PlayerJoinEvent(gamePlayer)))
|
||||
bus.Dispatch(ev);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -42,14 +46,11 @@ public class PlayerConnectionsHandler(IServiceProvider provider)
|
||||
|
||||
private void disconnectFromServer(int playerSlot) {
|
||||
var player = Utilities.GetPlayerFromSlot(playerSlot);
|
||||
Console.WriteLine($"Player {playerSlot} disconnected from server.");
|
||||
if (player == null || !player.IsValid) {
|
||||
Console.WriteLine($"Player {playerSlot} does not exist.");
|
||||
return;
|
||||
}
|
||||
if (player == null || !player.IsValid) return;
|
||||
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
bus.Dispatch(new PlayerLeaveEvent(gamePlayer));
|
||||
Server.NextWorldUpdate(()
|
||||
=> bus.Dispatch(new PlayerLeaveEvent(gamePlayer)));
|
||||
}
|
||||
|
||||
private void connectToServer(int playerSlot) {
|
||||
@@ -61,6 +62,7 @@ public class PlayerConnectionsHandler(IServiceProvider provider)
|
||||
}
|
||||
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
bus.Dispatch(new PlayerJoinEvent(gamePlayer));
|
||||
|
||||
Server.NextWorldUpdate(() => bus.Dispatch(new PlayerJoinEvent(gamePlayer)));
|
||||
}
|
||||
}
|
||||
@@ -1,120 +1,81 @@
|
||||
using System.Drawing;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Modules.Timers;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Messages;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.Events;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.CS2.Utils;
|
||||
using TTT.CS2.RayTrace.Class;
|
||||
using TTT.CS2.RayTrace.Enum;
|
||||
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
|
||||
|
||||
namespace TTT.CS2.GameHandlers;
|
||||
|
||||
public class PropMover(IServiceProvider provider) : IPluginModule {
|
||||
// TODO: Make this configurable
|
||||
public static readonly float MIN_LOOK_ACCURACY = 2000f;
|
||||
public static readonly float MAX_DISTANCE = 100f;
|
||||
public static readonly float MIN_HOLDING_DISTANCE = 100f;
|
||||
public static readonly float MAX_HOLDING_DISTANCE = 10000f;
|
||||
public static readonly float MAX_DISTANCE = 200;
|
||||
public static readonly float MIN_HOLDING_DISTANCE = 80;
|
||||
public static readonly float MAX_HOLDING_DISTANCE = 150;
|
||||
|
||||
private static readonly QAngle DEAD_ANGLE = new(90, 45, 90);
|
||||
private readonly IEventBus bus = provider.GetRequiredService<IEventBus>();
|
||||
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
public readonly HashSet<CBaseEntity> MapEntities = [];
|
||||
private readonly IMessenger msg = provider.GetRequiredService<IMessenger>();
|
||||
|
||||
private readonly Dictionary<CCSPlayerController, MovementInfo>
|
||||
playersPressingE = new();
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public string Name => nameof(PropMover);
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
|
||||
public void Start() { }
|
||||
|
||||
public void Start(BasePlugin? plugin, bool hotReload) {
|
||||
plugin?.AddTimer(Server.TickInterval, refreshLines, TimerFlags.REPEAT);
|
||||
plugin?.RegisterListener<CounterStrikeSharp.API.Core.Listeners.OnTick>(
|
||||
refreshBodies);
|
||||
refreshHeld);
|
||||
plugin
|
||||
?.RegisterListener<
|
||||
CounterStrikeSharp.API.Core.Listeners.OnPlayerButtonsChanged>(
|
||||
buttonsChanged);
|
||||
|
||||
if (!hotReload) return;
|
||||
OnRoundStart(null!, null!);
|
||||
onButtonsChanged);
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[GameEventHandler]
|
||||
public HookResult OnRoundStart(EventRoundStart _, GameEventInfo _1) {
|
||||
var entities =
|
||||
Utilities.GetAllEntities().Where(ent => ent.IsValid).ToList();
|
||||
foreach (var propMultiplayer in from ent in entities
|
||||
where ent.DesignerName.Equals("prop_physics_multiplayer")
|
||||
select new CPhysicsPropMultiplayer(ent.Handle))
|
||||
MapEntities.Add(propMultiplayer);
|
||||
foreach (var propMultiplayer in from ent in entities
|
||||
where ent.DesignerName.Equals("prop_ragdoll")
|
||||
select new CRagdollProp(ent.Handle))
|
||||
MapEntities.Add(propMultiplayer);
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private void buttonsChanged(CCSPlayerController player, PlayerButtons pressed,
|
||||
PlayerButtons released) {
|
||||
if (playersPressingE.TryGetValue(player, out var e)) {
|
||||
if (!released.HasFlag(PlayerButtons.Use)) return;
|
||||
playersPressingE.Remove(player);
|
||||
if (!e.Ragdoll.IsValid) return;
|
||||
e.Ragdoll.AcceptInput("EnableMotion");
|
||||
if (e.Beam != null && e.Beam.IsValid) e.Beam.AcceptInput("Kill");
|
||||
private void onButtonsChanged(CCSPlayerController player,
|
||||
PlayerButtons pressed, PlayerButtons released) {
|
||||
if (playersPressingE.TryGetValue(player, out var heldItem)) {
|
||||
onCeaseUse(player, released, heldItem);
|
||||
return;
|
||||
}
|
||||
|
||||
var playerPos = player.PlayerPawn.Value?.AbsOrigin;
|
||||
if (playerPos == null) return;
|
||||
|
||||
if (!pressed.HasFlag(PlayerButtons.Use)) return;
|
||||
|
||||
var target = RayTrace.FindRayTraceIntersection(player);
|
||||
onStartUse(player);
|
||||
}
|
||||
|
||||
private void onStartUse(CCSPlayerController player) {
|
||||
var playerPos = player.PlayerPawn.Value?.AbsOrigin;
|
||||
if (playerPos == null) return;
|
||||
var target = player.GetGameTraceByEyePosition(TraceMask.MaskSolid,
|
||||
Contents.NoDraw, player);
|
||||
if (target == null) return;
|
||||
|
||||
msg.DebugInform(target.ToString());
|
||||
target.Value.HitEntityByDesignerName(out CBaseEntity? hitEntity,
|
||||
"prop_ragdoll");
|
||||
|
||||
CBaseEntity? foundEntity = null;
|
||||
MapEntities.RemoveWhere(ent => !ent.IsValid);
|
||||
var closestDist = double.MaxValue;
|
||||
foreach (var ent in MapEntities) {
|
||||
if (!ent.IsValid) continue;
|
||||
var rayPointDist =
|
||||
ent.AbsOrigin?.DistanceSquared(target) ?? double.MaxValue;
|
||||
msg.Debug($"Checking entity {ent.DesignerName} at {ent.AbsOrigin}, "
|
||||
+ $"distance squared: {rayPointDist}");
|
||||
if (rayPointDist >= MIN_LOOK_ACCURACY || rayPointDist >= closestDist)
|
||||
continue;
|
||||
if (hitEntity == null || !hitEntity.IsValid)
|
||||
target.Value.HitEntityByDesignerName(out hitEntity,
|
||||
"prop_physics_multiplayer");
|
||||
|
||||
closestDist = rayPointDist;
|
||||
foundEntity = ent;
|
||||
}
|
||||
|
||||
var playerDist = playerPos.Distance(target);
|
||||
msg.Debug($"Player distance squared to target: {playerDist}");
|
||||
var playerDist = target.Value.Distance();
|
||||
if (playerDist > MAX_DISTANCE) return;
|
||||
if (foundEntity == null) return;
|
||||
if (hitEntity == null) return;
|
||||
|
||||
var apiPlayer = converter.GetPlayer(player);
|
||||
var pickupEvent = new PropPickupEvent(apiPlayer, foundEntity);
|
||||
var pickupEvent = new PropPickupEvent(apiPlayer, hitEntity);
|
||||
bus.Dispatch(pickupEvent);
|
||||
if (pickupEvent.IsCanceled) return;
|
||||
|
||||
@@ -124,40 +85,76 @@ public class PropMover(IServiceProvider provider) : IPluginModule {
|
||||
pickupEvent.Prop.AcceptInput("DisableMotion");
|
||||
}
|
||||
|
||||
private void refreshBodies() {
|
||||
foreach (var (player, info) in playersPressingE)
|
||||
refreshBodies(player, info);
|
||||
private void onCeaseUse(CCSPlayerController player, PlayerButtons released,
|
||||
MovementInfo heldItem) {
|
||||
if (!released.HasFlag(PlayerButtons.Use)) return;
|
||||
playersPressingE.Remove(player);
|
||||
if (!heldItem.Ragdoll.IsValid) return;
|
||||
heldItem.Ragdoll.AcceptInput("EnableMotion");
|
||||
if (heldItem.Beam != null && heldItem.Beam.IsValid)
|
||||
heldItem.Beam.AcceptInput("Kill");
|
||||
}
|
||||
|
||||
private void refreshBodies(CCSPlayerController player, MovementInfo info) {
|
||||
private void refreshHeld() {
|
||||
foreach (var (player, info) in playersPressingE) refreshHeld(player, info);
|
||||
}
|
||||
|
||||
private void refreshHeld(CCSPlayerController player, MovementInfo info) {
|
||||
var ent = info.Ragdoll;
|
||||
if (!player.IsValid || !ent.IsValid) {
|
||||
if (!player.IsValid || !ent.IsValid || ent.AbsOrigin == null) {
|
||||
playersPressingE.Remove(player);
|
||||
return;
|
||||
}
|
||||
|
||||
var playerPawn = player.PlayerPawn.Value;
|
||||
if (playerPawn == null || !playerPawn.IsValid) return;
|
||||
var playerOrigin = playerPawn.AbsOrigin;
|
||||
var playerOrigin = player.GetEyePosition();
|
||||
|
||||
if (playerOrigin == null) {
|
||||
playersPressingE.Remove(player);
|
||||
return;
|
||||
}
|
||||
|
||||
playerOrigin = playerOrigin.Clone()!;
|
||||
playerOrigin.Z += 64;
|
||||
var raytrace = player.GetGameTraceByEyePosition(TraceMask.MaskSolid,
|
||||
Contents.NoDraw, player);
|
||||
|
||||
var eyeAngles = playerPawn!.EyeAngles;
|
||||
if (raytrace == null) return;
|
||||
|
||||
var targetVector = playerOrigin + eyeAngles.Clone()!.ToForward()
|
||||
* Math.Clamp(info.Distance, MIN_HOLDING_DISTANCE, MAX_HOLDING_DISTANCE);
|
||||
var isOnSelf =
|
||||
raytrace.Value.HitEntityByDesignerName(out CBaseEntity? _,
|
||||
ent.DesignerName);
|
||||
|
||||
targetVector.Z = Math.Max(targetVector.Z, playerOrigin.Z - 48);
|
||||
var endPos = raytrace.Value.EndPos.toVector();
|
||||
|
||||
if (ent.AbsOrigin == null) return;
|
||||
var lerpedVector = ent.AbsOrigin.Lerp(targetVector, 0.3f);
|
||||
if (isOnSelf || raytrace.Value.Distance() > MAX_HOLDING_DISTANCE)
|
||||
endPos = playerOrigin
|
||||
+ playerPawn.EyeAngles.ToForward() * MAX_HOLDING_DISTANCE;
|
||||
|
||||
ent.Teleport(lerpedVector, QAngle.Zero, Vector.Zero);
|
||||
if (ent.DesignerName == "prop_physics_multiplayer") {
|
||||
ent.Teleport(endPos, QAngle.Zero, Vector.Zero);
|
||||
return;
|
||||
}
|
||||
|
||||
moveBody(endPos, ent);
|
||||
}
|
||||
|
||||
private void moveBody(Vector endPos, CBaseEntity ent) {
|
||||
var deadRot = DEAD_ANGLE.Clone()!;
|
||||
|
||||
var rotDeg = Server.CurrentTime * 64f % 360;
|
||||
var rotRad = (rotDeg + 0) * (MathF.PI / 180);
|
||||
deadRot.Y += rotDeg;
|
||||
|
||||
var xOff = MathF.Cos(rotRad) * 32;
|
||||
var yOff = MathF.Sin(rotRad) * 32;
|
||||
var xBias = MathF.Cos(rotRad + MathF.PI / 2) * 16;
|
||||
var yBias = MathF.Sin(rotRad + MathF.PI / 2) * 16;
|
||||
endPos.X += xBias;
|
||||
endPos.Y += yBias;
|
||||
|
||||
endPos -= new Vector(xOff, yOff, 0);
|
||||
|
||||
ent.Teleport(endPos, deadRot, Vector.Zero);
|
||||
}
|
||||
|
||||
private void refreshLines() {
|
||||
@@ -182,7 +179,7 @@ public class PropMover(IServiceProvider provider) : IPluginModule {
|
||||
playerOrigin = playerOrigin.Clone()!;
|
||||
playerOrigin.Z += 64;
|
||||
|
||||
var eyeAngles = playerPawn!.EyeAngles;
|
||||
var eyeAngles = playerPawn.EyeAngles;
|
||||
|
||||
var targetVector = playerOrigin + eyeAngles.Clone()!.ToForward()
|
||||
* Math.Clamp(info.Distance, MIN_HOLDING_DISTANCE, MAX_HOLDING_DISTANCE);
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Role;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.CS2.Hats;
|
||||
using TTT.CS2.Roles;
|
||||
using TTT.Game.Events.Game;
|
||||
using TTT.Game.Events.Player;
|
||||
using TTT.Game.Listeners;
|
||||
using TTT.Game.Roles;
|
||||
|
||||
namespace TTT.CS2.GameHandlers;
|
||||
|
||||
public class RoleIconsHandler(IServiceProvider provider)
|
||||
: IPluginModule, IListener {
|
||||
private readonly IEventBus bus = provider.GetRequiredService<IEventBus>();
|
||||
: BaseListener(provider), IPluginModule {
|
||||
private static readonly string CT_MODEL =
|
||||
"characters/models/ctm_fbi/ctm_fbi_varianth.vmdl";
|
||||
|
||||
private static readonly string T_MODEL =
|
||||
"characters/models/tm_phoenix/tm_phoenix.vmdl";
|
||||
|
||||
private readonly IDictionary<int, IEnumerable<CPointWorldText>>
|
||||
detectiveIcons = new Dictionary<int, IEnumerable<CPointWorldText>>();
|
||||
|
||||
private readonly IPlayerConverter<CCSPlayerController> players =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
@@ -29,47 +39,34 @@ public class RoleIconsHandler(IServiceProvider provider)
|
||||
|
||||
private readonly ISet<int> traitors = new HashSet<int>();
|
||||
|
||||
public void Dispose() { bus.UnregisterListener(this); }
|
||||
|
||||
public string Name => nameof(RoleIconsHandler);
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
|
||||
public void Start() { }
|
||||
|
||||
public void Start(BasePlugin? plugin) {
|
||||
plugin
|
||||
?.RegisterListener<CounterStrikeSharp.API.Core.Listeners.CheckTransmit>(
|
||||
onTransmit);
|
||||
|
||||
bus.RegisterListener(this);
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[EventHandler(IgnoreCanceled = true)]
|
||||
public void OnRoundStart(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState != State.IN_PROGRESS) return;
|
||||
traitors.Clear();
|
||||
traitorIcons.Clear();
|
||||
detectiveIcons.Clear();
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[EventHandler(IgnoreCanceled = true)]
|
||||
public void OnAssigned(PlayerRoleAssignEvent ev) {
|
||||
var player = players.GetPlayer(ev.Player);
|
||||
if (player == null || !player.IsValid) return;
|
||||
|
||||
if (player.Team == CsTeam.Spectator) {
|
||||
ev.Role = new SpectatorRole(provider);
|
||||
ev.Role = new SpectatorRole(Provider);
|
||||
return;
|
||||
}
|
||||
|
||||
traitorIcons.TryGetValue(player.Slot, out var icons);
|
||||
if (icons != null) {
|
||||
foreach (var icon in icons) {
|
||||
if (!icon.IsValid) continue;
|
||||
icon.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
traitors.Remove(player.Slot);
|
||||
// Remove in case we're re-assigning for some reason
|
||||
removeAllIcons(player);
|
||||
|
||||
player.SwitchTeam(ev.Role is DetectiveRole ?
|
||||
CsTeam.CounterTerrorist :
|
||||
@@ -79,39 +76,59 @@ public class RoleIconsHandler(IServiceProvider provider)
|
||||
var pawn = player.Pawn.Value;
|
||||
if (pawn == null || !pawn.IsValid) return;
|
||||
|
||||
pawn.SetModel(ev.Role is DetectiveRole ?
|
||||
"characters/models/ctm_fbi/ctm_fbi_varianth.vmdl" :
|
||||
"characters/models/tm_phoenix/tm_phoenix.vmdl");
|
||||
pawn.SetModel(ev.Role is DetectiveRole ? CT_MODEL : T_MODEL);
|
||||
|
||||
if (ev.Role is InnocentRole) return;
|
||||
|
||||
assignIcon(player, ev.Role);
|
||||
}
|
||||
|
||||
private void assignIcon(CCSPlayerController player, IRole role) {
|
||||
var textSettings = new TextSetting {
|
||||
msg = ev.Role.Name.First(char.IsAsciiLetter) + "", color = ev.Role.Color
|
||||
msg = role.Name.First(char.IsAsciiLetter).ToString(), color = role.Color
|
||||
};
|
||||
var roleIcon = textSpawner?.CreateTextHat(textSettings, player);
|
||||
|
||||
if (roleIcon == null) return;
|
||||
|
||||
if (ev.Role is not TraitorRole) return;
|
||||
if (role is DetectiveRole) {
|
||||
detectiveIcons[player.Slot] = roleIcon;
|
||||
return;
|
||||
}
|
||||
|
||||
traitors.Add(player.Slot);
|
||||
traitorIcons[player.Slot] = roleIcon;
|
||||
}
|
||||
|
||||
private void removeAllIcons(CCSPlayerController player) {
|
||||
removeTraitorIcon(player);
|
||||
removeDetectiveIcon(player);
|
||||
}
|
||||
|
||||
private void removeTraitorIcon(CCSPlayerController player) {
|
||||
removeIcons(player.Slot, traitorIcons);
|
||||
}
|
||||
|
||||
private void removeDetectiveIcon(CCSPlayerController player) {
|
||||
removeIcons(player.Slot, detectiveIcons);
|
||||
}
|
||||
|
||||
private void removeIcons(int slot,
|
||||
IDictionary<int, IEnumerable<CPointWorldText>> cache) {
|
||||
cache.Remove(slot, out var icons);
|
||||
if (icons == null) return;
|
||||
foreach (var icon in icons) {
|
||||
if (!icon.IsValid) continue;
|
||||
icon.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
[EventHandler(Priority = Priority.MONITOR)]
|
||||
public void OnDeath(PlayerDeathEvent ev) {
|
||||
var gamePlayer = players.GetPlayer(ev.Victim);
|
||||
if (gamePlayer == null || !gamePlayer.IsValid) return;
|
||||
|
||||
if (!traitors.Contains(gamePlayer.Slot)) return;
|
||||
|
||||
traitorIcons.TryGetValue(gamePlayer.Slot, out var icons);
|
||||
if (icons != null) {
|
||||
foreach (var icon in icons) {
|
||||
if (!icon.IsValid) continue;
|
||||
icon.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
traitors.Remove(gamePlayer.Slot);
|
||||
removeAllIcons(gamePlayer);
|
||||
}
|
||||
|
||||
// ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Game;
|
||||
@@ -7,24 +8,24 @@ using TTT.Game.Roles;
|
||||
|
||||
namespace TTT.CS2.GameHandlers;
|
||||
|
||||
public class RoundEndHandler(IServiceProvider provider) : IPluginModule {
|
||||
public class RoundEnd_GameEndHandler(IServiceProvider provider)
|
||||
: IPluginModule {
|
||||
private readonly IGameManager games =
|
||||
provider.GetRequiredService<IGameManager>();
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public string Name => nameof(RoundEndHandler);
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
|
||||
public void Start() { }
|
||||
|
||||
[UsedImplicitly]
|
||||
[GameEventHandler]
|
||||
public HookResult OnRoundEnd(EventRoundEnd _, GameEventInfo _1) {
|
||||
if (!games.IsGameActive()) return HookResult.Continue;
|
||||
if (games.ActiveGame is not { State: State.IN_PROGRESS })
|
||||
return HookResult.Continue;
|
||||
var game = games.ActiveGame ?? throw new InvalidOperationException(
|
||||
"Active game is null, but round end event was triggered.");
|
||||
if (game.FinishedAt != null)
|
||||
// The game's round ended due to our TTT game ending
|
||||
// We caused this round to end already, don't end it again
|
||||
return HookResult.Continue;
|
||||
|
||||
game.EndGame(EndReason.TIMEOUT(new InnocentRole(provider)));
|
||||
@@ -1,5 +1,6 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Game;
|
||||
@@ -8,7 +9,8 @@ using TTT.Game;
|
||||
|
||||
namespace TTT.CS2.GameHandlers;
|
||||
|
||||
public class RoundStartHandler(IServiceProvider provider) : IPluginModule {
|
||||
public class RoundStart_GameStartHandler(IServiceProvider provider)
|
||||
: IPluginModule {
|
||||
private readonly TTTConfig config =
|
||||
provider.GetService<IStorage<TTTConfig>>()?.Load().GetAwaiter().GetResult()
|
||||
?? new TTTConfig();
|
||||
@@ -16,15 +18,15 @@ public class RoundStartHandler(IServiceProvider provider) : IPluginModule {
|
||||
private readonly IGameManager games =
|
||||
provider.GetRequiredService<IGameManager>();
|
||||
|
||||
public void Dispose() { throw new NotImplementedException(); }
|
||||
public string Name => nameof(RoundStartHandler);
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
public void Dispose() { }
|
||||
|
||||
public void Start() { }
|
||||
|
||||
[UsedImplicitly]
|
||||
[GameEventHandler]
|
||||
public HookResult OnRoundStart(EventRoundStart _, GameEventInfo _1) {
|
||||
if (games.IsGameActive()) return HookResult.Continue;
|
||||
if (games.ActiveGame is { State: State.IN_PROGRESS or State.COUNTDOWN })
|
||||
return HookResult.Continue;
|
||||
|
||||
var game = games.CreateGame();
|
||||
game?.Start(config.RoundCfg.CountDownDuration);
|
||||
@@ -19,4 +19,7 @@ public interface ITextSpawner {
|
||||
|
||||
IEnumerable<CPointWorldText> CreateTextHat(TextSetting setting,
|
||||
CCSPlayerController player);
|
||||
|
||||
IEnumerable<CPointWorldText> CreateTextScreen(TextSetting setting,
|
||||
CCSPlayerController player);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.CS2.RayTrace.Class;
|
||||
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
|
||||
|
||||
namespace TTT.CS2.Hats;
|
||||
@@ -36,10 +37,36 @@ public class TextSpawner : ITextSpawner {
|
||||
return [one, two];
|
||||
}
|
||||
|
||||
public IEnumerable<CPointWorldText> CreateTextScreen(TextSetting setting,
|
||||
CCSPlayerController player) {
|
||||
var screen = spawnScreen(setting, player);
|
||||
return [screen];
|
||||
}
|
||||
|
||||
private CPointWorldText spawnScreen(TextSetting setting,
|
||||
CCSPlayerController player) {
|
||||
if (player.Pawn.Value == null || player.Pawn.Value.AbsRotation == null)
|
||||
throw new Exception("Failed to get player rotation");
|
||||
var eyes = player.GetEyePosition().Clone()!;
|
||||
var localAngle = player.Pawn.Value.AbsRotation.Clone()!;
|
||||
var forward = localAngle.Clone()!.ToForward();
|
||||
var inFront = eyes + forward * 50;
|
||||
|
||||
var angle = new Angle(localAngle.X, localAngle.Y, 90);
|
||||
// point angle at player
|
||||
angle.Y += 180;
|
||||
angle.Pitch = 90;
|
||||
|
||||
var ent = CreateText(setting, inFront,
|
||||
new QAngle(angle.X, angle.Y, angle.Z));
|
||||
ent.AcceptInput("SetParent", player.Pawn.Value, null, "!activator");
|
||||
return ent;
|
||||
}
|
||||
|
||||
private CPointWorldText spawnHatPart(TextSetting setting,
|
||||
CCSPlayerController player, float yRot) {
|
||||
var position = player.PlayerPawn.Value?.AbsOrigin;
|
||||
var rotation = player.PlayerPawn.Value?.AbsRotation;
|
||||
var position = player.Pawn.Value?.AbsOrigin;
|
||||
var rotation = player.Pawn.Value?.AbsRotation;
|
||||
if (position == null || rotation == null)
|
||||
throw new Exception("Failed to get player position");
|
||||
position = position.Clone()!;
|
||||
@@ -49,7 +76,7 @@ public class TextSpawner : ITextSpawner {
|
||||
position.Add(GetRightVector(rotation) * -10);
|
||||
|
||||
var ent = CreateText(setting, position, rotation);
|
||||
ent.AcceptInput("SetParent", player.PlayerPawn.Value, null, "!activator");
|
||||
ent.AcceptInput("SetParent", player.Pawn.Value, null, "!activator");
|
||||
return ent;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,89 +1,67 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Messages;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Role;
|
||||
using TTT.CS2.API;
|
||||
using TTT.CS2.Events;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.Game;
|
||||
using TTT.Game.Events.Body;
|
||||
using TTT.Game.Events.Game;
|
||||
using TTT.Locale;
|
||||
using TTT.Game.Listeners;
|
||||
using TTT.Game.Roles;
|
||||
|
||||
namespace TTT.CS2.Listeners;
|
||||
|
||||
public class BodyPickupListener(IServiceProvider provider) : IListener {
|
||||
private readonly Dictionary<CBaseEntity, IBody> bodyCache = new();
|
||||
private readonly IEventBus bus = provider.GetRequiredService<IEventBus>();
|
||||
public class BodyPickupListener(IServiceProvider provider)
|
||||
: BaseListener(provider) {
|
||||
private readonly IBodyTracker bodies =
|
||||
provider.GetRequiredService<IBodyTracker>();
|
||||
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
private readonly IMsgLocalizer locale =
|
||||
provider.GetRequiredService<IMsgLocalizer>();
|
||||
|
||||
private readonly IMessenger msg = provider.GetRequiredService<IMessenger>();
|
||||
|
||||
private readonly IRoleAssigner roles =
|
||||
provider.GetRequiredService<IRoleAssigner>();
|
||||
|
||||
public void Dispose() { bus.UnregisterListener(this); }
|
||||
private readonly IAliveSpoofer? spoofer =
|
||||
provider.GetService<IAliveSpoofer>();
|
||||
|
||||
[EventHandler]
|
||||
public void OnGameState(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState != State.IN_PROGRESS) return;
|
||||
bodyCache.Clear();
|
||||
}
|
||||
|
||||
[EventHandler]
|
||||
public void OnBodyCreate(BodyCreateEvent ev) {
|
||||
if (!int.TryParse(ev.Body.Id, out var index))
|
||||
throw new ArgumentException(
|
||||
$"Body ID '{ev.Body.Id}' is not a valid entity index.");
|
||||
|
||||
var entity = Utilities.GetEntityFromIndex<CRagdollProp>(index);
|
||||
|
||||
if (entity == null || !entity.IsValid)
|
||||
throw new InvalidOperationException(
|
||||
$"Could not find valid entity for body ID '{ev.Body.Id}'.");
|
||||
|
||||
bodyCache[entity] = ev.Body;
|
||||
}
|
||||
|
||||
[EventHandler(Priority = Priority.HIGH)]
|
||||
public void OnPropPickup(PropPickupEvent ev) {
|
||||
var prop = ev.Prop as CRagdollProp;
|
||||
if (prop == null || !prop.IsValid) return;
|
||||
|
||||
if (!bodyCache.TryGetValue(prop, out var body)) return;
|
||||
if (body.IsIdentified) return;
|
||||
|
||||
if (!bodies.TryLookup(ev.Prop.Index.ToString(), out var body)) return;
|
||||
if (body == null || body.IsIdentified) return;
|
||||
if (ev.Player is not IOnlinePlayer online)
|
||||
throw new InvalidOperationException("Player is not an online player.");
|
||||
|
||||
var identifyEvent = new BodyIdentifyEvent(body, online);
|
||||
|
||||
bus.Dispatch(identifyEvent);
|
||||
Bus.Dispatch(identifyEvent);
|
||||
if (identifyEvent.IsCanceled) return;
|
||||
}
|
||||
|
||||
body.IsIdentified = true;
|
||||
var role = roles.GetRoles(body.OfPlayer);
|
||||
[EventHandler]
|
||||
public void OnIdentify(BodyIdentifyEvent ev) {
|
||||
ev.Body.IsIdentified = true;
|
||||
|
||||
var role = Roles.GetRoles(ev.Body.OfPlayer);
|
||||
if (role.Count == 0) return;
|
||||
var primaryRole = role.First();
|
||||
|
||||
prop.SetColor(primaryRole.Color);
|
||||
msg.MessageAll(
|
||||
locale[GameMsgs.BODY_IDENTIFIED(online, body.OfPlayer, primaryRole)]);
|
||||
var primary = role.First();
|
||||
|
||||
var onlinePlayer = converter.GetPlayer(body.OfPlayer);
|
||||
if (onlinePlayer == null || !onlinePlayer.IsValid) return;
|
||||
Messenger.MessageAll(Locale[
|
||||
GameMsgs.BODY_IDENTIFIED(ev.Identifier, ev.Body.OfPlayer, primary)]);
|
||||
|
||||
onlinePlayer.PawnIsAlive = false;
|
||||
onlinePlayer.SetClan(primaryRole.Name);
|
||||
Utilities.SetStateChanged(onlinePlayer, "CCSPlayerController",
|
||||
"m_bPawnIsAlive");
|
||||
if (!bodies.Bodies.TryGetValue(ev.Body, out var ragdoll)) return;
|
||||
|
||||
if (ragdoll.IsValid) ragdoll.SetColor(primary.Color);
|
||||
|
||||
var online = converter.GetPlayer(ev.Body.OfPlayer);
|
||||
if (online is not { IsValid: true }) return;
|
||||
|
||||
if (primary is InnocentRole) online.SwitchTeam(CsTeam.CounterTerrorist);
|
||||
|
||||
spoofer?.UnspoofAlive(online);
|
||||
online.PawnIsAlive = false;
|
||||
online.SetClan(primary.Name);
|
||||
Utilities.SetStateChanged(online, "CCSPlayerController", "m_bPawnIsAlive");
|
||||
}
|
||||
}
|
||||
38
TTT/CS2/Listeners/BodyTracker.cs
Normal file
38
TTT/CS2/Listeners/BodyTracker.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.CS2.API;
|
||||
using TTT.Game;
|
||||
using TTT.Game.Events.Body;
|
||||
using TTT.Game.Events.Game;
|
||||
using TTT.Game.Listeners;
|
||||
|
||||
namespace TTT.CS2.Listeners;
|
||||
|
||||
public class BodyTracker(IServiceProvider provider)
|
||||
: BaseListener(provider), IBodyTracker {
|
||||
private readonly Dictionary<IBody, CRagdollProp> bodyCache = new();
|
||||
public IDictionary<IBody, CRagdollProp> Bodies => bodyCache;
|
||||
|
||||
[EventHandler]
|
||||
public void OnGameState(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState != State.IN_PROGRESS) return;
|
||||
bodyCache.Clear();
|
||||
}
|
||||
|
||||
[EventHandler]
|
||||
public void OnBodyCreate(BodyCreateEvent ev) {
|
||||
if (!int.TryParse(ev.Body.Id, out var index))
|
||||
throw new ArgumentException(
|
||||
$"Body ID '{ev.Body.Id}' is not a valid entity index.");
|
||||
|
||||
var entity = Utilities.GetEntityFromIndex<CRagdollProp>(index);
|
||||
|
||||
if (entity == null || !entity.IsValid)
|
||||
throw new InvalidOperationException(
|
||||
$"Could not find valid entity for body ID '{ev.Body.Id}'.");
|
||||
|
||||
bodyCache[ev.Body] = entity;
|
||||
}
|
||||
}
|
||||
27
TTT/CS2/Listeners/LateSpawnListener.cs
Normal file
27
TTT/CS2/Listeners/LateSpawnListener.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.Game.Events.Player;
|
||||
using TTT.Game.Listeners;
|
||||
|
||||
namespace TTT.CS2.Listeners;
|
||||
|
||||
public class LateSpawnListener(IServiceProvider provider)
|
||||
: BaseListener(provider) {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
[EventHandler]
|
||||
public void OnJoin(PlayerJoinEvent ev) {
|
||||
if (Games.ActiveGame is { State: State.IN_PROGRESS }) return;
|
||||
|
||||
Server.NextWorldUpdate(() => {
|
||||
var player = converter.GetPlayer(ev.Player);
|
||||
if (player == null || !player.IsValid) return;
|
||||
player.Respawn();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Messages;
|
||||
using TTT.API.Player;
|
||||
using TTT.Game.Events.Body;
|
||||
using TTT.Game.Events.Game;
|
||||
@@ -17,13 +18,16 @@ public class PlayerStatsTracker(IServiceProvider provider) : IListener {
|
||||
private readonly IPlayerFinder finder =
|
||||
provider.GetRequiredService<IPlayerFinder>();
|
||||
|
||||
public void Dispose() { }
|
||||
private readonly IMessenger messenger =
|
||||
provider.GetRequiredService<IMessenger>();
|
||||
|
||||
private readonly ISet<int> revealedDeaths = new HashSet<int>();
|
||||
|
||||
private readonly IDictionary<int, (int, int)> roundKillsAndAssists =
|
||||
new Dictionary<int, (int, int)>();
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
[EventHandler(Priority = Priority.MONITOR)]
|
||||
public void OnIdentify(BodyIdentifyEvent ev) {
|
||||
var gamePlayer = converter.GetPlayer(ev.Body.OfPlayer);
|
||||
@@ -38,7 +42,9 @@ public class PlayerStatsTracker(IServiceProvider provider) : IListener {
|
||||
revealedDeaths.Add(gamePlayer.Slot);
|
||||
}
|
||||
|
||||
[EventHandler]
|
||||
// Needs to be higher so we detect the kill the game ends
|
||||
// in the case that this is the last player
|
||||
[EventHandler(Priority = Priority.HIGHER)]
|
||||
public void OnKill(PlayerDeathEvent ev) {
|
||||
var killer = ev.Killer == null ? null : converter.GetPlayer(ev.Killer);
|
||||
var assister =
|
||||
@@ -78,6 +84,9 @@ public class PlayerStatsTracker(IServiceProvider provider) : IListener {
|
||||
.Where(p => p.IsValid && !revealedDeaths.Contains(p.Slot));
|
||||
|
||||
foreach (var player in online) {
|
||||
player.PawnIsAlive = false;
|
||||
Utilities.SetStateChanged(player, "CCSPlayerController",
|
||||
"m_bPawnIsAlive");
|
||||
var stats = player.ActionTrackingServices?.MatchStats;
|
||||
if (stats == null) continue;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Drawing;
|
||||
using System.Reactive.Linq;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
@@ -8,22 +9,18 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Role;
|
||||
using TTT.API.Storage;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.CS2.Utils;
|
||||
using TTT.Game;
|
||||
using TTT.Game.Events.Game;
|
||||
using TTT.Game.Listeners;
|
||||
using TTT.Game.Roles;
|
||||
|
||||
namespace TTT.CS2.Listeners;
|
||||
|
||||
public class RoundTimerListener(IServiceProvider provider) : IListener {
|
||||
private readonly IEventBus bus = provider.GetRequiredService<IEventBus>();
|
||||
|
||||
private readonly IRoleAssigner roles =
|
||||
provider.GetRequiredService<IRoleAssigner>();
|
||||
|
||||
public class RoundTimerListener(IServiceProvider provider)
|
||||
: BaseListener(provider) {
|
||||
private readonly TTTConfig config = provider
|
||||
.GetRequiredService<IStorage<TTTConfig>>()
|
||||
.Load()
|
||||
@@ -33,8 +30,6 @@ public class RoundTimerListener(IServiceProvider provider) : IListener {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
public void Dispose() { bus.UnregisterListener(this); }
|
||||
|
||||
[UsedImplicitly]
|
||||
[EventHandler(IgnoreCanceled = true)]
|
||||
public void OnRoundStart(GameStateUpdateEvent ev) {
|
||||
@@ -44,12 +39,8 @@ public class RoundTimerListener(IServiceProvider provider) : IListener {
|
||||
.TotalSeconds);
|
||||
Server.ExecuteCommand("mp_ignore_round_win_conditions 1");
|
||||
foreach (var player in Utilities.GetPlayers()
|
||||
.Where(p => p.LifeState != (int)LifeState_t.LIFE_ALIVE)) {
|
||||
player.PawnIsAlive = true;
|
||||
.Where(p => p.LifeState != (int)LifeState_t.LIFE_ALIVE))
|
||||
player.Respawn();
|
||||
Utilities.SetStateChanged(player, "CCSPlayerController",
|
||||
"m_bPawnIsAlive");
|
||||
}
|
||||
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
player.SetColor(Color.FromArgb(254, 255, 255, 255));
|
||||
@@ -71,29 +62,57 @@ public class RoundTimerListener(IServiceProvider provider) : IListener {
|
||||
[EventHandler(IgnoreCanceled = true)]
|
||||
public void OnRoundEnd(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState != State.FINISHED) return;
|
||||
|
||||
revealRoles(ev.Game);
|
||||
|
||||
// If CS caused the round to end, we will have 0 time left
|
||||
// in this case, CS automatically handles the end of round stuff
|
||||
// so we don't need to do anything
|
||||
if (RoundUtil.GetTimeRemaining() <= 1) return;
|
||||
|
||||
foreach (var player in ev.Game.Players) {
|
||||
var csPlayer = converter.GetPlayer(player);
|
||||
if (csPlayer == null || !csPlayer.IsValid) continue;
|
||||
var role = roles.GetRoles(player).FirstOrDefault();
|
||||
if (role == null) continue;
|
||||
csPlayer.SetClan(role.Name, false);
|
||||
}
|
||||
Server.NextWorldUpdate(() => {
|
||||
var endReason = endRound(ev);
|
||||
|
||||
foreach (var inno in ev.Game.GetAlive(typeof(InnocentRole))) {
|
||||
var player = converter.GetPlayer(inno);
|
||||
player?.SwitchTeam(CsTeam.CounterTerrorist);
|
||||
}
|
||||
if (ev.Game.WinningRole != null)
|
||||
RoundUtil.AddTeamScore(
|
||||
endReason == RoundEndReason.CTsWin ?
|
||||
CsTeam.CounterTerrorist :
|
||||
CsTeam.Terrorist, 1);
|
||||
|
||||
new EventNextlevelChanged(true).FireEvent(false);
|
||||
var timer = Observable.Timer(
|
||||
config.RoundCfg.TimeBetweenRounds, Scheduler);
|
||||
timer.Subscribe(_
|
||||
=> Server.NextWorldUpdate(() => RoundUtil.EndRound(endReason)));
|
||||
});
|
||||
}
|
||||
|
||||
private RoundEndReason endRound(GameStateUpdateEvent ev) {
|
||||
var endReason =
|
||||
ev.Game.WinningRole != null && ev.Game.WinningRole.GetType()
|
||||
.IsAssignableTo(typeof(TraitorRole)) ?
|
||||
RoundEndReason.TerroristsWin :
|
||||
RoundEndReason.CTsWin;
|
||||
|
||||
RoundUtil.EndRound(endReason);
|
||||
var panelWinEvent = new EventCsWinPanelRound(true);
|
||||
var winningTeam = endReason == RoundEndReason.TerroristsWin ?
|
||||
CsTeam.Terrorist :
|
||||
CsTeam.CounterTerrorist;
|
||||
panelWinEvent.Set("final_event",
|
||||
winningTeam == CsTeam.CounterTerrorist ? 2 : 3);
|
||||
panelWinEvent.FireEvent(false);
|
||||
return endReason;
|
||||
}
|
||||
|
||||
private void revealRoles(IGame game) {
|
||||
foreach (var player in game.Players) {
|
||||
var csPlayer = converter.GetPlayer(player);
|
||||
if (csPlayer == null || !csPlayer.IsValid) continue;
|
||||
var role = Roles.GetRoles(player).FirstOrDefault();
|
||||
if (role == null) continue;
|
||||
csPlayer.SetClan(role.Name, false);
|
||||
if (role is InnocentRole) csPlayer.SwitchTeam(CsTeam.CounterTerrorist);
|
||||
}
|
||||
|
||||
new EventNextlevelChanged(true).FireEvent(false);
|
||||
}
|
||||
}
|
||||
@@ -38,8 +38,6 @@ public class CCPlayerConverter : IPluginModule,
|
||||
}
|
||||
|
||||
public void Dispose() { playerCache.Clear(); }
|
||||
public string Name => "PlayerConverter";
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
|
||||
public void Start() { }
|
||||
}
|
||||
65
TTT/CS2/Player/CS2AliveSpoofer.cs
Normal file
65
TTT/CS2/Player/CS2AliveSpoofer.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using TTT.API;
|
||||
using TTT.CS2.API;
|
||||
|
||||
namespace TTT.CS2.Player;
|
||||
|
||||
public class CS2AliveSpoofer : IAliveSpoofer, IPluginModule {
|
||||
private readonly HashSet<CCSPlayerController> _fakeAlivePlayers = new();
|
||||
public ISet<CCSPlayerController> FakeAlivePlayers => _fakeAlivePlayers;
|
||||
|
||||
public void SpoofAlive(CCSPlayerController player) {
|
||||
if (player.IsBot) {
|
||||
player.PawnIsAlive = true;
|
||||
Utilities.SetStateChanged(player, "CCSPlayerController",
|
||||
"m_bPawnIsAlive");
|
||||
return;
|
||||
}
|
||||
|
||||
FakeAlivePlayers.Add(player);
|
||||
|
||||
Server.NextWorldUpdate(() => {
|
||||
var pawn = player.Pawn.Value;
|
||||
if (pawn == null || !pawn.IsValid) return;
|
||||
pawn.DeathTime = 0;
|
||||
Utilities.SetStateChanged(pawn, "CBasePlayerPawn", "m_flDeathTime");
|
||||
Utilities.SetStateChanged(pawn, "CBasePlayerController", "m_flDeathTime");
|
||||
|
||||
Server.NextWorldUpdate(() => {
|
||||
player.PawnIsAlive = true;
|
||||
Utilities.SetStateChanged(player, "CCSPlayerController",
|
||||
"m_bPawnIsAlive");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void UnspoofAlive(CCSPlayerController player) {
|
||||
if (player.IsBot) {
|
||||
if (player.Pawn.Value != null && player.Pawn.Value.Health > 0) return;
|
||||
player.PawnIsAlive = false;
|
||||
Utilities.SetStateChanged(player, "CCSPlayerController",
|
||||
"m_bPawnIsAlive");
|
||||
return;
|
||||
}
|
||||
|
||||
FakeAlivePlayers.Remove(player);
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
public void Start(BasePlugin? plugin) {
|
||||
plugin?.RegisterListener<CounterStrikeSharp.API.Core.Listeners.OnTick>(
|
||||
onTick);
|
||||
}
|
||||
|
||||
private void onTick() {
|
||||
_fakeAlivePlayers.RemoveWhere(p => !p.IsValid);
|
||||
foreach (var player in _fakeAlivePlayers) {
|
||||
player.PawnIsAlive = true;
|
||||
Utilities.SetStateChanged(player, "CCSPlayerController",
|
||||
"m_bPawnIsAlive");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using TTT.API;
|
||||
using TTT.API.Player;
|
||||
@@ -8,48 +9,56 @@ namespace TTT.CS2.Player;
|
||||
public class CS2InventoryManager(
|
||||
IPlayerConverter<CCSPlayerController> converter) : IInventoryManager {
|
||||
public void GiveWeapon(IOnlinePlayer player, IWeapon weapon) {
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
Server.NextWorldUpdate(() => {
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
|
||||
gamePlayer.GiveNamedItem(weapon.Id);
|
||||
if (weapon.ReserveAmmo == null && weapon.CurrentAmmo == null) return;
|
||||
var weaponBase = gamePlayer.GetWeaponBase(weapon.Id);
|
||||
if (weaponBase == null) return;
|
||||
if (weapon.CurrentAmmo != null) weaponBase.Clip1 = weapon.CurrentAmmo.Value;
|
||||
if (weapon.ReserveAmmo != null) weaponBase.Clip2 = weapon.ReserveAmmo.Value;
|
||||
gamePlayer.GiveNamedItem(weapon.Id);
|
||||
if (weapon.ReserveAmmo == null && weapon.CurrentAmmo == null) return;
|
||||
var weaponBase = gamePlayer.GetWeaponBase(weapon.Id);
|
||||
if (weaponBase == null) return;
|
||||
if (weapon.CurrentAmmo != null)
|
||||
weaponBase.Clip1 = weapon.CurrentAmmo.Value;
|
||||
if (weapon.ReserveAmmo != null)
|
||||
weaponBase.Clip2 = weapon.ReserveAmmo.Value;
|
||||
});
|
||||
}
|
||||
|
||||
public void RemoveWeapon(IOnlinePlayer player, string weaponId) {
|
||||
if (!player.IsAlive) return;
|
||||
Server.NextWorldUpdate(() => {
|
||||
if (!player.IsAlive) return;
|
||||
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
|
||||
var pawn = gamePlayer.PlayerPawn.Value;
|
||||
var pawn = gamePlayer.Pawn.Value;
|
||||
|
||||
if (pawn == null || pawn.WeaponServices == null) return;
|
||||
if (pawn == null || pawn.WeaponServices == null) return;
|
||||
|
||||
var matchedWeapon =
|
||||
pawn.WeaponServices.MyWeapons.FirstOrDefault(x
|
||||
=> x.Value?.DesignerName == weaponId);
|
||||
var matchedWeapon =
|
||||
pawn.WeaponServices.MyWeapons.FirstOrDefault(x
|
||||
=> x.Value?.DesignerName == weaponId);
|
||||
|
||||
if (matchedWeapon?.Value == null || !matchedWeapon.IsValid) return;
|
||||
pawn.WeaponServices.ActiveWeapon.Raw = matchedWeapon.Raw;
|
||||
if (matchedWeapon?.Value == null || !matchedWeapon.IsValid) return;
|
||||
pawn.WeaponServices.ActiveWeapon.Raw = matchedWeapon.Raw;
|
||||
|
||||
// Make them equip the desired weapon
|
||||
var activeWeaponEntity =
|
||||
pawn.WeaponServices.ActiveWeapon.Value?.As<CBaseEntity>();
|
||||
// Make them equip the desired weapon
|
||||
var activeWeaponEntity =
|
||||
pawn.WeaponServices.ActiveWeapon.Value?.As<CBaseEntity>();
|
||||
|
||||
gamePlayer.DropActiveWeapon();
|
||||
activeWeaponEntity?.AddEntityIOEvent("Kill", activeWeaponEntity);
|
||||
gamePlayer.DropActiveWeapon();
|
||||
activeWeaponEntity?.AddEntityIOEvent("Kill", activeWeaponEntity);
|
||||
});
|
||||
}
|
||||
|
||||
public void RemoveAllWeapons(IOnlinePlayer player) {
|
||||
if (!player.IsAlive) return;
|
||||
Server.NextWorldUpdate(() => {
|
||||
if (!player.IsAlive) return;
|
||||
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
|
||||
gamePlayer.RemoveWeapons();
|
||||
gamePlayer.RemoveWeapons();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ namespace TTT.CS2.Player;
|
||||
/// Note that slot numbers are not guaranteed to be stable across server restarts.
|
||||
/// </summary>
|
||||
public class CS2Player : IOnlinePlayer {
|
||||
private CCSPlayerController? cachePlayer;
|
||||
|
||||
protected CS2Player(string id, string name) {
|
||||
Id = id;
|
||||
Name = name;
|
||||
@@ -33,26 +35,36 @@ public class CS2Player : IOnlinePlayer {
|
||||
|
||||
private CCSPlayerController? Player {
|
||||
get {
|
||||
if (cachePlayer != null && cachePlayer.IsValid) return cachePlayer;
|
||||
var player = Utilities.GetPlayerFromSteamId(ulong.Parse(Id))
|
||||
?? Utilities.GetPlayerFromIndex(int.Parse(Id));
|
||||
#if DEBUG
|
||||
if (player == null || !player.IsValid)
|
||||
Console.WriteLine("Failed to find player with ID: " + Id);
|
||||
#endif
|
||||
if (player != null && player.IsValid) cachePlayer = player;
|
||||
return player is { IsValid: true } ? player : null;
|
||||
}
|
||||
}
|
||||
|
||||
private int namePadding
|
||||
=> Math.Min(Utilities.GetPlayers().Select(p => p.PlayerName.Length).Max(),
|
||||
24);
|
||||
|
||||
public string Id { get; }
|
||||
public string Name { get; }
|
||||
|
||||
// public ICollection<IRole> Roles { get; } = [];
|
||||
|
||||
public int Health {
|
||||
get => Player?.Pawn.Value != null ? Player.Pawn.Value.Health : 0;
|
||||
|
||||
set {
|
||||
if (Player?.Pawn.Value == null) return;
|
||||
|
||||
if (value <= 0) {
|
||||
Player.CommitSuicide(false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
Player.Pawn.Value.Health = value;
|
||||
Utilities.SetStateChanged(Player.Pawn.Value, "CBaseEntity", "m_iHealth");
|
||||
}
|
||||
@@ -70,21 +82,17 @@ public class CS2Player : IOnlinePlayer {
|
||||
}
|
||||
|
||||
public int Armor {
|
||||
get
|
||||
=> Player?.PlayerPawn.Value != null ?
|
||||
Player.PlayerPawn.Value.ArmorValue :
|
||||
0;
|
||||
get => Player?.PawnArmor ?? 0;
|
||||
|
||||
set {
|
||||
if (Player?.PlayerPawn.Value == null) return;
|
||||
Player.PlayerPawn.Value.ArmorValue = value;
|
||||
Utilities.SetStateChanged(Player.PlayerPawn.Value, "CCSPlayerPawn",
|
||||
"m_ArmorValue");
|
||||
if (Player == null) return;
|
||||
Player.PawnArmor = value;
|
||||
Utilities.SetStateChanged(Player, "CCSPlayerController", "m_iPawnArmor");
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAlive {
|
||||
get => Player != null && Player.PlayerPawn.Value is { Health: > 0 };
|
||||
get => Player != null && Player.Pawn.Value is { Health: > 0 };
|
||||
|
||||
set
|
||||
=> throw new NotSupportedException(
|
||||
@@ -96,7 +104,18 @@ public class CS2Player : IOnlinePlayer {
|
||||
return player.SteamID.ToString();
|
||||
}
|
||||
|
||||
public override string ToString() { return $"({getSuffix(Id, 5)}) {Name}"; }
|
||||
public override string ToString() { return createPaddedName(); }
|
||||
|
||||
// Goal: Pad the name to a fixed width for better alignment in logs
|
||||
// Left-align ID, right-align name
|
||||
private string createPaddedName() {
|
||||
var idPart = $"({getSuffix(Id, 5)})";
|
||||
var effectivePadding = namePadding - idPart.Length;
|
||||
var namePart = Name.Length >= effectivePadding ?
|
||||
getSuffix(Name, effectivePadding) :
|
||||
Name.PadLeft(effectivePadding);
|
||||
return $"{idPart} {namePart}";
|
||||
}
|
||||
|
||||
private string getSuffix(string s, int len) {
|
||||
return s.Length <= len ? s : s[^len..];
|
||||
|
||||
12
TTT/CS2/RayTrace/Class/Address.cs
Normal file
12
TTT/CS2/RayTrace/Class/Address.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace TTT.CS2.RayTrace.Class;
|
||||
|
||||
internal static class Address {
|
||||
public static unsafe IntPtr GetAbsoluteAddress(IntPtr addr, IntPtr offset,
|
||||
int size) {
|
||||
if (addr == IntPtr.Zero)
|
||||
throw new Exception("Failed to find RayTrace signature.");
|
||||
|
||||
var code = *(int*)(addr + offset);
|
||||
return addr + code + size;
|
||||
}
|
||||
}
|
||||
226
TTT/CS2/RayTrace/Class/GameTraceExtensions.cs
Normal file
226
TTT/CS2/RayTrace/Class/GameTraceExtensions.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
using System.Numerics;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using TTT.CS2.RayTrace.Enum;
|
||||
using TTT.CS2.RayTrace.Struct;
|
||||
|
||||
namespace TTT.CS2.RayTrace.Class;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="CGameTrace" /> class
|
||||
/// </summary>
|
||||
public static class GameTraceExtensions {
|
||||
/// <summary>
|
||||
/// Determines if the trace hit anything.
|
||||
/// </summary>
|
||||
public static bool DidHit(this CGameTrace gameTrace) {
|
||||
return gameTrace is { Fraction: < 1.0f, AllSolid: false };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the distance between the start and end positions of the trace.
|
||||
/// </summary>
|
||||
public static float Distance(this CGameTrace gametrace) {
|
||||
return Vector3.Distance(gametrace.StartPos, gametrace.EndPos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the normalized direction vector of the trace.
|
||||
/// </summary>
|
||||
public static Vector3 Direction(this CGameTrace gametrace) {
|
||||
return Vector3.Normalize(gametrace.EndPos - gametrace.StartPos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the entity of type <typeparamref name="T" /> if the trace hit an entity
|
||||
/// with a designer name matching the specified pattern.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of entity to check for.</typeparam>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="entity">The entity that was hit, if any.</param>
|
||||
/// <param name="designerName">The designer name pattern to match.</param>
|
||||
/// <param name="matchType">The type of matching to perform.</param>
|
||||
/// <returns>True if an entity of type <typeparamref name="T" /> matching the pattern was hit, false otherwise.</returns>
|
||||
public static bool HitEntityByDesignerName<T>(this CGameTrace gametrace,
|
||||
out T? entity, string designerName,
|
||||
DesignerNameMatchType matchType = DesignerNameMatchType.Equals)
|
||||
where T : CEntityInstance {
|
||||
if ((T?)Activator.CreateInstance(typeof(T), gametrace.HitEntity)
|
||||
is { } entityInstance) {
|
||||
var isMatch = matchType switch {
|
||||
DesignerNameMatchType.Equals => entityInstance.DesignerName
|
||||
== designerName,
|
||||
DesignerNameMatchType.StartsWith => entityInstance.DesignerName
|
||||
.StartsWith(designerName, StringComparison.OrdinalIgnoreCase),
|
||||
DesignerNameMatchType.EndsWith => entityInstance.DesignerName.EndsWith(
|
||||
designerName, StringComparison.OrdinalIgnoreCase),
|
||||
_ => false
|
||||
};
|
||||
|
||||
if (isMatch) {
|
||||
entity = entityInstance;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
entity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the player controller if the trace hit a player.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="player">The player controller that was hit, if any.</param>
|
||||
/// <returns>True if a player was hit, false otherwise.</returns>
|
||||
public static bool HitPlayer(this CGameTrace gametrace,
|
||||
out CCSPlayerController? player) {
|
||||
if (gametrace.HitEntityByDesignerName(out CCSPlayerPawn? playerPawn,
|
||||
"player")) {
|
||||
player = playerPawn?.OriginalController.Value;
|
||||
return player != null;
|
||||
}
|
||||
|
||||
player = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the weapon if the trace hit a weapon.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="weapon">The weapon that was hit, if any.</param>
|
||||
/// <returns>True if a weapon was hit, false otherwise.</returns>
|
||||
public static bool
|
||||
HitWeapon(this CGameTrace gametrace, out CBasePlayerWeapon? weapon) {
|
||||
return gametrace.HitEntityByDesignerName(out weapon, "weapon_",
|
||||
DesignerNameMatchType.StartsWith);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the chicken if the trace hit a chicken.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="chicken">The chicken that was hit, if any.</param>
|
||||
/// <returns>True if a chicken was hit, false otherwise.</returns>
|
||||
public static bool HitChicken(this CGameTrace gametrace,
|
||||
out CChicken? chicken) {
|
||||
return gametrace.HitEntityByDesignerName(out chicken, "chicken");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the button if the trace hit a button.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="button">The button that was hit, if any.</param>
|
||||
/// <returns>True if a button was hit, false otherwise.</returns>
|
||||
public static bool HitButton(this CGameTrace gametrace,
|
||||
out CBaseButton? button) {
|
||||
return gametrace.HitEntityByDesignerName(out button, "func_door");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the buyzone if the trace hit a buyzone.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="buyzone">The buyzone that was hit, if any.</param>
|
||||
/// <returns>True if a buyzone was hit, false otherwise.</returns>
|
||||
public static bool
|
||||
HitBuyzone(this CGameTrace gametrace, out CBuyZone? buyzone) {
|
||||
return gametrace.HitEntityByDesignerName(out buyzone, "func_buyzone");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the sky if the trace hit the sky.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="sky">The sky that was hit, if any.</param>
|
||||
/// <returns>True if the sky was hit, false otherwise.</returns>
|
||||
public static bool HitSky(this CGameTrace gametrace, out CEnvSky? sky) {
|
||||
return gametrace.HitEntityByDesignerName(out sky, "env_sky");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the door if the trace hit a door.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="door">The door that was hit, if any.</param>
|
||||
/// <returns>True if a door was hit, false otherwise.</returns>
|
||||
public static bool HitDoor(this CGameTrace gametrace, out CBaseDoor? door) {
|
||||
return gametrace.HitEntityByDesignerName(out door, "func_door");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the rotating door if the trace hit a rotating door.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="door">The rotating door that was hit, if any.</param>
|
||||
/// <returns>True if a rotating door was hit, false otherwise.</returns>
|
||||
public static bool HitDoor(this CGameTrace gametrace, out CRotDoor? door) {
|
||||
return gametrace.HitEntityByDesignerName(out door, "func_door_rotating");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the ladder if the trace hit a ladder.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="ladder">The ladder that was hit, if any.</param>
|
||||
/// <returns>True if a ladder was hit, false otherwise.</returns>
|
||||
public static bool HitLadder(this CGameTrace gametrace,
|
||||
out CFuncLadder? ladder) {
|
||||
return gametrace.HitEntityByDesignerName(out ladder, "func_ladder");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the grenade if the trace hit a grenade.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="grenade">The grenade that was hit, if any.</param>
|
||||
/// <returns>True if a grenade was hit, false otherwise.</returns>
|
||||
public static bool HitGrenade(this CGameTrace gametrace,
|
||||
out CBaseCSGrenade? grenade) {
|
||||
return gametrace.HitEntityByDesignerName(out grenade, "grenade");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the planted C4 if the trace hit a planted C4.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="c4">The planted C4 that was hit, if any.</param>
|
||||
/// <returns>True if a planted C4 was hit, false otherwise.</returns>
|
||||
public static bool
|
||||
HitPlantedC4(this CGameTrace gametrace, out CPlantedC4? c4) {
|
||||
return gametrace.HitEntityByDesignerName(out c4, "planted_c4");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the world text if the trace hit a point world text.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="pointWorldText">The point world text that was hit, if any.</param>
|
||||
/// <returns>True if a point world text was hit, false otherwise.</returns>
|
||||
public static bool HitPointWorldText(this CGameTrace gametrace,
|
||||
out CPointWorldText? pointWorldText) {
|
||||
return gametrace.HitEntityByDesignerName(out pointWorldText,
|
||||
"point_worldtext");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the C4 if the trace hit a C4 weapon.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="c4">The C4 weapon that was hit, if any.</param>
|
||||
/// <returns>True if a C4 weapon was hit, false otherwise.</returns>
|
||||
public static bool HitC4(this CGameTrace gametrace, out CC4? c4) {
|
||||
return gametrace.HitEntityByDesignerName(out c4, "weapon_c4");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the world entity if the trace hit the world.
|
||||
/// </summary>
|
||||
/// <param name="gametrace">The gametrace</param>
|
||||
/// <param name="world">The world entity that was hit, if any.</param>
|
||||
/// <returns>True if the world was hit, false otherwise.</returns>
|
||||
public static bool HitWorld(this CGameTrace gametrace, out CWorld? world) {
|
||||
return gametrace.HitEntityByDesignerName(out world, "worldent");
|
||||
}
|
||||
}
|
||||
113
TTT/CS2/RayTrace/Class/PlayerExtensions.cs
Normal file
113
TTT/CS2/RayTrace/Class/PlayerExtensions.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using TTT.CS2.RayTrace.Enum;
|
||||
using TTT.CS2.RayTrace.Struct;
|
||||
|
||||
namespace TTT.CS2.RayTrace.Class;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="CCSPlayerController" /> and <see cref="CCSPlayerPawn" /> classes
|
||||
/// to support various player-related operations including trace rays and position calculations.
|
||||
/// </summary>
|
||||
public static class PlayerExtensions {
|
||||
/// <summary>
|
||||
/// Performs a game trace from the player's eye position in the direction they are looking.
|
||||
/// </summary>
|
||||
/// <param name="player">The player controller to trace from.</param>
|
||||
/// <param name="mask">The trace mask to use for collision detection.</param>
|
||||
/// <param name="contents">The content flags to filter the trace.</param>
|
||||
/// <param name="skipPlayer">Optional player whose pawn should be ignored in the trace.</param>
|
||||
/// <returns>A <see cref="CGameTrace" /> object containing the trace results, or null if the trace couldn't be performed.</returns>
|
||||
public static CGameTrace? GetGameTraceByEyePosition(
|
||||
this CCSPlayerController player, TraceMask mask, Contents contents,
|
||||
CCSPlayerController? skipPlayer) {
|
||||
return player.PlayerPawn.Value?.GetGameTraceByEyePosition(mask, contents,
|
||||
skipPlayer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a game trace from the player pawn's eye position in the direction they are looking.
|
||||
/// </summary>
|
||||
/// <param name="playerPawn">The player pawn to trace from.</param>
|
||||
/// <param name="mask">The trace mask to use for collision detection.</param>
|
||||
/// <param name="contents">The contents flags to filter the trace.</param>
|
||||
/// <param name="skipPlayer">Optional player whose pawn should be ignored in the trace.</param>
|
||||
/// <returns>A <see cref="CGameTrace" /> object containing the trace results, or null if the trace couldn't be performed.</returns>
|
||||
public static CGameTrace? GetGameTraceByEyePosition(
|
||||
this CCSPlayerPawn playerPawn, TraceMask mask, Contents contents,
|
||||
CCSPlayerController? skipPlayer) {
|
||||
if (playerPawn.GetEyePosition() is not { } eyePosition) return null;
|
||||
|
||||
var skip = skipPlayer?.PlayerPawn.Value?.Handle ?? IntPtr.Zero;
|
||||
var eyeAngles = playerPawn.EyeAngles;
|
||||
var _trace =
|
||||
TraceRay.TraceShape(eyePosition, eyeAngles, mask, contents, skip);
|
||||
|
||||
return _trace;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the eye position of the player in world coordinates.
|
||||
/// </summary>
|
||||
/// <param name="player">The player controller to get the eye position from.</param>
|
||||
/// <returns>A <see cref="Vector" /> representing the eye position, or null if the position couldn't be determined.</returns>
|
||||
public static Vector? GetEyePosition(this CCSPlayerController player) {
|
||||
return player.PlayerPawn.Value?.GetEyePosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the eye position of the player pawn in world coordinates.
|
||||
/// </summary>
|
||||
/// <param name="playerPawn">The player pawn to get the eye position from.</param>
|
||||
/// <returns>A <see cref="Vector" /> representing the eye position, or null if the position couldn't be determined.</returns>
|
||||
public static Vector? GetEyePosition(this CCSPlayerPawn playerPawn) {
|
||||
return playerPawn.AbsOrigin is not { } absOrigin ?
|
||||
null :
|
||||
new Vector(absOrigin.X, absOrigin.Y,
|
||||
absOrigin.Z + playerPawn.ViewOffset.Z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the vertical distance from the player to the ground below them.
|
||||
/// </summary>
|
||||
/// <param name="player">The player controller to measure from.</param>
|
||||
/// <returns>The distance in units, or 0 if the player is on the ground or the measurement couldn't be taken.</returns>
|
||||
public static float GetGroundDistance(this CCSPlayerController player) {
|
||||
return player.PlayerPawn.Value?.GetGroundDistance() ?? 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the vertical distance from the player pawn to the ground below them.
|
||||
/// </summary>
|
||||
/// <param name="playerPawn">The player pawn to measure from.</param>
|
||||
/// <returns>The distance in units, or 0 if the player is on the ground or the measurement couldn't be taken.</returns>
|
||||
public static float GetGroundDistance(this CCSPlayerPawn playerPawn) {
|
||||
if (playerPawn.GroundEntity.IsValid
|
||||
|| playerPawn.AbsOrigin is not { } absOrigin)
|
||||
return 0.0f;
|
||||
|
||||
var _trace = TraceRay.TraceShape(absOrigin, new QAngle(90, 0, 0),
|
||||
TraceMask.MaskAll, Contents.Sky, 0);
|
||||
return _trace.Distance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the bitmask representing the content layers this pawn interacts with (trace mask).
|
||||
/// Commonly used in trace and collision filtering logic.
|
||||
/// </summary>
|
||||
/// <param name="pawn">The player pawn instance.</param>
|
||||
/// <returns>The interaction bitmask from the pawn's collision attributes.</returns>
|
||||
public static ulong GetInteractsWith(this CCSPlayerPawn pawn) {
|
||||
return pawn.Collision.CollisionAttribute.InteractsWith;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the hierarchy ID used for organizing entity relationships during collision detection.
|
||||
/// This ID helps optimize trace results by skipping or including entities based on hierarchy context.
|
||||
/// </summary>
|
||||
/// <param name="pawn">The player pawn instance.</param>
|
||||
/// <returns>The hierarchy ID from the pawn's collision attributes.</returns>
|
||||
public static ushort GetHierarchyId(this CCSPlayerPawn pawn) {
|
||||
return pawn.Collision.CollisionAttribute.HierarchyId;
|
||||
}
|
||||
}
|
||||
185
TTT/CS2/RayTrace/Class/TraceRay.cs
Normal file
185
TTT/CS2/RayTrace/Class/TraceRay.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using TTT.CS2.RayTrace.Struct;
|
||||
|
||||
namespace TTT.CS2.RayTrace.Class;
|
||||
|
||||
/// <summary>
|
||||
/// Provides static methods for performing trace operations in CS2.
|
||||
/// </summary>
|
||||
public static unsafe partial class TraceRay {
|
||||
private static readonly IntPtr CTraceFilterVtable;
|
||||
private static readonly IntPtr GameTraceManager;
|
||||
private static readonly TraceShapeDelegate _traceShape;
|
||||
private static readonly TraceShapeRayFilterDelegate _traceShapeRayFilter;
|
||||
|
||||
static TraceRay() {
|
||||
var traceFunc = NativeAPI.FindSignature(Addresses.ServerPath,
|
||||
GameData.GetSignature("TraceFunc"));
|
||||
var traceShape = NativeAPI.FindSignature(Addresses.ServerPath,
|
||||
GameData.GetSignature("TraceShape"));
|
||||
CTraceFilterVtable = NativeAPI.FindSignature(Addresses.ServerPath,
|
||||
GameData.GetSignature("CTraceFilterVtable"));
|
||||
GameTraceManager = NativeAPI.FindSignature(Addresses.ServerPath,
|
||||
GameData.GetSignature("GameTraceManager"));
|
||||
_traceShape =
|
||||
Marshal.GetDelegateForFunctionPointer<TraceShapeDelegate>(traceFunc);
|
||||
_traceShapeRayFilter =
|
||||
Marshal.GetDelegateForFunctionPointer<TraceShapeRayFilterDelegate>(
|
||||
traceShape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags
|
||||
/// </summary>
|
||||
/// <param name="origin">Starting position of the trace</param>
|
||||
/// <param name="angle">Direction of the trace</param>
|
||||
/// <param name="mask">Trace mask flags as ulong</param>
|
||||
/// <param name="content">Content flags as ulong</param>
|
||||
/// <param name="skip">Entity to skip (IntPtr handle)</param>
|
||||
/// <returns>CGameTrace containing the trace results</returns>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle, ulong mask,
|
||||
ulong content, IntPtr skip) {
|
||||
Vector _forward = new();
|
||||
NativeAPI.AngleVectors(angle.Handle, _forward.Handle, 0, 0);
|
||||
Vector _endOrigin = new(origin.X + _forward.X * 8192,
|
||||
origin.Y + _forward.Y * 8192, origin.Z + _forward.Z * 8192);
|
||||
|
||||
return TraceShape(origin, _endOrigin, mask, content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and content flags
|
||||
/// </summary>
|
||||
/// <param name="start">Starting position of the trace</param>
|
||||
/// <param name="end">Ending position of the trace</param>
|
||||
/// <param name="mask">Trace mask flags as ulong</param>
|
||||
/// <param name="content">Content flags as ulong</param>
|
||||
/// <param name="skip">Entity to skip (IntPtr handle)</param>
|
||||
/// <returns>CGameTrace containing the trace results</returns>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, ulong mask,
|
||||
ulong content, IntPtr skip) {
|
||||
var _trace = stackalloc CGameTrace[1];
|
||||
var _gameTraceManagerAddress =
|
||||
Address.GetAbsoluteAddress(GameTraceManager, 3, 7);
|
||||
|
||||
_traceShape(*(IntPtr*)_gameTraceManagerAddress, start.Handle, end.Handle,
|
||||
skip, mask, content, _trace);
|
||||
|
||||
return *_trace;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags
|
||||
/// </summary>
|
||||
/// <param name="origin">Starting position of the trace</param>
|
||||
/// <param name="angle">Direction of the trace</param>
|
||||
/// <param name="mask">Trace mask flags as ulong</param>
|
||||
/// <param name="content">Content flags as ulong</param>
|
||||
/// <param name="skip">Entity to skip (IntPtr handle)</param>
|
||||
/// <param name="result">Return of _traceShape</param>
|
||||
/// <returns>CGameTrace containing the trace results</returns>
|
||||
public static CGameTrace TraceShapeWithResult(Vector origin, QAngle angle,
|
||||
ulong mask, ulong content, IntPtr skip, out bool result) {
|
||||
Vector _forward = new();
|
||||
NativeAPI.AngleVectors(angle.Handle, _forward.Handle, 0, 0);
|
||||
Vector _endOrigin = new(origin.X + _forward.X * 8192,
|
||||
origin.Y + _forward.Y * 8192, origin.Z + _forward.Z * 8192);
|
||||
|
||||
return TraceShapeWithResult(origin, _endOrigin, mask, content, skip,
|
||||
out result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and content flags
|
||||
/// </summary>
|
||||
/// <param name="start">Starting position of the trace</param>
|
||||
/// <param name="end">Ending position of the trace</param>
|
||||
/// <param name="mask">Trace mask flags as ulong</param>
|
||||
/// <param name="content">Content flags as ulong</param>
|
||||
/// <param name="skip">Entity to skip (IntPtr handle)</param>
|
||||
/// <param name="result">Return of _traceShape</param>
|
||||
/// <returns>CGameTrace containing the trace results</returns>
|
||||
public static CGameTrace TraceShapeWithResult(Vector start, Vector end,
|
||||
ulong mask, ulong content, IntPtr skip, out bool result) {
|
||||
var _trace = stackalloc CGameTrace[1];
|
||||
var _gameTraceManagerAddress =
|
||||
Address.GetAbsoluteAddress(GameTraceManager, 3, 7);
|
||||
|
||||
result = _traceShape(*(IntPtr*)_gameTraceManagerAddress, start.Handle,
|
||||
end.Handle, skip, mask, content, _trace);
|
||||
|
||||
return *_trace;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a hull-based ray trace using the provided shape, direction, and filter information.
|
||||
/// This method wraps the native _traceShapeRayFilter call, setting up the necessary filter and trace data on the stack.
|
||||
/// </summary>
|
||||
/// <param name="start">Starting position of the trace</param>
|
||||
/// <param name="end">Starting position of the trace</param>
|
||||
/// <param name="filter"> The filter used to determine which entities or collisions should be excluded during the trace./// </param>
|
||||
/// <param name="ray">A pointer to the shape of the ray (e.g., line, sphere, hull, capsule, mesh) to be traced.</param>
|
||||
/// <returns>
|
||||
/// Returns a <see cref="CGameTrace" /> structure containing the result of the trace operation, including hit data,
|
||||
/// entity, and surface details.
|
||||
/// </returns>
|
||||
public static CGameTrace TraceHull(Vector start, Vector end,
|
||||
CTraceFilter filter, Ray ray) {
|
||||
var _trace = stackalloc CGameTrace[1];
|
||||
var _filter = stackalloc CTraceFilter[1];
|
||||
|
||||
var _vtable = Address.GetAbsoluteAddress(CTraceFilterVtable, 3, 7);
|
||||
var _gameTraceManager = Address.GetAbsoluteAddress(GameTraceManager, 3, 7);
|
||||
|
||||
*_filter = filter;
|
||||
_filter->Vtable = (void*)_vtable;
|
||||
|
||||
_traceShapeRayFilter(*(nint*)_gameTraceManager, &ray, start.Handle,
|
||||
end.Handle, _filter, _trace);
|
||||
|
||||
return *_trace;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a hull-based ray trace using the provided shape, direction, and filter information.
|
||||
/// This method wraps the native _traceShapeRayFilter call, setting up the necessary filter and trace data on the stack.
|
||||
/// </summary>
|
||||
/// <param name="start">Starting position of the trace</param>
|
||||
/// <param name="end">Starting position of the trace</param>
|
||||
/// <param name="filter"> The filter used to determine which entities or collisions should be excluded during the trace./// </param>
|
||||
/// <param name="ray">A pointer to the shape of the ray (e.g., line, sphere, hull, capsule, mesh) to be traced.</param>
|
||||
/// <param name="result">Return of _traceShape</param>
|
||||
/// <returns>
|
||||
/// Returns a <see cref="CGameTrace" /> structure containing the result of the trace operation, including hit data,
|
||||
/// entity, and surface details.
|
||||
/// </returns>
|
||||
public static CGameTrace TraceHullWithResult(Vector start, Vector end,
|
||||
CTraceFilter filter, Ray ray, out bool result) {
|
||||
var _trace = stackalloc CGameTrace[1];
|
||||
var _filter = stackalloc CTraceFilter[1];
|
||||
|
||||
var _vtable = Address.GetAbsoluteAddress(CTraceFilterVtable, 3, 7);
|
||||
var _gameTraceManager = Address.GetAbsoluteAddress(GameTraceManager, 3, 7);
|
||||
|
||||
*_filter = filter;
|
||||
_filter->Vtable = (void*)_vtable;
|
||||
|
||||
result = _traceShapeRayFilter(*(nint*)_gameTraceManager, &ray, start.Handle,
|
||||
end.Handle, _filter, _trace);
|
||||
|
||||
return *_trace;
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate bool TraceShapeDelegate(IntPtr GameTraceManager,
|
||||
IntPtr vecStart, IntPtr vecEnd, IntPtr skip, ulong mask, ulong content,
|
||||
CGameTrace* pGameTrace);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate bool TraceShapeRayFilterDelegate(IntPtr GameTraceManager,
|
||||
Ray* trace, IntPtr vecStart, IntPtr vecEnd, CTraceFilter* traceFilter,
|
||||
CGameTrace* pGameTrace);
|
||||
}
|
||||
561
TTT/CS2/RayTrace/Class/TraceRayExtensions.cs
Normal file
561
TTT/CS2/RayTrace/Class/TraceRayExtensions.cs
Normal file
@@ -0,0 +1,561 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using TTT.CS2.RayTrace.Enum;
|
||||
using TTT.CS2.RayTrace.Struct;
|
||||
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
|
||||
|
||||
namespace TTT.CS2.RayTrace.Class;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="TraceRay" /> class
|
||||
/// </summary>
|
||||
public static partial class TraceRay {
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags, skipping a player
|
||||
/// controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
TraceMask mask, Contents content, CCSPlayerController skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
TraceMask mask, ulong content, IntPtr skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle, ulong mask,
|
||||
Contents content, IntPtr skip) {
|
||||
return TraceShape(origin, angle, mask, (ulong)content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
TraceMask mask, Contents content, IntPtr skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
Contents mask, ulong content, IntPtr skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
Contents mask, Contents content, IntPtr skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified content flags
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
Contents content, IntPtr skip) {
|
||||
return TraceShape(origin, angle, (ulong)content, (ulong)content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask flags
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
TraceMask mask, IntPtr skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)mask, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with raw mask value
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle, ulong mask,
|
||||
IntPtr skip) {
|
||||
return TraceShape(origin, angle, mask, mask, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified content flags, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
Contents content, CCSPlayerController skip) {
|
||||
return TraceShape(origin, angle, (ulong)content, (ulong)content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and raw content value, skipping a player
|
||||
/// controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
TraceMask mask, ulong content, CCSPlayerController skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with raw mask and content flags, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle, ulong mask,
|
||||
Contents content, CCSPlayerController skip) {
|
||||
return TraceShape(origin, angle, mask, (ulong)content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and raw content value, skipping a player
|
||||
/// controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
Contents mask, ulong content, CCSPlayerController skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with raw mask and content values, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle, ulong mask,
|
||||
ulong content, CCSPlayerController skip) {
|
||||
return TraceShape(origin, angle, mask, content, GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags, skipping a player
|
||||
/// controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
Contents mask, Contents content, CCSPlayerController skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask flags, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
TraceMask mask, CCSPlayerController skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)mask,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with raw mask value, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle, ulong mask,
|
||||
CCSPlayerController skip) {
|
||||
return TraceShape(origin, angle, mask, mask, GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
TraceMask mask, Contents content, CCSPlayerPawn skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified content flags, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
Contents content, CCSPlayerPawn skip) {
|
||||
return TraceShape(origin, angle, (ulong)content, (ulong)content,
|
||||
skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and raw content value, skipping a player
|
||||
/// pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
TraceMask mask, ulong content, CCSPlayerPawn skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with raw mask and content flags, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle, ulong mask,
|
||||
Contents content, CCSPlayerPawn skip) {
|
||||
return TraceShape(origin, angle, mask, (ulong)content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and raw content value, skipping a player
|
||||
/// pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
Contents mask, ulong content, CCSPlayerPawn skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with raw mask and content values, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle, ulong mask,
|
||||
ulong content, CCSPlayerPawn skip) {
|
||||
return TraceShape(origin, angle, mask, content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
Contents mask, Contents content, CCSPlayerPawn skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask flags, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
TraceMask mask, CCSPlayerPawn skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)mask, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with raw mask value, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle, ulong mask,
|
||||
CCSPlayerPawn skip) {
|
||||
return TraceShape(origin, angle, mask, mask, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and content flags
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, TraceMask mask,
|
||||
ulong content, IntPtr skip) {
|
||||
return TraceShape(start, end, (ulong)mask, content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with raw mask and content flags
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, ulong mask,
|
||||
Contents content, IntPtr skip) {
|
||||
return TraceShape(start, end, mask, (ulong)content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and content flags
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, TraceMask mask,
|
||||
Contents content, IntPtr skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and raw content value
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, Contents mask,
|
||||
ulong content, IntPtr skip) {
|
||||
return TraceShape(start, end, (ulong)mask, content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and content flags
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, Contents mask,
|
||||
Contents content, IntPtr skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified content flags
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end,
|
||||
Contents content, IntPtr skip) {
|
||||
return TraceShape(start, end, (ulong)content, (ulong)content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask flags
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, TraceMask mask,
|
||||
IntPtr skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)mask, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with raw mask value
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, ulong mask,
|
||||
IntPtr skip) {
|
||||
return TraceShape(start, end, mask, mask, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and content flags, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, TraceMask mask,
|
||||
Contents content, CCSPlayerController skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified content flags, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end,
|
||||
Contents content, CCSPlayerController skip) {
|
||||
return TraceShape(start, end, (ulong)content, (ulong)content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and raw content value, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, TraceMask mask,
|
||||
ulong content, CCSPlayerController skip) {
|
||||
return TraceShape(start, end, (ulong)mask, content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with raw mask and content flags, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, ulong mask,
|
||||
Contents content, CCSPlayerController skip) {
|
||||
return TraceShape(start, end, mask, (ulong)content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and raw content value, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, Contents mask,
|
||||
ulong content, CCSPlayerController skip) {
|
||||
return TraceShape(start, end, (ulong)mask, content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with raw mask and content values, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, ulong mask,
|
||||
ulong content, CCSPlayerController skip) {
|
||||
return TraceShape(start, end, mask, content, GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and content flags, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, Contents mask,
|
||||
Contents content, CCSPlayerController skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask flags, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, TraceMask mask,
|
||||
CCSPlayerController skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)mask, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with raw mask value, skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, ulong mask,
|
||||
CCSPlayerController skip) {
|
||||
return TraceShape(start, end, mask, mask, GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and content flags, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, TraceMask mask,
|
||||
Contents content, CCSPlayerPawn skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified content flags, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end,
|
||||
Contents content, CCSPlayerPawn skip) {
|
||||
return TraceShape(start, end, (ulong)content, (ulong)content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and raw content value, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, TraceMask mask,
|
||||
ulong content, CCSPlayerPawn skip) {
|
||||
return TraceShape(start, end, (ulong)mask, content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with raw mask and content flags, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, ulong mask,
|
||||
Contents content, CCSPlayerPawn skip) {
|
||||
return TraceShape(start, end, mask, (ulong)content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and raw content value, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, Contents mask,
|
||||
ulong content, CCSPlayerPawn skip) {
|
||||
return TraceShape(start, end, (ulong)mask, content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with raw mask and content values, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, ulong mask,
|
||||
ulong content, CCSPlayerPawn skip) {
|
||||
return TraceShape(start, end, mask, content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and content flags, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, Contents mask,
|
||||
Contents content, CCSPlayerPawn skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask flags, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, TraceMask mask,
|
||||
CCSPlayerPawn skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)mask, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with raw mask value, skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, ulong mask,
|
||||
CCSPlayerPawn skip) {
|
||||
return TraceShape(start, end, mask, mask, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags (both as TraceMask)
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
TraceMask mask, TraceMask content, IntPtr skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask (as Contents) and content (as TraceMask), skipping a player
|
||||
/// controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, Contents mask,
|
||||
TraceMask content, CCSPlayerController skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags (both as TraceMask),
|
||||
/// skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
TraceMask mask, TraceMask content, CCSPlayerPawn skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask (as Contents) and content (as TraceMask)
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
Contents mask, TraceMask content, IntPtr skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask and content flags (both as TraceMask),
|
||||
/// skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
TraceMask mask, TraceMask content, CCSPlayerController skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask (as Contents) and content (as TraceMask),
|
||||
/// skipping a player controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
Contents mask, TraceMask content, CCSPlayerController skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin in the direction of angle with specified mask (as Contents) and content (as TraceMask),
|
||||
/// skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector origin, QAngle angle,
|
||||
Contents mask, TraceMask content, CCSPlayerPawn skip) {
|
||||
return TraceShape(origin, angle, (ulong)mask, (ulong)content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and content flags (both as TraceMask)
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, TraceMask mask,
|
||||
TraceMask content, IntPtr skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask (as Contents) and content (as TraceMask)
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, Contents mask,
|
||||
TraceMask content, IntPtr skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)content, skip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and content flags (both as TraceMask), skipping a player
|
||||
/// controller
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, TraceMask mask,
|
||||
TraceMask content, CCSPlayerController skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)content,
|
||||
GetSafeSkipHandle(skip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask and content flags (both as TraceMask), skipping a player pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, TraceMask mask,
|
||||
TraceMask content, CCSPlayerPawn skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)content, skip.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a trace from origin to end with specified mask (as Contents) and content (as TraceMask), skipping a player
|
||||
/// pawn
|
||||
/// </summary>
|
||||
public static CGameTrace TraceShape(Vector start, Vector end, Contents mask,
|
||||
TraceMask content, CCSPlayerPawn skip) {
|
||||
return TraceShape(start, end, (ulong)mask, (ulong)content, skip.Handle);
|
||||
}
|
||||
|
||||
private static IntPtr GetSafeSkipHandle(CCSPlayerController player) {
|
||||
return player.PlayerPawn.Value is not { } playerPawn ?
|
||||
IntPtr.Zero :
|
||||
playerPawn.Handle;
|
||||
}
|
||||
}
|
||||
131
TTT/CS2/RayTrace/Enum/Contents.cs
Normal file
131
TTT/CS2/RayTrace/Enum/Contents.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
namespace TTT.CS2.RayTrace.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask flags representing collision layers and content types in CS2.
|
||||
/// Used for trace operations to filter what should be hit.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum Contents : ulong {
|
||||
/// <summary>Empty</summary>
|
||||
Empty = 0,
|
||||
|
||||
/// <summary>Solid</summary>
|
||||
Solid = 1ul << LayerIndex.Solid,
|
||||
|
||||
/// <summary>Hitbox</summary>
|
||||
Hitbox = 1ul << LayerIndex.Hitbox,
|
||||
|
||||
/// <summary>Trigger</summary>
|
||||
Trigger = 1ul << LayerIndex.Trigger,
|
||||
|
||||
/// <summary>Sky</summary>
|
||||
Sky = 1ul << LayerIndex.Sky,
|
||||
|
||||
/// <summary>PlayerClip</summary>
|
||||
PlayerClip = 1ul << StandardLayerIndex.PlayerClip,
|
||||
|
||||
/// <summary>NpcClip</summary>
|
||||
NpcClip = 1ul << StandardLayerIndex.NpcClip,
|
||||
|
||||
/// <summary>BlockLos</summary>
|
||||
BlockLos = 1ul << StandardLayerIndex.BlockLos,
|
||||
|
||||
/// <summary>BlockLight</summary>
|
||||
BlockLight = 1ul << StandardLayerIndex.BlockLight,
|
||||
|
||||
/// <summary>Ladder</summary>
|
||||
Ladder = 1ul << StandardLayerIndex.Ladder,
|
||||
|
||||
/// <summary>Pickup</summary>
|
||||
Pickup = 1ul << StandardLayerIndex.Pickup,
|
||||
|
||||
/// <summary>BlockSound</summary>
|
||||
BlockSound = 1ul << StandardLayerIndex.BlockSound,
|
||||
|
||||
/// <summary>NoDraw</summary>
|
||||
NoDraw = 1ul << StandardLayerIndex.NoDraw,
|
||||
|
||||
/// <summary>Window</summary>
|
||||
Window = 1ul << StandardLayerIndex.Window,
|
||||
|
||||
/// <summary>PassBullets</summary>
|
||||
PassBullets = 1ul << StandardLayerIndex.PassBullets,
|
||||
|
||||
/// <summary>WorldGeometry</summary>
|
||||
WorldGeometry = 1ul << StandardLayerIndex.WorldGeometry,
|
||||
|
||||
/// <summary>Water</summary>
|
||||
Water = 1ul << StandardLayerIndex.Water,
|
||||
|
||||
/// <summary>Slime</summary>
|
||||
Slime = 1ul << StandardLayerIndex.Slime,
|
||||
|
||||
/// <summary>TouchAll</summary>
|
||||
TouchAll = 1ul << StandardLayerIndex.TouchAll,
|
||||
|
||||
/// <summary>Player</summary>
|
||||
Player = 1ul << StandardLayerIndex.Player,
|
||||
|
||||
/// <summary>Npc</summary>
|
||||
Npc = 1ul << StandardLayerIndex.Npc,
|
||||
|
||||
/// <summary>Debris</summary>
|
||||
Debris = 1ul << StandardLayerIndex.Debris,
|
||||
|
||||
/// <summary>PhysicsProp</summary>
|
||||
PhysicsProp = 1ul << StandardLayerIndex.PhysicsProp,
|
||||
|
||||
/// <summary>NavIgnore</summary>
|
||||
NavIgnore = 1ul << StandardLayerIndex.NavIgnore,
|
||||
|
||||
/// <summary>NavLocalIgnore</summary>
|
||||
NavLocalIgnore = 1ul << StandardLayerIndex.NavLocalIgnore,
|
||||
|
||||
/// <summary>PostProcessingVolume</summary>
|
||||
PostProcessingVolume = 1ul << StandardLayerIndex.PostProcessingVolume,
|
||||
|
||||
/// <summary>UnusedLayer3</summary>
|
||||
UnusedLayer3 = 1ul << StandardLayerIndex.UnusedLayer3,
|
||||
|
||||
/// <summary>CarriedObject</summary>
|
||||
CarriedObject = 1ul << StandardLayerIndex.CarriedObject,
|
||||
|
||||
/// <summary>Pushaway</summary>
|
||||
Pushaway = 1ul << StandardLayerIndex.Pushaway,
|
||||
|
||||
/// <summary>ServerEntityOnClient</summary>
|
||||
ServerEntityOnClient = 1ul << StandardLayerIndex.ServerEntityOnClient,
|
||||
|
||||
/// <summary>CarriedWeapon</summary>
|
||||
CarriedWeapon = 1ul << StandardLayerIndex.CarriedWeapon,
|
||||
|
||||
/// <summary>StaticLevel</summary>
|
||||
StaticLevel = 1ul << StandardLayerIndex.StaticLevel,
|
||||
|
||||
/// <summary>CsgoTeam1</summary>
|
||||
CsgoTeam1 = 1ul << CsgoLayerIndex.Team1,
|
||||
|
||||
/// <summary>CsgoTeam2</summary>
|
||||
CsgoTeam2 = 1ul << CsgoLayerIndex.Team2,
|
||||
|
||||
/// <summary>CsgoGrenadeClip</summary>
|
||||
CsgoGrenadeClip = 1ul << CsgoLayerIndex.GrenadeClip,
|
||||
|
||||
/// <summary>CsgoDroneClip</summary>
|
||||
CsgoDroneClip = 1ul << CsgoLayerIndex.DroneClip,
|
||||
|
||||
/// <summary>CsgoMoveable</summary>
|
||||
CsgoMoveable = 1ul << CsgoLayerIndex.Moveable,
|
||||
|
||||
/// <summary>CsgoOpaque</summary>
|
||||
CsgoOpaque = 1ul << CsgoLayerIndex.Opaque,
|
||||
|
||||
/// <summary>CsgoMonster</summary>
|
||||
CsgoMonster = 1ul << CsgoLayerIndex.Monster,
|
||||
|
||||
/// <summary>CsgoUnusedLayer</summary>
|
||||
CsgoUnusedLayer = 1ul << CsgoLayerIndex.UnusedLayer,
|
||||
|
||||
/// <summary>CsgoThrownGrenade</summary>
|
||||
CsgoThrownGrenade = 1ul << CsgoLayerIndex.ThrownGrenade
|
||||
}
|
||||
33
TTT/CS2/RayTrace/Enum/CsgoLayerIndex.cs
Normal file
33
TTT/CS2/RayTrace/Enum/CsgoLayerIndex.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace TTT.CS2.RayTrace.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Specific layer indices used for content masking
|
||||
/// </summary>
|
||||
public enum CsgoLayerIndex {
|
||||
/// <summary>Team 1 layer</summary>
|
||||
Team1 = StandardLayerIndex.FirstModSpecific,
|
||||
|
||||
/// <summary>Team 2 layer</summary>
|
||||
Team2,
|
||||
|
||||
/// <summary>Grenade collision layer</summary>
|
||||
GrenadeClip,
|
||||
|
||||
/// <summary>Drone collision layer</summary>
|
||||
DroneClip,
|
||||
|
||||
/// <summary>Movable physics objects layer</summary>
|
||||
Moveable,
|
||||
|
||||
/// <summary>Opaque surfaces layer</summary>
|
||||
Opaque,
|
||||
|
||||
/// <summary>Monster/NPC layer</summary>
|
||||
Monster,
|
||||
|
||||
/// <summary>Unused/reserved layer</summary>
|
||||
UnusedLayer,
|
||||
|
||||
/// <summary>Thrown grenade entities layer</summary>
|
||||
ThrownGrenade
|
||||
}
|
||||
21
TTT/CS2/RayTrace/Enum/DesignerNameMatchType.cs
Normal file
21
TTT/CS2/RayTrace/Enum/DesignerNameMatchType.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace TTT.CS2.RayTrace.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the type of matching to perform on the designer name.
|
||||
/// </summary>
|
||||
public enum DesignerNameMatchType {
|
||||
/// <summary>
|
||||
/// Matches if the designer name is exactly equal to the specified string.
|
||||
/// </summary>
|
||||
Equals,
|
||||
|
||||
/// <summary>
|
||||
/// Matches if the designer name starts with the specified string.
|
||||
/// </summary>
|
||||
StartsWith,
|
||||
|
||||
/// <summary>
|
||||
/// Matches if the designer name ends with the specified string.
|
||||
/// </summary>
|
||||
EndsWith
|
||||
}
|
||||
27
TTT/CS2/RayTrace/Enum/LayerIndex.cs
Normal file
27
TTT/CS2/RayTrace/Enum/LayerIndex.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace TTT.CS2.RayTrace.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Defines base layer indices used for collision detection and tracing
|
||||
/// </summary>
|
||||
public enum LayerIndex {
|
||||
/// <summary>Solid objects layer</summary>
|
||||
Solid = 0,
|
||||
|
||||
/// <summary>Hitbox collision layer</summary>
|
||||
Hitbox,
|
||||
|
||||
/// <summary>Trigger volume layer</summary>
|
||||
Trigger,
|
||||
|
||||
/// <summary>Skybox layer</summary>
|
||||
Sky,
|
||||
|
||||
/// <summary>First available layer for user-defined content</summary>
|
||||
FirstUser,
|
||||
|
||||
/// <summary>Special value indicating layer not found</summary>
|
||||
NotFound = -1,
|
||||
|
||||
/// <summary>Maximum allowed layer index</summary>
|
||||
MaxAllowed = 64
|
||||
}
|
||||
32
TTT/CS2/RayTrace/Enum/RayType.cs
Normal file
32
TTT/CS2/RayTrace/Enum/RayType.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace TTT.CS2.RayTrace.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the geometric shape of a ray used in tracing and collision detection operations.
|
||||
/// Determines how the underlying ray data should be interpreted during a trace.
|
||||
/// </summary>
|
||||
public enum RayType {
|
||||
/// <summary>
|
||||
/// A straight line with optional thickness (radius). Default shape for basic traces.
|
||||
/// </summary>
|
||||
Line,
|
||||
|
||||
/// <summary>
|
||||
/// A spherical shape used for proximity or point-radius-based traces.
|
||||
/// </summary>
|
||||
Sphere,
|
||||
|
||||
/// <summary>
|
||||
/// An axis-aligned bounding box (AABB) used for volume-based tracing.
|
||||
/// </summary>
|
||||
Hull,
|
||||
|
||||
/// <summary>
|
||||
/// A capsule shape defined by two points and a radius. Suitable for player bounding volumes.
|
||||
/// </summary>
|
||||
Capsule,
|
||||
|
||||
/// <summary>
|
||||
/// A custom mesh composed of multiple vertices for complex trace geometry.
|
||||
/// </summary>
|
||||
Mesh
|
||||
}
|
||||
90
TTT/CS2/RayTrace/Enum/StandardLayerIndex.cs
Normal file
90
TTT/CS2/RayTrace/Enum/StandardLayerIndex.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
namespace TTT.CS2.RayTrace.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Standard layer indices used for content masking
|
||||
/// </summary>
|
||||
public enum StandardLayerIndex {
|
||||
/// <summary>PlayerClip</summary>
|
||||
PlayerClip = LayerIndex.FirstUser,
|
||||
|
||||
/// <summary>NpcClip</summary>
|
||||
NpcClip,
|
||||
|
||||
/// <summary>BlockLos</summary>
|
||||
BlockLos,
|
||||
|
||||
/// <summary>BlockLight</summary>
|
||||
BlockLight,
|
||||
|
||||
/// <summary>Ladder</summary>
|
||||
Ladder,
|
||||
|
||||
/// <summary>Pickup</summary>
|
||||
Pickup,
|
||||
|
||||
/// <summary>BlockSound</summary>
|
||||
BlockSound,
|
||||
|
||||
/// <summary>NoDraw</summary>
|
||||
NoDraw,
|
||||
|
||||
/// <summary>Window</summary>
|
||||
Window,
|
||||
|
||||
/// <summary>PassBullets</summary>
|
||||
PassBullets,
|
||||
|
||||
/// <summary>WorldGeometry</summary>
|
||||
WorldGeometry,
|
||||
|
||||
/// <summary>Water</summary>
|
||||
Water,
|
||||
|
||||
/// <summary>Slime</summary>
|
||||
Slime,
|
||||
|
||||
/// <summary>TouchAll</summary>
|
||||
TouchAll,
|
||||
|
||||
/// <summary>Player</summary>
|
||||
Player,
|
||||
|
||||
/// <summary>Npc</summary>
|
||||
Npc,
|
||||
|
||||
/// <summary>Debris</summary>
|
||||
Debris,
|
||||
|
||||
/// <summary>PhysicsProp</summary>
|
||||
PhysicsProp,
|
||||
|
||||
/// <summary>NavIgnore</summary>
|
||||
NavIgnore,
|
||||
|
||||
/// <summary>NavLocalIgnore</summary>
|
||||
NavLocalIgnore,
|
||||
|
||||
/// <summary>PostProcessingVolume</summary>
|
||||
PostProcessingVolume,
|
||||
|
||||
/// <summary>UnusedLayer3</summary>
|
||||
UnusedLayer3,
|
||||
|
||||
/// <summary>CarriedObject</summary>
|
||||
CarriedObject,
|
||||
|
||||
/// <summary>Pushaway</summary>
|
||||
Pushaway,
|
||||
|
||||
/// <summary>ServerEntityOnClient</summary>
|
||||
ServerEntityOnClient,
|
||||
|
||||
/// <summary>CarriedWeapon</summary>
|
||||
CarriedWeapon,
|
||||
|
||||
/// <summary>StaticLevel</summary>
|
||||
StaticLevel,
|
||||
|
||||
/// <summary>FirstModSpecific</summary>
|
||||
FirstModSpecific
|
||||
}
|
||||
55
TTT/CS2/RayTrace/Enum/TraceMask.cs
Normal file
55
TTT/CS2/RayTrace/Enum/TraceMask.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace TTT.CS2.RayTrace.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Predefined trace masks for common collision detection scenarios
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum TraceMask : ulong {
|
||||
/// <summary>Matches everything</summary>
|
||||
MaskAll = ~0ul,
|
||||
|
||||
/// <summary>Everything that is normally solid</summary>
|
||||
MaskSolid = Contents.Solid | Contents.Window | Contents.Player | Contents.Npc
|
||||
| Contents.PassBullets,
|
||||
|
||||
/// <summary>Everything that blocks player movement</summary>
|
||||
MaskPlayerSolid = Contents.Solid | Contents.PlayerClip | Contents.Window
|
||||
| Contents.Player | Contents.Npc | Contents.PassBullets,
|
||||
|
||||
/// <summary>Blocks NPC movement</summary>
|
||||
MaskNpcSolid = Contents.Solid | Contents.NpcClip | Contents.Window
|
||||
| Contents.Player | Contents.Npc | Contents.PassBullets,
|
||||
|
||||
/// <summary>Blocks fluid movement</summary>
|
||||
MaskNpcFluid = Contents.Solid | Contents.NpcClip | Contents.Window
|
||||
| Contents.Player | Contents.Npc,
|
||||
|
||||
/// <summary>Water physics contents</summary>
|
||||
MaskWater = Contents.Water | Contents.Slime,
|
||||
|
||||
/// <summary>Contents that bullets see as solid</summary>
|
||||
MaskShot = Contents.Solid | Contents.Player | Contents.Npc | Contents.Window
|
||||
| Contents.Debris | Contents.Hitbox,
|
||||
|
||||
/// <summary>Bullet collision (world+brush only, no monsters)</summary>
|
||||
MaskShotBrushOnly = Contents.Solid | Contents.Window | Contents.Debris,
|
||||
|
||||
/// <summary>Non-raycasted weapons collision (includes grates)</summary>
|
||||
MaskShotHull = Contents.Solid | Contents.Player | Contents.Npc
|
||||
| Contents.Window | Contents.Debris | Contents.PassBullets,
|
||||
|
||||
/// <summary>Portal gun trace collision</summary>
|
||||
MaskShotPortal =
|
||||
Contents.Solid | Contents.Window | Contents.Player | Contents.Npc,
|
||||
|
||||
/// <summary>Solid contents (world+brush only, no monsters)</summary>
|
||||
MaskSolidBrushOnly = Contents.Solid | Contents.Window | Contents.PassBullets,
|
||||
|
||||
/// <summary>Player movement collision (world+brush only, no monsters)</summary>
|
||||
MaskPlayerSolidBrushOnly = Contents.Solid | Contents.Window
|
||||
| Contents.PlayerClip | Contents.PassBullets,
|
||||
|
||||
/// <summary>NPC movement collision (world+brush only, no monsters)</summary>
|
||||
MaskNpcSolidBrushOnly = Contents.Solid | Contents.Window | Contents.NpcClip
|
||||
| Contents.PassBullets
|
||||
}
|
||||
70
TTT/CS2/RayTrace/Struct/CGameTrace.cs
Normal file
70
TTT/CS2/RayTrace/Struct/CGameTrace.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TTT.CS2.RayTrace.Struct;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the results of a game trace operation, containing information about what was hit.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0xB8)]
|
||||
public unsafe struct CGameTrace {
|
||||
/// <summary>
|
||||
/// The surface that was hit by the trace.
|
||||
/// </summary>
|
||||
[FieldOffset(0x00)]
|
||||
public IntPtr Surface;
|
||||
|
||||
/// <summary>
|
||||
/// The entity that was hit by the trace.
|
||||
/// </summary>
|
||||
[FieldOffset(0x08)]
|
||||
public IntPtr HitEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the hitbox data if a hitbox was hit.
|
||||
/// </summary>
|
||||
[FieldOffset(0x10)]
|
||||
public CTraceHitbox* HitboxData;
|
||||
|
||||
/// <summary>
|
||||
/// The contents at the point of impact.
|
||||
/// </summary>
|
||||
[FieldOffset(0x50)]
|
||||
public uint Contents;
|
||||
|
||||
/// <summary>
|
||||
/// The starting position of the trace.
|
||||
/// </summary>
|
||||
[FieldOffset(0x78)]
|
||||
public Vector3 StartPos;
|
||||
|
||||
/// <summary>
|
||||
/// The end position of the trace.
|
||||
/// </summary>
|
||||
[FieldOffset(0x84)]
|
||||
public Vector3 EndPos;
|
||||
|
||||
/// <summary>
|
||||
/// The surface normal at the point of impact.
|
||||
/// </summary>
|
||||
[FieldOffset(0x90)]
|
||||
public Vector3 Normal;
|
||||
|
||||
/// <summary>
|
||||
/// The exact position where the trace hit.
|
||||
/// </summary>
|
||||
[FieldOffset(0x9C)]
|
||||
public Vector3 Position;
|
||||
|
||||
/// <summary>
|
||||
/// Fraction of the trace completed when the hit occurred (0.0-1.0).
|
||||
/// </summary>
|
||||
[FieldOffset(0xAC)]
|
||||
public float Fraction;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the trace was completely inside a solid (no free space).
|
||||
/// </summary>
|
||||
[FieldOffset(0xB6)]
|
||||
public bool AllSolid;
|
||||
}
|
||||
154
TTT/CS2/RayTrace/Struct/CTraceFilter.cs
Normal file
154
TTT/CS2/RayTrace/Struct/CTraceFilter.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TTT.CS2.RayTrace.Struct;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a filter used during ray tracing operations to determine which entities should be included or excluded
|
||||
/// from the trace.
|
||||
/// This structure closely reflects the trace filter system of the Source 2 engine.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = 72)]
|
||||
public unsafe struct CTraceFilter {
|
||||
/// <summary>
|
||||
/// Delegate pointing to the virtual destructor for the filter object.
|
||||
/// </summary>
|
||||
public delegate void DestructorDelegate(CTraceFilter* filter);
|
||||
|
||||
/// <summary>
|
||||
/// Delegate used to evaluate whether a specific entity should be hit by the ray trace.
|
||||
/// </summary>
|
||||
public delegate bool ShouldHitEntityDelegate(CTraceFilter* filter,
|
||||
IntPtr entity);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CTraceFilter" /> struct, configuring it to ignore specific entities and
|
||||
/// owners.
|
||||
/// </summary>
|
||||
/// <param name="entityIdToIgnore">The entity ID to exclude from the trace.</param>
|
||||
/// <param name="ownerId">Optional owner ID to exclude. Default is 0xFFFFFFFF (none).</param>
|
||||
/// <param name="hierarchyId">Optional hierarchy ID to exclude. Default is 0xFFFF (none).</param>
|
||||
public CTraceFilter(uint entityIdToIgnore, uint ownerId = 0xFFFFFFFF,
|
||||
ushort hierarchyId = 0xFFFF) {
|
||||
Vtable = null;
|
||||
|
||||
m_nInteractsWith = 0;
|
||||
m_nInteractsExclude = 0x20311;
|
||||
m_nInteractsAs = 0x40000;
|
||||
|
||||
m_nOwnerIdsToIgnore[0] = ownerId;
|
||||
m_nOwnerIdsToIgnore[1] = 0xFFFFFFFF;
|
||||
|
||||
m_nEntityIdsToIgnore[0] = entityIdToIgnore;
|
||||
m_nEntityIdsToIgnore[1] = 0xFFFFFFFF;
|
||||
|
||||
m_nHierarchyIds[0] = hierarchyId;
|
||||
m_nHierarchyIds[1] = 0xFFFF;
|
||||
|
||||
m_nObjectSetMask = 7;
|
||||
m_nCollisionGroup = 4;
|
||||
m_nBits = 0b01000001;
|
||||
|
||||
m_bHitEntities = true;
|
||||
m_bHitTriggers = true;
|
||||
m_bTestHitboxes = true;
|
||||
m_bTraceComplexEntities = false;
|
||||
m_bOnlyHitIfHasPhysics = false;
|
||||
m_bIterateEntities = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the virtual function table used internally by the engine.
|
||||
/// </summary>
|
||||
[FieldOffset(0x00)]
|
||||
internal void* Vtable;
|
||||
|
||||
/// <summary>
|
||||
/// Mask of interaction types to include in the trace.
|
||||
/// </summary>
|
||||
[FieldOffset(0x08)]
|
||||
public ulong m_nInteractsWith;
|
||||
|
||||
/// <summary>
|
||||
/// Mask of interaction types to exclude from the trace.
|
||||
/// </summary>
|
||||
[FieldOffset(0x10)]
|
||||
public ulong m_nInteractsExclude;
|
||||
|
||||
/// <summary>
|
||||
/// Mask of interaction types that this object interacts as.
|
||||
/// </summary>
|
||||
[FieldOffset(0x18)]
|
||||
public ulong m_nInteractsAs;
|
||||
|
||||
/// <summary>
|
||||
/// Array of up to two owner IDs to ignore during the trace.
|
||||
/// </summary>
|
||||
[FieldOffset(0x20)]
|
||||
public fixed uint m_nOwnerIdsToIgnore[2];
|
||||
|
||||
/// <summary>
|
||||
/// Array of up to two entity IDs to ignore during the trace.
|
||||
/// </summary>
|
||||
[FieldOffset(0x28)]
|
||||
public fixed uint m_nEntityIdsToIgnore[2];
|
||||
|
||||
/// <summary>
|
||||
/// Array of up to two hierarchy IDs to ignore during the trace.
|
||||
/// </summary>
|
||||
[FieldOffset(0x30)]
|
||||
public fixed ushort m_nHierarchyIds[2];
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask specifying which object sets should be considered.
|
||||
/// </summary>
|
||||
[FieldOffset(0x34)]
|
||||
public byte m_nObjectSetMask;
|
||||
|
||||
/// <summary>
|
||||
/// Collision group the trace belongs to.
|
||||
/// </summary>
|
||||
[FieldOffset(0x35)]
|
||||
public byte m_nCollisionGroup;
|
||||
|
||||
/// <summary>
|
||||
/// Miscellaneous behavior flags encoded as bit flags.
|
||||
/// </summary>
|
||||
[FieldOffset(0x36)]
|
||||
public byte m_nBits;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether entities should be included in the trace.
|
||||
/// </summary>
|
||||
[FieldOffset(0x37)]
|
||||
public bool m_bHitEntities;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether trigger volumes should be hit.
|
||||
/// </summary>
|
||||
[FieldOffset(0x38)]
|
||||
public bool m_bHitTriggers;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether hitboxes should be tested during the trace.
|
||||
/// </summary>
|
||||
[FieldOffset(0x39)]
|
||||
public bool m_bTestHitboxes;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether to trace through complex entities such as physics proxies.
|
||||
/// </summary>
|
||||
[FieldOffset(0x3A)]
|
||||
public bool m_bTraceComplexEntities;
|
||||
|
||||
/// <summary>
|
||||
/// If set, only entities with physics will be considered in the trace.
|
||||
/// </summary>
|
||||
[FieldOffset(0x3B)]
|
||||
public bool m_bOnlyHitIfHasPhysics;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the trace system should iterate over entities to apply filtering.
|
||||
/// </summary>
|
||||
[FieldOffset(0x3C)]
|
||||
public bool m_bIterateEntities;
|
||||
}
|
||||
31
TTT/CS2/RayTrace/Struct/CTraceHitbox.cs
Normal file
31
TTT/CS2/RayTrace/Struct/CTraceHitbox.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TTT.CS2.RayTrace.Struct;
|
||||
|
||||
/// <summary>
|
||||
/// Represents trace hitbox information including hit group and hitbox ID.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x44)]
|
||||
public struct CTraceHitbox {
|
||||
/// <summary>
|
||||
/// The hit group that was hit by the trace.
|
||||
/// Common values:
|
||||
/// 0 = Generic
|
||||
/// 1 = Head
|
||||
/// 2 = Chest
|
||||
/// 3 = Stomach
|
||||
/// 4 = Left Arm
|
||||
/// 5 = Right Arm
|
||||
/// 6 = Left Leg
|
||||
/// 7 = Right Leg
|
||||
/// </summary>
|
||||
[FieldOffset(0x38)]
|
||||
public int HitGroup;
|
||||
|
||||
/// <summary>
|
||||
/// The specific hitbox ID that was hit by the trace.
|
||||
/// Hitbox IDs are model-specific and correspond to hitboxes defined in the model.
|
||||
/// </summary>
|
||||
[FieldOffset(0x40)]
|
||||
public int HitboxId;
|
||||
}
|
||||
26
TTT/CS2/RayTrace/Struct/Capsule.cs
Normal file
26
TTT/CS2/RayTrace/Struct/Capsule.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TTT.CS2.RayTrace.Struct;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a 3D capsule shape defined by two centers and a radius.
|
||||
/// Commonly used in collision detection and physics systems.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Capsule {
|
||||
/// <summary>
|
||||
/// The center point of one end of the capsule.
|
||||
/// </summary>
|
||||
public Vector3 CenterA;
|
||||
|
||||
/// <summary>
|
||||
/// The center point of the opposite end of the capsule.
|
||||
/// </summary>
|
||||
public Vector3 CenterB;
|
||||
|
||||
/// <summary>
|
||||
/// The radius of the capsule.
|
||||
/// </summary>
|
||||
public float Radius;
|
||||
}
|
||||
21
TTT/CS2/RayTrace/Struct/Hull.cs
Normal file
21
TTT/CS2/RayTrace/Struct/Hull.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TTT.CS2.RayTrace.Struct;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an axis-aligned bounding box (AABB) used for spatial queries and collision detection.
|
||||
/// Defined by minimum and maximum 3D coordinates.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Hull {
|
||||
/// <summary>
|
||||
/// The minimum corner of the bounding box (usually the lowest x, y, z values).
|
||||
/// </summary>
|
||||
public Vector3 Mins;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum corner of the bounding box (usually the highest x, y, z values).
|
||||
/// </summary>
|
||||
public Vector3 Maxs;
|
||||
}
|
||||
21
TTT/CS2/RayTrace/Struct/Line.cs
Normal file
21
TTT/CS2/RayTrace/Struct/Line.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TTT.CS2.RayTrace.Struct;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a cylindrical line used in trace or collision tests.
|
||||
/// Defined by a starting offset and a radius around the path.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Line {
|
||||
/// <summary>
|
||||
/// The offset from the origin to the start point of the line.
|
||||
/// </summary>
|
||||
public Vector3 StartOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The radius of the line used to simulate thickness (e.g., swept sphere or capsule).
|
||||
/// </summary>
|
||||
public float Radius;
|
||||
}
|
||||
32
TTT/CS2/RayTrace/Struct/Mesh.cs
Normal file
32
TTT/CS2/RayTrace/Struct/Mesh.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TTT.CS2.RayTrace.Struct;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a 3D mesh consisting of a set of vertices and bounding box information.
|
||||
/// Often used in complex collision or trace geometry.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Mesh {
|
||||
/// <summary>
|
||||
/// The minimum bounding coordinates (AABB) of the mesh.
|
||||
/// </summary>
|
||||
public Vector3 Mins;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum bounding coordinates (AABB) of the mesh.
|
||||
/// </summary>
|
||||
public Vector3 Maxs;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to an array of vertices in memory.
|
||||
/// Each vertex is typically a Vector3 or a custom vertex format.
|
||||
/// </summary>
|
||||
public IntPtr Vertices;
|
||||
|
||||
/// <summary>
|
||||
/// Number of vertices in the mesh.
|
||||
/// </summary>
|
||||
public int NumVertices;
|
||||
}
|
||||
133
TTT/CS2/RayTrace/Struct/Ray.cs
Normal file
133
TTT/CS2/RayTrace/Struct/Ray.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using TTT.CS2.RayTrace.Enum;
|
||||
|
||||
namespace TTT.CS2.RayTrace.Struct;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a polymorphic ray shape used in spatial queries and collision detection.
|
||||
/// This structure can represent a line, sphere, hull (AABB), capsule, or mesh—depending on the constructor used.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct Ray {
|
||||
/// <summary>
|
||||
/// The ray data interpreted as a line.
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public Line Line;
|
||||
|
||||
/// <summary>
|
||||
/// The ray data interpreted as a sphere.
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public Sphere Sphere;
|
||||
|
||||
/// <summary>
|
||||
/// The ray data interpreted as an axis-aligned bounding box (AABB).
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public Hull Hull;
|
||||
|
||||
/// <summary>
|
||||
/// The ray data interpreted as a capsule.
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public Capsule Capsule;
|
||||
|
||||
/// <summary>
|
||||
/// The ray data interpreted as a mesh with custom vertices.
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public Mesh Mesh;
|
||||
|
||||
/// <summary>
|
||||
/// The active ray shape type, used to determine how the union should be interpreted.
|
||||
/// </summary>
|
||||
[FieldOffset(40)]
|
||||
public RayType Type;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a ray as a simple line with no radius.
|
||||
/// </summary>
|
||||
/// <param name="startOffset">The start offset of the line from the origin.</param>
|
||||
public Ray(Vector3 startOffset) {
|
||||
this = default;
|
||||
Line = new Line { StartOffset = startOffset, Radius = 0f };
|
||||
Type = RayType.Line;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a ray as a sphere if the radius is positive, otherwise defaults to a line.
|
||||
/// </summary>
|
||||
/// <param name="center">Center of the sphere or line.</param>
|
||||
/// <param name="radius">Radius of the sphere.</param>
|
||||
public Ray(Vector3 center, float radius) {
|
||||
this = default;
|
||||
if (radius > 0f) {
|
||||
Sphere = new Sphere { Center = center, Radius = radius };
|
||||
Type = RayType.Sphere;
|
||||
} else {
|
||||
Line = new Line { StartOffset = center, Radius = 0f };
|
||||
Type = RayType.Line;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a ray as a hull (AABB) if bounds are not equal, otherwise defaults to a line.
|
||||
/// </summary>
|
||||
/// <param name="mins">Minimum bounding box coordinates.</param>
|
||||
/// <param name="maxs">Maximum bounding box coordinates.</param>
|
||||
public Ray(Vector3 mins, Vector3 maxs) {
|
||||
this = default;
|
||||
if (mins != maxs) {
|
||||
Hull = new Hull { Mins = mins, Maxs = maxs };
|
||||
Type = RayType.Hull;
|
||||
} else {
|
||||
Line = new Line { StartOffset = mins, Radius = 0f };
|
||||
Type = RayType.Line;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a ray as a capsule if the endpoints are distinct and the radius is positive; otherwise falls back to
|
||||
/// simpler shapes.
|
||||
/// </summary>
|
||||
/// <param name="centerA">First endpoint of the capsule.</param>
|
||||
/// <param name="centerB">Second endpoint of the capsule.</param>
|
||||
/// <param name="radius">Radius of the capsule.</param>
|
||||
public Ray(Vector3 centerA, Vector3 centerB, float radius) {
|
||||
this = default;
|
||||
if (centerA != centerB) {
|
||||
if (radius > 0f) {
|
||||
Capsule = new Capsule {
|
||||
CenterA = centerA, CenterB = centerB, Radius = radius
|
||||
};
|
||||
Type = RayType.Capsule;
|
||||
} else {
|
||||
Line = new Line { StartOffset = centerA, Radius = 0f };
|
||||
Type = RayType.Line;
|
||||
}
|
||||
} else { this = new Ray(centerA, radius); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a ray as a mesh with custom vertex data.
|
||||
/// </summary>
|
||||
/// <param name="mins">Minimum bounding box coordinates of the mesh.</param>
|
||||
/// <param name="maxs">Maximum bounding box coordinates of the mesh.</param>
|
||||
/// <param name="vertices">An array of 3D vertices representing the mesh.</param>
|
||||
public Ray(Vector3 mins, Vector3 maxs, Vector3[] vertices) {
|
||||
this = default;
|
||||
unsafe {
|
||||
fixed (Vector3* ptr = vertices) {
|
||||
Mesh = new Mesh {
|
||||
Mins = mins,
|
||||
Maxs = maxs,
|
||||
Vertices = (IntPtr)ptr,
|
||||
NumVertices = vertices.Length
|
||||
};
|
||||
Type = RayType.Mesh;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
TTT/CS2/RayTrace/Struct/Sphere.cs
Normal file
21
TTT/CS2/RayTrace/Struct/Sphere.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TTT.CS2.RayTrace.Struct;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a sphere used for spatial queries or collision detection.
|
||||
/// Defined by its center position and radius.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Sphere {
|
||||
/// <summary>
|
||||
/// The center point of the sphere in world or local space.
|
||||
/// </summary>
|
||||
public Vector3 Center;
|
||||
|
||||
/// <summary>
|
||||
/// The radius of the sphere.
|
||||
/// </summary>
|
||||
public float Radius;
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public static class RayTrace {
|
||||
|
||||
private static readonly nint GameTraceManager = NativeAPI.FindSignature(
|
||||
Addresses.ServerPath,
|
||||
OperatingSystem.IsLinux() ? "4C 8D 05 ? ? ? ? BB" : "48 8B 0D ? ? ? ? 0C");
|
||||
OperatingSystem.IsLinux() ? "4C 8D 0D ? ? ? ? BB" : "48 8B 0D ? ? ? ? 0C");
|
||||
|
||||
public static Vector? TraceShape(Vector _origin, QAngle _viewangles,
|
||||
bool fromPlayer = false) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
namespace TTT.CS2.Utils;
|
||||
|
||||
@@ -11,6 +12,8 @@ public static class RoundUtil {
|
||||
TerminateRoundFunc =
|
||||
new(GameData.GetSignature("CCSGameRules_TerminateRound"));
|
||||
|
||||
private static IEnumerable<CCSTeam>? _teamManager;
|
||||
|
||||
public static int GetTimeElapsed() {
|
||||
if (ServerUtil.GameRules == null) return 0;
|
||||
var freezeTime = ServerUtil.GameRules.FreezeTime;
|
||||
@@ -45,10 +48,35 @@ public static class RoundUtil {
|
||||
return rules == null || rules.WarmupPeriod;
|
||||
}
|
||||
|
||||
public static void EndRound(RoundEndReason reason, float delay = 0) {
|
||||
public static void EndRound(RoundEndReason reason) {
|
||||
var gameRules = ServerUtil.GameRulesProxy;
|
||||
if (gameRules == null || gameRules.GameRules == null) return;
|
||||
// TODO: Figure out what these params do
|
||||
TerminateRoundFunc.Invoke(gameRules.GameRules.Handle, 5f, reason, 0, 0);
|
||||
}
|
||||
|
||||
public static void SetTeamScore(CsTeam team, int score) {
|
||||
_teamManager ??=
|
||||
Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager");
|
||||
|
||||
foreach (var entry in _teamManager) {
|
||||
if (entry.TeamNum != (byte)team) continue;
|
||||
entry.Score = score;
|
||||
Utilities.SetStateChanged(entry, "CTeam", "m_iScore");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddTeamScore(CsTeam team, int score) {
|
||||
SetTeamScore(team, GetTeamScore(team) + score);
|
||||
}
|
||||
|
||||
public static int GetTeamScore(CsTeam team) {
|
||||
_teamManager ??=
|
||||
Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager");
|
||||
|
||||
return (from entry in _teamManager
|
||||
where entry.TeamNum == (byte)team
|
||||
select entry.Score).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
30
TTT/CS2/gamedata/TTT.gamedata.json
Normal file
30
TTT/CS2/gamedata/TTT.gamedata.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"GameTraceManager": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "48 8B 0D ? ? ? ? 0C",
|
||||
"linux": "4C 8D 0D ? ? ? ? BB"
|
||||
}
|
||||
},
|
||||
"TraceFunc": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "4C 8B DC 49 89 5B ? 49 89 6B ? 49 89 73 ? 57 41 56 41 57 48 81 EC",
|
||||
"linux": "48 B8 ? ? ? ? ? ? ? ? 55 66 0F EF C0 48 89 E5 41 57 41 56 49 89 D6"
|
||||
}
|
||||
},
|
||||
"CTraceFilterVtable": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "4C 8D 2D ? ? ? ? 24",
|
||||
"linux": "48 8D 0D ? ? ? ? 66 89 95"
|
||||
}
|
||||
},
|
||||
"TraceShape": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "48 89 5C 24 ? 48 89 4C 24 ? 55 57",
|
||||
"linux": "55 48 89 E5 41 57 49 89 CF 41 56 49 89 F6 41 55 4D 89 C5 41 54 49 89 D4 53 4C 89 CB"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,21 +33,21 @@ public class CS2Messenger(IServiceProvider provider)
|
||||
|
||||
public override void Debug(string msg, params object[] args) {
|
||||
#if DEBUG
|
||||
_ = ((IMessenger)this).BackgroundMsgAll(msg,
|
||||
_ = ((IMessenger)this).BackgroundMsgAll(
|
||||
$"[DEBUG] {string.Format(msg, args)}");
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void DebugAnnounce(string msg, params object[] args) {
|
||||
#if DEBUG
|
||||
_ = ((IMessenger)this).MessageAll(msg,
|
||||
_ = ((IMessenger)this).MessageAll(
|
||||
$"[DEBUG ANNOUNCE] {string.Format(msg, args)}");
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void DebugInform(string msg, params object[] args) {
|
||||
#if DEBUG
|
||||
_ = ((IMessenger)this).ScreenMsgAll(msg,
|
||||
_ = ((IMessenger)this).ScreenMsgAll(
|
||||
$"[DEBUG INFORM] {string.Format(msg, args)}");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
using TTT.Locale;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Role;
|
||||
using TTT.Game;
|
||||
using TTT.Locale;
|
||||
|
||||
namespace TTT.CS2.lang;
|
||||
|
||||
public static class CS2Msgs {
|
||||
public static IMsg ROLE_SPECTATOR
|
||||
=> MsgFactory.Create(nameof(ROLE_SPECTATOR));
|
||||
|
||||
public static IMsg TASER_SCANNED(IPlayer scannedPlayer, IRole role) {
|
||||
var rolePrefix = GameMsgs.GetRolePrefix(role);
|
||||
return MsgFactory.Create(nameof(TASER_SCANNED),
|
||||
rolePrefix + scannedPlayer.Name, role.Name);
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
ROLE_SPECTATOR: "Spectator"
|
||||
ROLE_SPECTATOR: "Spectator"
|
||||
TASER_SCANNED: "%PREFIX%You scanned {0}{grey}, they are %an% {1}{grey}!"
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.332"/>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340"/>
|
||||
<PackageReference Include="System.Reactive" Version="6.0.1"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,19 +1,38 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Role;
|
||||
using TTT.Game.Events.Player;
|
||||
|
||||
namespace TTT.Game.Actions;
|
||||
|
||||
public class DamagedAction(IPlayer victim, IPlayer? attacker, string? weapon,
|
||||
int damage) : IAction {
|
||||
public DamagedAction(PlayerDamagedEvent ev) : this(ev.Player, ev.Attacker,
|
||||
ev.Weapon, ev.DmgDealt) { }
|
||||
public class DamagedAction(IRoleAssigner roles, IPlayer victim,
|
||||
IPlayer attacker, string? weapon, int damage) : IAction {
|
||||
public DamagedAction(IRoleAssigner roles, PlayerDamagedEvent ev) : this(roles,
|
||||
ev.Player,
|
||||
ev.Attacker ?? throw new ArgumentNullException(nameof(ev.Attacker),
|
||||
"Attacker cannot be null"), ev.Weapon, ev.DmgDealt) { }
|
||||
|
||||
|
||||
public string? Weapon { get; } = weapon;
|
||||
public int Damage { get; } = damage;
|
||||
public IPlayer Player { get; } = attacker;
|
||||
public IPlayer? Other { get; } = victim;
|
||||
public IRole? PlayerRole { get; } = roles.GetRoles(attacker).FirstOrDefault();
|
||||
public IRole? OtherRole { get; } = roles.GetRoles(victim).FirstOrDefault();
|
||||
public string Id => "basegame.action.attack";
|
||||
public string Verb => "damaged";
|
||||
public string Details => $"for {Damage} damage with {Weapon}";
|
||||
|
||||
#region ConstructorAliases
|
||||
|
||||
public DamagedAction(IServiceProvider provider, IPlayer victim,
|
||||
IPlayer attacker, string? weapon, int damage) : this(
|
||||
provider.GetRequiredService<IRoleAssigner>(), victim, attacker, weapon,
|
||||
damage) { }
|
||||
|
||||
public DamagedAction(IServiceProvider provider, PlayerDamagedEvent ev) : this(
|
||||
provider.GetRequiredService<IRoleAssigner>(), ev) { }
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,18 +1,50 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Role;
|
||||
using TTT.Game.Events.Player;
|
||||
|
||||
namespace TTT.Game.Actions;
|
||||
|
||||
public class DeathAction(IPlayer victim, IPlayer? killer) : IAction {
|
||||
public DeathAction(PlayerDeathEvent ev) : this(ev.Player, ev.Killer) {
|
||||
public class DeathAction(IRoleAssigner roles, IPlayer victim, IPlayer? killer)
|
||||
: IAction {
|
||||
public DeathAction(IRoleAssigner roles, PlayerDeathEvent ev) : this(roles,
|
||||
ev.Player, ev.Killer) {
|
||||
Details = $"using {ev.Weapon}";
|
||||
}
|
||||
|
||||
public IPlayer Player { get; } = victim;
|
||||
public IPlayer? Other { get; } = killer;
|
||||
public IRole? PlayerRole { get; } = roles.GetRoles(victim).FirstOrDefault();
|
||||
|
||||
public IRole? OtherRole { get; } = killer is not null ?
|
||||
roles.GetRoles(killer).FirstOrDefault() :
|
||||
null;
|
||||
|
||||
public string Id { get; } = "basegame.action.death";
|
||||
public string Verb { get; } = killer is null ? "died" : "was killed by";
|
||||
public string Verb { get; } = killer is null ? "died" : "killed";
|
||||
|
||||
public string Details { get; } = string.Empty;
|
||||
|
||||
public string Format() {
|
||||
var pRole = PlayerRole != null ?
|
||||
$" [{PlayerRole.Name.First(char.IsAsciiLetter)}]" :
|
||||
"";
|
||||
var oRole = OtherRole != null ?
|
||||
$" [{OtherRole.Name.First(char.IsAsciiLetter)}]" :
|
||||
"";
|
||||
return Other is not null ?
|
||||
$"{Other}{oRole} {Verb} {Player}{pRole} {Details}" :
|
||||
$"{Player}{pRole} {Verb} {Details}";
|
||||
}
|
||||
|
||||
#region ConstructorAliases
|
||||
|
||||
public DeathAction(IServiceProvider provider, IPlayer victim, IPlayer? killer)
|
||||
: this(provider.GetRequiredService<IRoleAssigner>(), victim, killer) { }
|
||||
|
||||
public DeathAction(IServiceProvider provider, PlayerDeathEvent ev) : this(
|
||||
provider.GetRequiredService<IRoleAssigner>(), ev) { }
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,12 +1,29 @@
|
||||
using TTT.API.Game;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Role;
|
||||
using TTT.Game.Events.Body;
|
||||
|
||||
namespace TTT.Game.Actions;
|
||||
|
||||
public class IdentifyBodyAction(BodyIdentifyEvent ev) : IAction {
|
||||
public class IdentifyBodyAction(IRoleAssigner roles, BodyIdentifyEvent ev)
|
||||
: IAction {
|
||||
#region ConstructorAliases
|
||||
|
||||
public IdentifyBodyAction(IServiceProvider provider, BodyIdentifyEvent ev) :
|
||||
this(provider.GetRequiredService<IRoleAssigner>(), ev) { }
|
||||
|
||||
#endregion
|
||||
|
||||
public IPlayer Player { get; } = ev.Identifier;
|
||||
public IPlayer? Other { get; } = ev.Body.OfPlayer;
|
||||
|
||||
public IRole? PlayerRole { get; } =
|
||||
roles.GetRoles(ev.Identifier).FirstOrDefault();
|
||||
|
||||
public IRole? OtherRole { get; } =
|
||||
roles.GetRoles(ev.Body.OfPlayer).FirstOrDefault();
|
||||
|
||||
public string Id { get; } = "basegame.action.identify_body";
|
||||
public string Verb { get; } = "identified the body of";
|
||||
public string Details { get; } = "";
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace TTT.Game.Actions;
|
||||
public class RoleAssignedAction(IPlayer player, IRole role) : IAction {
|
||||
public IPlayer Player { get; } = player;
|
||||
public IPlayer? Other => null;
|
||||
public IRole? PlayerRole { get; } = role;
|
||||
public IRole? OtherRole { get; } = null;
|
||||
public string Id => "basegame.action.roleassigned";
|
||||
public string Verb => "was assigned";
|
||||
public string Details { get; } = role.Name;
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace TTT.Game.Commands;
|
||||
|
||||
public class CommandManager(IServiceProvider provider)
|
||||
: ICommandManager, ITerrorModule {
|
||||
protected readonly Dictionary<string, ICommand> Commands = new();
|
||||
protected readonly Dictionary<string, ICommand> cmdMap = new();
|
||||
|
||||
protected readonly IMsgLocalizer Localizer =
|
||||
provider.GetRequiredService<IMsgLocalizer>();
|
||||
@@ -19,11 +19,11 @@ public class CommandManager(IServiceProvider provider)
|
||||
protected readonly IServiceProvider Provider = provider;
|
||||
|
||||
public virtual bool RegisterCommand(ICommand command) {
|
||||
return command.Aliases.All(alias => Commands.TryAdd(alias, command));
|
||||
return command.Aliases.All(alias => cmdMap.TryAdd(alias, command));
|
||||
}
|
||||
|
||||
public bool UnregisterCommand(ICommand command) {
|
||||
return command.Aliases.All(alias => Commands.Remove(alias));
|
||||
return command.Aliases.All(alias => cmdMap.Remove(alias));
|
||||
}
|
||||
|
||||
public bool CanExecute(IOnlinePlayer? executor, ICommand command) {
|
||||
@@ -37,7 +37,7 @@ public class CommandManager(IServiceProvider provider)
|
||||
var executor = info.CallingPlayer;
|
||||
if (info.ArgCount == 0) return CommandResult.ERROR;
|
||||
|
||||
if (!Commands.TryGetValue(info.Args[0], out var command)) {
|
||||
if (!cmdMap.TryGetValue(info.Args[0], out var command)) {
|
||||
info.ReplySync(Localizer[GameMsgs.GENERIC_UNKNOWN(info.Args[0])]);
|
||||
return CommandResult.UNKNOWN_COMMAND;
|
||||
}
|
||||
@@ -64,11 +64,14 @@ public class CommandManager(IServiceProvider provider)
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual string Name => "base.commands";
|
||||
public virtual string Version => GitVersionInformation.FullSemVer;
|
||||
public ISet<ICommand> Commands => cmdMap.Values.ToHashSet();
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public virtual void Start() { RegisterCommand(new LogsCommand(Provider)); }
|
||||
public virtual void Start() {
|
||||
var commands = Provider.GetServices<ICommand>();
|
||||
foreach (var command in commands) RegisterCommand(command);
|
||||
}
|
||||
|
||||
private void printNoPermission(IOnlinePlayer? executor, ICommand command,
|
||||
ICommandInfo info) {
|
||||
|
||||
@@ -12,23 +12,20 @@ public class LogsCommand(IServiceProvider provider) : ICommand {
|
||||
public void Dispose() { }
|
||||
|
||||
public string Name => "logs";
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
public void Start() { }
|
||||
|
||||
// TODO: Restrict and verbalize usage
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
if (!games.IsGameActive()) {
|
||||
if (games.ActiveGame is not {
|
||||
State: State.IN_PROGRESS or State.FINISHED
|
||||
}) {
|
||||
info.ReplySync("No active game to show logs for.");
|
||||
return Task.FromResult(CommandResult.ERROR);
|
||||
}
|
||||
|
||||
var game = games.ActiveGame;
|
||||
if (game == null) {
|
||||
info.ReplySync("No active game to show logs for.");
|
||||
return Task.FromResult(CommandResult.ERROR);
|
||||
}
|
||||
|
||||
game.Logger.PrintLogs(executor);
|
||||
games.ActiveGame.Logger.PrintLogs(executor);
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user