mirror of
https://github.com/MSWS/TTT.git
synced 2025-12-07 23:06:33 -08:00
Compare commits
199 Commits
0.5.0
...
0.13.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f904df564 | ||
|
|
42a2fc2caf | ||
|
|
306be07e8f | ||
|
|
233396fbd1 | ||
|
|
a1194008ad | ||
|
|
5bc52acf3c | ||
|
|
9a6af7acab | ||
|
|
8cc241bcca | ||
|
|
cab156184c | ||
|
|
9ee69a0b28 | ||
|
|
e529229200 | ||
|
|
e7dc5c02fe | ||
|
|
522e42a5ff | ||
|
|
871500fbdc | ||
|
|
f023d36aa9 | ||
|
|
a185b217e0 | ||
|
|
dbfd360c6c | ||
|
|
4a64741a8e | ||
|
|
7372ffda45 | ||
|
|
9ea9c78208 | ||
|
|
85601f1fc0 | ||
|
|
ddf52f057d | ||
|
|
c2ecba1847 | ||
|
|
354ccf2fbe | ||
|
|
9d1a7f5618 | ||
|
|
ea62b312be | ||
|
|
6778531312 | ||
|
|
8ab4328d9b | ||
|
|
c5a91f334d | ||
|
|
b2f4474e8f | ||
|
|
8aee59a87e | ||
|
|
e27bddf8e2 | ||
|
|
4514e9baa0 | ||
|
|
67755c36c6 | ||
|
|
eaf1ab627e | ||
|
|
57bef00055 | ||
|
|
7dd6d4dd38 | ||
|
|
f1cce6c230 | ||
|
|
922f121009 | ||
|
|
2a0924138f | ||
|
|
a4dc781ee4 | ||
|
|
935b430769 | ||
|
|
9dd4414733 | ||
|
|
dce4edd6a4 | ||
|
|
324711acb9 | ||
|
|
85ae2c4210 | ||
|
|
2e6743c25d | ||
|
|
5ff27b37e5 | ||
|
|
1a4e5e3e77 | ||
|
|
721504f612 | ||
|
|
86c24533b5 | ||
|
|
eba49139c2 | ||
|
|
d33550a5a4 | ||
|
|
453ce77711 | ||
|
|
b427dc370e | ||
|
|
ede9badbd9 | ||
|
|
5736588484 | ||
|
|
8a894c65e8 | ||
|
|
0634af8ad8 | ||
|
|
9f6c3f7be4 | ||
|
|
9eb313e9f1 | ||
|
|
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 | ||
|
|
223daa1085 | ||
|
|
fbda4fe38b | ||
|
|
a663e556f9 | ||
|
|
49d45a12b9 | ||
|
|
96b7b64634 | ||
|
|
fb00a5be2d | ||
|
|
1374eeae76 |
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
|
||||
169
.github/workflows/release.yml
vendored
169
.github/workflows/release.yml
vendored
@@ -12,10 +12,18 @@ permissions:
|
||||
jobs:
|
||||
auto-release:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# Tweak these if you want a different model or style
|
||||
OPENAI_MODEL: gpt-4o-mini
|
||||
OPENAI_TEMPERATURE: "0.2"
|
||||
# Safety: cap how many characters we feed to the model
|
||||
MAX_CHANGELOG_CHARS: "50000"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
# 1. Calculate version using GitVersion
|
||||
- name: Install GitVersion
|
||||
@@ -34,18 +42,21 @@ 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
|
||||
dotnet publish TTT/Plugin/Plugin.csproj --no-restore -c Release -o build/TTT
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: TTT
|
||||
path: build/TTT
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Zip Artifacts
|
||||
run: |
|
||||
cd build/TTT
|
||||
zip -r TTT-${{ steps.gitversion.outputs.fullSemVer }}.zip *
|
||||
|
||||
# 2. Get latest tag
|
||||
- name: Get latest tag
|
||||
id: latest_tag
|
||||
@@ -58,41 +69,145 @@ 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
|
||||
# 4. Determine previous relevant tag (lineage-aware)
|
||||
- name: Determine previous relevant tag
|
||||
id: prev_tag
|
||||
run: |
|
||||
set -euo pipefail
|
||||
branch="${GITHUB_REF_NAME}"
|
||||
if [[ "$branch" == "main" ]]; then
|
||||
prev=$(git tag --sort=-creatordate | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sed -n 2p)
|
||||
else
|
||||
prev=$(git tag --sort=-creatordate | grep -E '^[0-9]+\.[0-9]+\.[0-9]+-' | sed -n 2p)
|
||||
fi
|
||||
echo "tag=${prev:-0.0.0}" >> $GITHUB_OUTPUT
|
||||
|
||||
# 5. Generate changelog
|
||||
# Use HEAD^ to skip the tag we just created. If no parent, fall back to HEAD.
|
||||
if git rev-parse --verify -q HEAD^ >/dev/null; then
|
||||
base_rev="HEAD^"
|
||||
else
|
||||
base_rev="HEAD"
|
||||
fi
|
||||
|
||||
# Match stable tags on main and prerelease tags on non-main
|
||||
if [[ "$branch" == "main" ]]; then
|
||||
pattern='[0-9]*.[0-9]*.[0-9]*'
|
||||
else
|
||||
pattern='[0-9]*.[0-9]*.[0-9]*-*'
|
||||
fi
|
||||
|
||||
# Nearest tag reachable on this lineage, not just "second most recent by date"
|
||||
prev=$(git describe --tags --abbrev=0 --match "$pattern" --tags "$base_rev" 2>/dev/null || true)
|
||||
|
||||
echo "tag=${prev:-0.0.0}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
|
||||
# 5. Generate changelog using local git (no compare API)
|
||||
- name: Generate changelog
|
||||
run: |
|
||||
gh api repos/${{ github.repository }}/compare/${{ steps.prev_tag.outputs.tag }}...${{ steps.gitversion.outputs.MajorMinorPatch }} \
|
||||
--jq '.commits[].commit.message' > CHANGELOG.md
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
set -euo pipefail
|
||||
|
||||
# 6. Create release
|
||||
prev="${{ steps.prev_tag.outputs.tag }}"
|
||||
curr="${{ steps.gitversion.outputs.fullSemVer }}"
|
||||
|
||||
# Choose what you want in the raw feed: %s = subject only, %B = full message
|
||||
GIT_LOG_FORMAT='%B'
|
||||
|
||||
if [[ "$prev" == "0.0.0" ]]; then
|
||||
# First release: whole history to this tag, first-parent to reflect main’s narrative
|
||||
git log --no-merges --format="${GIT_LOG_FORMAT}" --reverse "$curr" > CHANGELOG.md
|
||||
else
|
||||
# Strict range between the previous reachable tag and the new tag on this lineage
|
||||
git log --no-merges --format="${GIT_LOG_FORMAT}" --reverse "$prev..$curr" > CHANGELOG.md
|
||||
fi
|
||||
|
||||
# Fallback in case nothing was captured
|
||||
if [[ ! -s CHANGELOG.md ]]; then
|
||||
echo "No commits found between $prev and $curr on first-parent. Using full messages without first-parent filter." >&2
|
||||
if [[ "$prev" == "0.0.0" ]]; then
|
||||
git log --no-merges --format="${GIT_LOG_FORMAT}" --reverse "$curr" > CHANGELOG.md
|
||||
else
|
||||
git log --no-merges --format="${GIT_LOG_FORMAT}" --reverse "$prev..$curr" > CHANGELOG.md
|
||||
fi
|
||||
fi
|
||||
|
||||
cat CHANGELOG.md
|
||||
|
||||
# 5b. Rewrite changelog with OpenAI
|
||||
- name: Rewrite changelog with OpenAI
|
||||
id: ai_changelog
|
||||
if: success()
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_MODEL: ${{ env.OPENAI_MODEL }}
|
||||
OPENAI_TEMPERATURE: ${{ env.OPENAI_TEMPERATURE }}
|
||||
MAX_CHANGELOG_CHARS: ${{ env.MAX_CHANGELOG_CHARS }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Ensure we have a changelog to work with
|
||||
if [[ ! -s CHANGELOG.md ]]; then
|
||||
echo "CHANGELOG.md is empty. Skipping AI rewrite."
|
||||
echo "skipped=true" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Trim the input to a safe size for token limits
|
||||
head -c "${MAX_CHANGELOG_CHARS}" CHANGELOG.md > CHANGELOG_RAW.md
|
||||
|
||||
# Build the JSON body. We feed system guidance and the raw changelog
|
||||
# See OpenAI Responses API docs for the schema and output_text helper. :contentReference[oaicite:0]{index=0}
|
||||
jq -Rs --arg sys "You are an expert release-notes writer. Given a list of changes in various formats (e.g: commits, merges, etc.), write Release notes, grouping by features, features, and other pertinent groups where appropriate. Do not include a group if it is not necessary / populated. Remove internal ticket IDs and commit hashes unless essential. Merge duplicates. Use imperative, past tense voice voice. Output valid Markdown only." \
|
||||
--arg temp "${OPENAI_TEMPERATURE}" \
|
||||
--arg model "${OPENAI_MODEL}" \
|
||||
'{model:$model, temperature: ($temp|tonumber), input:[{role:"system", content:$sys},{role:"user", content:.}]}' CHANGELOG_RAW.md > request.json
|
||||
|
||||
# Call the API
|
||||
# Basic retry on transient failures
|
||||
for i in 1 2 3; do
|
||||
HTTP_CODE=$(curl -sS -w "%{http_code}" -o ai_response.json \
|
||||
https://api.openai.com/v1/responses \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||
--data-binary @request.json) && break || true
|
||||
echo "Call attempt $i failed with HTTP $HTTP_CODE"
|
||||
sleep $((i*i))
|
||||
done
|
||||
|
||||
if [[ "${HTTP_CODE:-000}" -lt 200 || "${HTTP_CODE:-000}" -ge 300 ]]; then
|
||||
echo "OpenAI API call failed with HTTP $HTTP_CODE. Keeping raw changelog."
|
||||
echo "skipped=true" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Prefer output_text if present. Fallback to first text item. :contentReference[oaicite:1]{index=1}
|
||||
if jq -e '.output_text' ai_response.json >/dev/null; then
|
||||
jq -r '.output_text' ai_response.json > CHANGELOG.md
|
||||
else
|
||||
jq -r '.output[0].content[] | select(.type=="output_text") | .text' ai_response.json | sed '/^[[:space:]]*$/d' > CHANGELOG.md
|
||||
fi
|
||||
|
||||
# If the rewrite somehow produced an empty file, keep the raw one
|
||||
if [[ ! -s CHANGELOG.md ]]; then
|
||||
echo "AI returned empty content. Restoring raw changelog."
|
||||
mv CHANGELOG_RAW.md CHANGELOG.md
|
||||
echo "skipped=true" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "skipped=false" >> $GITHUB_OUTPUT
|
||||
echo "Rewritten changelog:"
|
||||
cat CHANGELOG.md
|
||||
|
||||
# 6. Create release using the (possibly rewritten) changelog
|
||||
- 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/**
|
||||
files: build/TTT/*.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
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}"
|
||||
32
LICENSES.MD
32
LICENSES.MD
@@ -1,14 +1,18 @@
|
||||
| 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/ |
|
||||
| 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/ |
|
||||
| Microsoft.NET.Test.Sdk | 17.14.1 | Expression | MIT | https://licenses.nuget.org/MIT | © Microsoft Corporation. All rights reserved. | Microsoft | https://github.com/microsoft/vstest |
|
||||
| Microsoft.Reactive.Testing | 6.0.1 | Expression | MIT | https://licenses.nuget.org/MIT | Copyright (c) .NET Foundation and Contributors. | .NET Foundation and Contributors | https://github.com/dotnet/reactive |
|
||||
| Microsoft.Testing.Extensions.CodeCoverage | 17.14.2 | Unknown | | https://aka.ms/deprecateLicenseUrl | © Microsoft Corporation. All rights reserved. | Microsoft | https://github.com/microsoft/codecoverage |
|
||||
| System.Reactive | 6.0.1 | Expression | MIT | https://licenses.nuget.org/MIT | Copyright (c) .NET Foundation and Contributors. | .NET Foundation and Contributors | https://github.com/dotnet/reactive |
|
||||
| Xunit.DependencyInjection | 10.6.0 | Expression | MIT | https://licenses.nuget.org/MIT | Copyright © 2019 | Wei Peng | https://github.com/pengweiqhca/Xunit.DependencyInjection/tree/main/src/Xunit.DependencyInjection |
|
||||
| xunit.runner.visualstudio | 3.1.3 | Expression | Apache-2.0 | https://licenses.nuget.org/Apache-2.0 | Copyright (C) .NET Foundation | jnewkirk,bradwilson | |
|
||||
| xunit.v3 | 3.0.0 | Expression | Apache-2.0 | https://licenses.nuget.org/Apache-2.0 | Copyright (C) .NET Foundation | jnewkirk,bradwilson | |
|
||||
| YamlDotNet | 16.3.0 | Expression | MIT | https://licenses.nuget.org/MIT | Copyright (c) Antoine Aubry and contributors | Antoine Aubry | https://github.com/aaubry/YamlDotNet/wiki |
|
||||
| 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/ |
|
||||
| Dapper | 2.1.66 | Expression | Apache-2.0 | https://licenses.nuget.org/Apache-2.0 | 2019 Stack Exchange, Inc. | Sam Saffron,Marc Gravell,Nick Craver | https://github.com/DapperLib/Dapper |
|
||||
| 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/ |
|
||||
| Microsoft.NET.Test.Sdk | 17.14.1 | Expression | MIT | https://licenses.nuget.org/MIT | © Microsoft Corporation. All rights reserved. | Microsoft | https://github.com/microsoft/vstest |
|
||||
| Microsoft.Reactive.Testing | 6.0.1 | Expression | MIT | https://licenses.nuget.org/MIT | Copyright (c) .NET Foundation and Contributors. | .NET Foundation and Contributors | https://github.com/dotnet/reactive |
|
||||
| Microsoft.Testing.Extensions.CodeCoverage | 17.14.2 | Unknown | | https://aka.ms/deprecateLicenseUrl | © Microsoft Corporation. All rights reserved. | Microsoft | https://github.com/microsoft/codecoverage |
|
||||
| MySqlConnector | 2.4.0 | Expression | MIT | https://licenses.nuget.org/MIT | Copyright 2016–2024 Bradley Grainger | Bradley Grainger | https://mysqlconnector.net/ |
|
||||
| System.Reactive | 6.0.1 | Expression | MIT | https://licenses.nuget.org/MIT | Copyright (c) .NET Foundation and Contributors. | .NET Foundation and Contributors | https://github.com/dotnet/reactive |
|
||||
| System.Text.Json | 8.0.5 | Expression | MIT | https://licenses.nuget.org/MIT | © Microsoft Corporation. All rights reserved. | Microsoft | https://dot.net/ |
|
||||
| Xunit.DependencyInjection | 10.6.0 | Expression | MIT | https://licenses.nuget.org/MIT | Copyright © 2019 | Wei Peng | https://github.com/pengweiqhca/Xunit.DependencyInjection/tree/main/src/Xunit.DependencyInjection |
|
||||
| xunit.runner.visualstudio | 3.1.3 | Expression | Apache-2.0 | https://licenses.nuget.org/Apache-2.0 | Copyright (C) .NET Foundation | jnewkirk,bradwilson | |
|
||||
| xunit.v3 | 3.0.0 | Expression | Apache-2.0 | https://licenses.nuget.org/Apache-2.0 | Copyright (C) .NET Foundation | jnewkirk,bradwilson | |
|
||||
| YamlDotNet | 16.3.0 | Expression | MIT | https://licenses.nuget.org/MIT | Copyright (c) Antoine Aubry and contributors | Antoine Aubry | https://github.com/aaubry/YamlDotNet/wiki |
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
12
TTT.sln
12
TTT.sln
@@ -19,6 +19,10 @@ 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
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShopAPI", "TTT\ShopAPI\ShopAPI.csproj", "{16F720B5-9D45-47BF-8C80-4F91005E36D1}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -60,6 +64,14 @@ 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
|
||||
{16F720B5-9D45-47BF-8C80-4F91005E36D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{16F720B5-9D45-47BF-8C80-4F91005E36D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{16F720B5-9D45-47BF-8C80-4F91005E36D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{16F720B5-9D45-47BF-8C80-4F91005E36D1}.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,7 @@ public interface ICommand : ITerrorModule {
|
||||
string[] Usage => [];
|
||||
string[] RequiredFlags => [];
|
||||
string[] RequiredGroups => [];
|
||||
string[] Aliases => [Name];
|
||||
string[] Aliases => [Id];
|
||||
|
||||
Task<CommandResult> Execute(IOnlinePlayer? executor, ICommandInfo info);
|
||||
}
|
||||
@@ -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,20 @@ 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)))
|
||||
collection.AddTransient<IPluginModule>(provider
|
||||
=> (provider.GetRequiredService<TExtension>() as IPluginModule)!);
|
||||
|
||||
if (typeof(TExtension).IsAssignableTo(typeof(IListener)))
|
||||
collection.AddTransient<IListener>(provider
|
||||
=> (provider.GetRequiredService<TExtension>() as IListener)!);
|
||||
|
||||
if (typeof(TExtension).IsAssignableTo(typeof(ICommand)))
|
||||
collection.AddTransient<ICommand>(provider
|
||||
=> (provider.GetRequiredService<TExtension>() as ICommand)!);
|
||||
|
||||
collection.AddScoped<TExtension>();
|
||||
|
||||
@@ -87,9 +48,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 Id => GetType().Name;
|
||||
string Version => GitVersionInformation.FullSemVer;
|
||||
|
||||
void Start();
|
||||
}
|
||||
@@ -4,7 +4,7 @@ public interface IWeapon {
|
||||
/// <summary>
|
||||
/// The internal ID of the weapon, should match the ID of the weapon in the underlying game.
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
public string WeaponId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of ammo that is in reserve for this weapon.
|
||||
|
||||
16
TTT/API/Player/IIconManager.cs
Normal file
16
TTT/API/Player/IIconManager.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace TTT.API.Player;
|
||||
|
||||
/// <summary>
|
||||
/// Assumes a maximum of 64 players.
|
||||
/// Each bit in the bitmask represents whether a player is visible to the client.
|
||||
/// Bit 0 is unused, bit 1 represents player 1, bit 2 represents player 2, and so on.
|
||||
/// </summary>
|
||||
public interface IIconManager {
|
||||
ulong GetVisiblePlayers(int client);
|
||||
void SetVisiblePlayers(int client, ulong playersBitmask);
|
||||
void RevealToAll(int client);
|
||||
void AddVisiblePlayer(int client, int player);
|
||||
void RemoveVisiblePlayer(int client, int player);
|
||||
|
||||
void ClearAllVisibility();
|
||||
}
|
||||
@@ -6,23 +6,24 @@ public interface IInventoryManager {
|
||||
/// </summary>
|
||||
/// <param name="player">The player to give the weapon to.</param>
|
||||
/// <param name="weapon"></param>
|
||||
void GiveWeapon(IOnlinePlayer player, IWeapon weapon);
|
||||
Task GiveWeapon(IOnlinePlayer player, IWeapon weapon);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a weapon from the player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to remove the weapon from.</param>
|
||||
/// <param name="weaponId">The ID of the weapon to remove.</param>
|
||||
void RemoveWeapon(IOnlinePlayer player, string weaponId);
|
||||
Task RemoveWeapon(IOnlinePlayer player, string weaponId);
|
||||
|
||||
void RemoveWeapon(IOnlinePlayer player, IWeapon weapon) {
|
||||
RemoveWeapon(player, weapon.Id);
|
||||
Task RemoveWeapon(IOnlinePlayer player, IWeapon weapon) {
|
||||
return RemoveWeapon(player, weapon.WeaponId);
|
||||
}
|
||||
|
||||
Task RemoveWeaponInSlot(IOnlinePlayer player, int slot);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all weapons from the player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to remove all weapons from.</param>
|
||||
void RemoveAllWeapons(IOnlinePlayer player);
|
||||
Task RemoveAllWeapons(IOnlinePlayer player);
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
namespace TTT.API.Player;
|
||||
|
||||
public interface IOnlinePlayer : IPlayer {
|
||||
// [Obsolete(
|
||||
// "Roles are now managed via IRoleAssigner. Use IRoleAssigner.GetRoles(IPlayer) instead.")]
|
||||
// ICollection<IRole> Roles { get; }
|
||||
|
||||
public int Health { get; set; }
|
||||
public int MaxHealth { get; set; }
|
||||
public int Armor { get; set; }
|
||||
|
||||
@@ -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="..\ShopAPI\ShopAPI.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="RayTrace\"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,7 +2,6 @@ using CounterStrikeSharp.API.Core;
|
||||
using TTT.API;
|
||||
using TTT.API.Player;
|
||||
using TTT.Game;
|
||||
using TTT.Game.Roles;
|
||||
|
||||
namespace TTT.CS2;
|
||||
|
||||
@@ -12,25 +11,17 @@ public class CS2Body(CRagdollProp ragdoll, IPlayer player) : IBody {
|
||||
public bool IsIdentified { get; set; }
|
||||
public IWeapon? MurderWeapon { get; private set; }
|
||||
|
||||
public IPlayer? Killer { get; private set; }
|
||||
public IPlayer? Killer { get; set; }
|
||||
public string Id { get; } = ragdoll.Index.ToString();
|
||||
public DateTime TimeOfDeath { get; } = DateTime.Now;
|
||||
|
||||
public CS2Body WithWeapon(IWeapon weapon) {
|
||||
MurderWeapon = weapon;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CS2Body WithWeapon(string weapon) {
|
||||
return WithWeapon(new BaseWeapon(weapon));
|
||||
}
|
||||
|
||||
public CS2Body WithKiller(IPlayer? killer) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,21 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ShopAPI.Configs;
|
||||
using ShopAPI.Configs.Traitor;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Extensions;
|
||||
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;
|
||||
@@ -20,36 +27,56 @@ 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>();
|
||||
collection.AddModBehavior<IIconManager, RoleIconsHandler>();
|
||||
|
||||
// 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>();
|
||||
collection.AddModBehavior<IStorage<C4Config>, CS2C4Config>();
|
||||
collection.AddModBehavior<IStorage<M4A1Config>, CS2M4A1Config>();
|
||||
|
||||
// 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<RoundEnd_GameEndHandler>();
|
||||
collection.AddModBehavior<RoundStart_GameStartHandler>();
|
||||
|
||||
// Damage Cancelers
|
||||
collection.AddModBehavior<OutOfRoundCanceler>();
|
||||
collection.AddModBehavior<TaserListenCanceler>();
|
||||
|
||||
// Listeners
|
||||
collection.AddListener<RoundTimerListener>();
|
||||
collection.AddListener<BodyPickupListener>();
|
||||
collection.AddModBehavior<BodyPickupListener>();
|
||||
collection.AddModBehavior<IBodyTracker, BodyTracker>();
|
||||
collection.AddModBehavior<LateSpawnListener>();
|
||||
collection.AddModBehavior<PlayerStatsTracker>();
|
||||
collection.AddModBehavior<RoundTimerListener>();
|
||||
collection.AddModBehavior<ScreenColorApplier>();
|
||||
|
||||
// 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,9 +5,8 @@ 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;
|
||||
using TTT.Game.lang;
|
||||
|
||||
namespace TTT.CS2.Command;
|
||||
|
||||
@@ -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 Id => "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);
|
||||
}
|
||||
}
|
||||
50
TTT/CS2/Command/Test/GiveItemCommand.cs
Normal file
50
TTT/CS2/Command/Test/GiveItemCommand.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ShopAPI;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Player;
|
||||
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class GiveItemCommand(IServiceProvider provider) : ICommand {
|
||||
private readonly IShop shop = provider.GetRequiredService<IShop>();
|
||||
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
public string Id => "giveitem";
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
if (executor == null) return Task.FromResult(CommandResult.PLAYER_ONLY);
|
||||
|
||||
if (info.ArgCount == 1) return Task.FromResult(CommandResult.PRINT_USAGE);
|
||||
|
||||
var query = string.Join(" ", info.Args.Skip(1));
|
||||
var item = searchItem(query);
|
||||
if (item == null) {
|
||||
info.ReplySync($"Item '{query}' not found.");
|
||||
return Task.FromResult(CommandResult.ERROR);
|
||||
}
|
||||
|
||||
shop.GiveItem(executor, item);
|
||||
info.ReplySync($"Gave item '{item.Name}' to {executor.Name}.");
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
|
||||
private IShopItem? searchItem(string query) {
|
||||
var item = shop.Items.FirstOrDefault(it
|
||||
=> it.Name.Equals(query, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (item != null) return item;
|
||||
|
||||
item = shop.Items.FirstOrDefault(it
|
||||
=> it.Name.Equals(query, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (item != null) return item;
|
||||
|
||||
item = shop.Items.FirstOrDefault(it
|
||||
=> it.Name.Contains(query, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
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 Id => "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);
|
||||
}
|
||||
}
|
||||
29
TTT/CS2/Command/Test/IndexCommand.cs
Normal file
29
TTT/CS2/Command/Test/IndexCommand.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Player;
|
||||
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class IndexCommand(IServiceProvider provider) : ICommand {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
public string Id => "index";
|
||||
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
if (executor == null) return Task.FromResult(CommandResult.PLAYER_ONLY);
|
||||
|
||||
Server.NextWorldUpdate(() => {
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
info.ReplySync($"{player.PlayerName} - {player.Slot}");
|
||||
});
|
||||
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
}
|
||||
35
TTT/CS2/Command/Test/ScreenColorCommand.cs
Normal file
35
TTT/CS2/Command/Test/ScreenColorCommand.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Drawing;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.Extensions;
|
||||
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class ScreenColorCommand(IServiceProvider provider) : ICommand {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
public string Id => "screencolor";
|
||||
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
if (executor == null) return Task.FromResult(CommandResult.PLAYER_ONLY);
|
||||
Server.NextWorldUpdate(() => {
|
||||
var player = converter.GetPlayer(executor);
|
||||
float hold = 0.5f, fade = 0.5f;
|
||||
if (info.ArgCount >= 2) float.TryParse(info.Args[1], out hold);
|
||||
if (info.ArgCount >= 3) float.TryParse(info.Args[2], out fade);
|
||||
|
||||
player?.ColorScreen(Color.Red, hold, fade);
|
||||
|
||||
info.ReplySync("Colored your screen red.");
|
||||
});
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
}
|
||||
27
TTT/CS2/Command/Test/SetHealthCommand.cs
Normal file
27
TTT/CS2/Command/Test/SetHealthCommand.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Player;
|
||||
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class SetHealthCommand : ICommand {
|
||||
public string Id => "sethealth";
|
||||
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
if (executor == null) return Task.FromResult(CommandResult.PLAYER_ONLY);
|
||||
|
||||
if (info.ArgCount != 2) return Task.FromResult(CommandResult.PRINT_USAGE);
|
||||
|
||||
if (!int.TryParse(info.Args[1], out var health)) {
|
||||
info.ReplySync("Invalid health value.");
|
||||
return Task.FromResult(CommandResult.ERROR);
|
||||
}
|
||||
|
||||
executor.Health = health;
|
||||
info.ReplySync($"Set health of {executor.Name} to {health}.");
|
||||
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;
|
||||
|
||||
@@ -18,8 +17,7 @@ public class SetRoleCommand(IServiceProvider provider) : ICommand {
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public string Name => "setrole";
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
public string Id => "setrole";
|
||||
public void Start() { }
|
||||
|
||||
public Task<CommandResult>
|
||||
|
||||
27
TTT/CS2/Command/Test/ShowIconsCommand.cs
Normal file
27
TTT/CS2/Command/Test/ShowIconsCommand.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Player;
|
||||
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class ShowIconsCommand(IServiceProvider provider) : ICommand {
|
||||
private readonly IIconManager icons =
|
||||
provider.GetRequiredService<IIconManager>();
|
||||
|
||||
public string Id => "showicons";
|
||||
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
Server.NextWorldUpdate(() => {
|
||||
for (var i = 0; i < Server.MaxPlayers; i++)
|
||||
icons.SetVisiblePlayers(i, ulong.MaxValue);
|
||||
});
|
||||
|
||||
info.ReplySync("Set all icons visible");
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
}
|
||||
26
TTT/CS2/Command/Test/StateCommand.cs
Normal file
26
TTT/CS2/Command/Test/StateCommand.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class StateCommand(IServiceProvider provider) : ICommand {
|
||||
private readonly IGameManager games =
|
||||
provider.GetRequiredService<IGameManager>();
|
||||
|
||||
public string Id => "state";
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
if (games.ActiveGame == null) {
|
||||
info.ReplySync("ActiveGame is null.");
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
|
||||
info.ReplySync($"Current game state: {games.ActiveGame?.State}");
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
}
|
||||
@@ -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 Id => "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,24 @@ 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 string Id => "test";
|
||||
|
||||
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("state", new StateCommand(provider));
|
||||
subCommands.Add("screencolor", new ScreenColorCommand(provider));
|
||||
subCommands.Add("giveitem", new GiveItemCommand(provider));
|
||||
subCommands.Add("index", new IndexCommand(provider));
|
||||
subCommands.Add("showicons", new ShowIconsCommand(provider));
|
||||
subCommands.Add("sethealth", new SetHealthCommand());
|
||||
}
|
||||
|
||||
public Task<CommandResult>
|
||||
@@ -33,7 +33,7 @@ public class TestCommand(IServiceProvider provider) : ICommand, IPluginModule {
|
||||
if (info.ArgCount == 1) {
|
||||
foreach (var c in subCommands.Values)
|
||||
info.ReplySync(
|
||||
$"- {c.Name} {c.Usage.FirstOrDefault()}: {c.Description ?? "No description provided."}");
|
||||
$"- {c.Id} {c.Usage.FirstOrDefault()}: {c.Description ?? "No description provided."}");
|
||||
|
||||
return Task.FromResult(CommandResult.INVALID_ARGS);
|
||||
}
|
||||
@@ -43,6 +43,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);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using TTT.API.Storage;
|
||||
using TTT.CS2.Validators;
|
||||
using TTT.Game;
|
||||
|
||||
namespace TTT.CS2.Game;
|
||||
namespace TTT.CS2.Configs;
|
||||
|
||||
public class CS2GameConfig : IStorage<TTTConfig>, IPluginModule {
|
||||
public static readonly FakeConVar<int> CV_ROUND_COUNTDOWN = new(
|
||||
@@ -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 ShopAPI.Configs;
|
||||
using TTT.API;
|
||||
using TTT.API.Storage;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
61
TTT/CS2/Configs/ShopItems/CS2C4Config.cs
Normal file
61
TTT/CS2/Configs/ShopItems/CS2C4Config.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Cvars;
|
||||
using CounterStrikeSharp.API.Modules.Cvars.Validators;
|
||||
using ShopAPI.Configs.Traitor;
|
||||
using TTT.API;
|
||||
using TTT.API.Storage;
|
||||
using TTT.CS2.Validators;
|
||||
|
||||
namespace TTT.CS2.Configs.ShopItems;
|
||||
|
||||
public class CS2C4Config : IStorage<C4Config>, IPluginModule {
|
||||
public static readonly FakeConVar<int> CV_PRICE = new("css_ttt_shop_c4_price",
|
||||
"Price of the C4 item", 140, ConVarFlags.FCVAR_NONE,
|
||||
new RangeValidator<int>(0, 10000));
|
||||
|
||||
public static readonly FakeConVar<string> CV_WEAPON = new(
|
||||
"css_ttt_shop_c4_weapon", "Weapon entity name used for the C4", "weapon_c4",
|
||||
ConVarFlags.FCVAR_NONE, new ItemValidator(allowMultiple: false));
|
||||
|
||||
public static readonly FakeConVar<int> CV_MAX_PER_ROUND = new(
|
||||
"css_ttt_shop_c4_max_per_round",
|
||||
"Maximum number of C4 that can be purchased per round", 0,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 64));
|
||||
|
||||
public static readonly FakeConVar<int> CV_MAX_AT_ONCE = new(
|
||||
"css_ttt_shop_c4_max_at_once",
|
||||
"Maximum number of C4 that can be active at once", 1,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 64));
|
||||
|
||||
public static readonly FakeConVar<float> CV_POWER = new(
|
||||
"css_ttt_shop_c4_power", "Explosion power (damage multiplier) of the C4",
|
||||
100f, ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 10000f));
|
||||
|
||||
public static readonly FakeConVar<int> CV_FUSE_TIME = new(
|
||||
"css_ttt_shop_c4_fuse_time", "Fuse time of the C4 in seconds", 30,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(1, 300));
|
||||
|
||||
public static readonly FakeConVar<bool> CV_FRIENDLY_FIRE = new(
|
||||
"css_ttt_shop_c4_ff", "Whether the C4 damages teammates");
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public void Start() { }
|
||||
|
||||
public void Start(BasePlugin? plugin) { plugin?.RegisterFakeConVars(this); }
|
||||
|
||||
public Task<C4Config?> Load() {
|
||||
var cfg = new C4Config {
|
||||
Price = CV_PRICE.Value,
|
||||
Weapon = CV_WEAPON.Value,
|
||||
MaxC4PerRound = CV_MAX_PER_ROUND.Value,
|
||||
MaxC4AtOnce = CV_MAX_AT_ONCE.Value,
|
||||
Power = CV_POWER.Value,
|
||||
FuseTime = TimeSpan.FromSeconds(CV_FUSE_TIME.Value),
|
||||
FriendlyFire = CV_FRIENDLY_FIRE.Value
|
||||
};
|
||||
|
||||
return Task.FromResult<C4Config?>(cfg);
|
||||
}
|
||||
}
|
||||
54
TTT/CS2/Configs/ShopItems/CS2M4A1Config.cs
Normal file
54
TTT/CS2/Configs/ShopItems/CS2M4A1Config.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Cvars;
|
||||
using CounterStrikeSharp.API.Modules.Cvars.Validators;
|
||||
using ShopAPI.Configs;
|
||||
using TTT.API;
|
||||
using TTT.API.Storage;
|
||||
using TTT.CS2.Validators;
|
||||
|
||||
namespace TTT.CS2.Configs.ShopItems;
|
||||
|
||||
public class CS2M4A1Config : IStorage<M4A1Config>, IPluginModule {
|
||||
public static readonly FakeConVar<int> CV_PRICE = new(
|
||||
"css_ttt_shop_m4a1_price", "Price of the M4A1 item", 90,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 10000));
|
||||
|
||||
public static readonly FakeConVar<string> CV_CLEAR_SLOTS = new(
|
||||
"css_ttt_shop_m4a1_clear_slots",
|
||||
"Slots to clear when granting M4A1 (comma-separated ints)", "0,1");
|
||||
|
||||
public static readonly FakeConVar<string> CV_WEAPONS = new(
|
||||
"css_ttt_shop_m4a1_weapons",
|
||||
"Weapons granted with this item (comma-separated names)",
|
||||
"weapon_m4a1,weapon_usp_silencer", ConVarFlags.FCVAR_NONE,
|
||||
new ItemValidator(allowMultiple: true));
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public void Start() { }
|
||||
|
||||
public void Start(BasePlugin? plugin) {
|
||||
ArgumentNullException.ThrowIfNull(plugin, nameof(plugin));
|
||||
plugin.RegisterFakeConVars(this);
|
||||
}
|
||||
|
||||
public Task<M4A1Config?> Load() {
|
||||
var slots = CV_CLEAR_SLOTS.Value.Split(',')
|
||||
.Select(s => s.Trim())
|
||||
.Where(s => int.TryParse(s, out _))
|
||||
.Select(int.Parse)
|
||||
.ToArray();
|
||||
|
||||
var weapons = CV_WEAPONS.Value.Split(',')
|
||||
.Select(s => s.Trim())
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.ToArray();
|
||||
|
||||
var cfg = new M4A1Config {
|
||||
Price = CV_PRICE.Value, ClearSlots = slots, Weapons = weapons
|
||||
};
|
||||
|
||||
return Task.FromResult<M4A1Config?>(cfg);
|
||||
}
|
||||
}
|
||||
47
TTT/CS2/Configs/ShopItems/CS2OneShotDeagleConfig.cs
Normal file
47
TTT/CS2/Configs/ShopItems/CS2OneShotDeagleConfig.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Cvars;
|
||||
using CounterStrikeSharp.API.Modules.Cvars.Validators;
|
||||
using ShopAPI.Configs;
|
||||
using TTT.API;
|
||||
using TTT.API.Storage;
|
||||
using TTT.CS2.Validators;
|
||||
|
||||
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", 120,
|
||||
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");
|
||||
|
||||
public static readonly FakeConVar<bool> CV_KILL_SHOOTER_ON_FF = new(
|
||||
"css_ttt_shop_onedeagle_kill_shooter_on_ff",
|
||||
"Whether the shooter is killed if they shoot a teammate", true);
|
||||
|
||||
public static readonly FakeConVar<string> CV_WEAPON = new(
|
||||
"css_ttt_shop_onedeagle_weapon",
|
||||
"Weapon entity name used for the One-Shot Weapon", "weapon_revolver",
|
||||
ConVarFlags.FCVAR_NONE, new ItemValidator(allowEmpty: false));
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public void Start() { }
|
||||
|
||||
public void Start(BasePlugin? 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,
|
||||
KillShooterOnFF = CV_KILL_SHOOTER_ON_FF.Value
|
||||
};
|
||||
|
||||
return Task.FromResult<OneShotDeagleConfig?>(cfg);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
using System.Drawing;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.UserMessages;
|
||||
|
||||
namespace TTT.CS2.Extensions;
|
||||
|
||||
public static class PlayerExtensions {
|
||||
public enum FadeFlags {
|
||||
FADE_IN, FADE_OUT, FADE_STAYOUT
|
||||
}
|
||||
|
||||
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
|
||||
@@ -25,13 +30,57 @@ public static class PlayerExtensions {
|
||||
ev.FireEvent(false);
|
||||
}
|
||||
|
||||
public static void SetHealth(this CCSPlayerController player, int health) {
|
||||
if (player.Pawn.Value == null) return;
|
||||
if (health <= 0) {
|
||||
player.CommitSuicide(false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
player.Pawn.Value.Health = health;
|
||||
Utilities.SetStateChanged(player.Pawn.Value, "CBaseEntity", "m_iHealth");
|
||||
}
|
||||
|
||||
public static int GetHealth(this CCSPlayerController player) {
|
||||
return player.Pawn.Value?.Health ?? 0;
|
||||
}
|
||||
|
||||
public static void AddHealth(this CCSPlayerController player, int health) {
|
||||
if (player.Pawn.Value == null) return;
|
||||
player.SetHealth(player.Pawn.Value.Health + health);
|
||||
}
|
||||
|
||||
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);
|
||||
color = Color.FromArgb(pawn.Render.A == 255 ? 255 : 254, color.R, color.G,
|
||||
color.B);
|
||||
pawn.SetColor(color);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -44,6 +46,40 @@ public static class VectorExtensions {
|
||||
(float)(Math.Sin(yaw) * cosPitch), (float)-Math.Sin(pitch));
|
||||
}
|
||||
|
||||
public static Vector ToRight(this QAngle angle) {
|
||||
var pitch = angle.X * (Math.PI / 180.0);
|
||||
var yaw = angle.Y * (Math.PI / 180.0);
|
||||
var roll = angle.Z * (Math.PI / 180.0);
|
||||
|
||||
var sinPitch = Math.Sin(pitch);
|
||||
var cosPitch = Math.Cos(pitch);
|
||||
var sinYaw = Math.Sin(yaw);
|
||||
var cosYaw = Math.Cos(yaw);
|
||||
var sinRoll = Math.Sin(roll);
|
||||
var cosRoll = Math.Cos(roll);
|
||||
|
||||
return new Vector((float)(sinYaw * sinPitch * cosRoll - cosYaw * sinRoll),
|
||||
(float)(-cosYaw * sinPitch * cosRoll - sinYaw * sinRoll),
|
||||
(float)(cosPitch * -sinRoll));
|
||||
}
|
||||
|
||||
public static Vector ToUp(this QAngle angle) {
|
||||
var pitch = angle.X * (Math.PI / 180.0);
|
||||
var yaw = angle.Y * (Math.PI / 180.0);
|
||||
var roll = angle.Z * (Math.PI / 180.0);
|
||||
|
||||
var sinPitch = Math.Sin(pitch);
|
||||
var cosPitch = Math.Cos(pitch);
|
||||
var sinYaw = Math.Sin(yaw);
|
||||
var cosYaw = Math.Cos(yaw);
|
||||
var sinRoll = Math.Sin(roll);
|
||||
var cosRoll = Math.Cos(roll);
|
||||
|
||||
return new Vector((float)(-cosYaw * sinPitch * cosRoll - sinYaw * sinRoll),
|
||||
(float)(-sinYaw * sinPitch * cosRoll + cosYaw * sinRoll),
|
||||
(float)(cosPitch * cosRoll));
|
||||
}
|
||||
|
||||
public static Vector Lerp(this Vector from, Vector to, float t) {
|
||||
return new Vector(from.X + (to.X - from.X) * t,
|
||||
from.Y + (to.Y - from.Y) * t, from.Z + (to.Z - from.Z) * t);
|
||||
@@ -53,8 +89,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);
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,14 @@ using TTT.API.Role;
|
||||
using TTT.CS2.Roles;
|
||||
using TTT.CS2.Utils;
|
||||
using TTT.Game;
|
||||
using TTT.Game.lang;
|
||||
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 +43,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;
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
using TTT.API.Game;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Messages;
|
||||
using TTT.Game;
|
||||
using TTT.Game.Events.Game;
|
||||
|
||||
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.");
|
||||
protected readonly IMessenger messenger =
|
||||
provider.GetRequiredService<IMessenger>();
|
||||
|
||||
public override IGame CreateGame() {
|
||||
messenger.Debug("Attempting to create a new CS2 game...");
|
||||
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;
|
||||
}
|
||||
|
||||
messenger.Debug("Creating a new CS2 game instance...");
|
||||
ActiveGame = new CS2Game(Provider);
|
||||
|
||||
var ev = new GameInitEvent(ActiveGame);
|
||||
|
||||
@@ -4,12 +4,15 @@ using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
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.CS2.Extensions;
|
||||
using TTT.Game.Events.Body;
|
||||
using TTT.Game.Roles;
|
||||
|
||||
namespace TTT.CS2.GameHandlers;
|
||||
|
||||
@@ -19,18 +22,20 @@ 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() { }
|
||||
|
||||
[UsedImplicitly]
|
||||
[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));
|
||||
player.SetColor(Color.FromArgb(0, 255, 255, 255));
|
||||
|
||||
var ragdollBody = makeGameRagdoll(player);
|
||||
var body = new CS2Body(ragdollBody, converter.GetPlayer(player));
|
||||
@@ -38,45 +43,41 @@ public class BodySpawner(IServiceProvider provider) : IPluginModule {
|
||||
if (ev.Attacker != null && ev.Attacker.IsValid)
|
||||
body.WithKiller(converter.GetPlayer(ev.Attacker));
|
||||
|
||||
body.WithWeapon(ev.Weapon);
|
||||
body.WithWeapon(new BaseWeapon(ev.Weapon));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[GameEventHandler]
|
||||
public HookResult OnStart(EventRoundStart ev, GameEventInfo _) {
|
||||
Server.NextWorldUpdate(() => {
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
player.SetColor(Color.FromArgb(254, 255, 255, 255));
|
||||
player.SetColor(Color.White);
|
||||
});
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -5,7 +5,9 @@ using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
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;
|
||||
@@ -16,8 +18,11 @@ public class CombatHandler(IServiceProvider provider) : IPluginModule {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
public string Name => "CombatListeners";
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
private readonly IGameManager games =
|
||||
provider.GetRequiredService<IGameManager>();
|
||||
|
||||
private readonly IAliveSpoofer spoofer =
|
||||
provider.GetRequiredService<IAliveSpoofer>();
|
||||
|
||||
public void Start() { }
|
||||
|
||||
@@ -31,6 +36,8 @@ public class CombatHandler(IServiceProvider provider) : IPluginModule {
|
||||
[UsedImplicitly]
|
||||
[GameEventHandler(HookMode.Pre)]
|
||||
public HookResult OnPlayerDeath_Pre(EventPlayerDeath ev, GameEventInfo info) {
|
||||
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);
|
||||
@@ -38,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;
|
||||
@@ -46,41 +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 _) {
|
||||
// DamageCanceler already handles this on non-Windows platforms
|
||||
if (!OperatingSystem.IsWindows()) return HookResult.Continue;
|
||||
var player = ev.Userid;
|
||||
if (player == null) return HookResult.Continue;
|
||||
|
||||
@@ -88,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 Id => 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;
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,6 @@ public class PlayerConnectionsHandler(IServiceProvider provider)
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
public string Name => nameof(PlayerConnectionsHandler);
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
|
||||
public void Start() { }
|
||||
|
||||
public void Start(BasePlugin? plugin, bool hotReload) {
|
||||
@@ -29,27 +26,23 @@ public class PlayerConnectionsHandler(IServiceProvider provider)
|
||||
CounterStrikeSharp.API.Core.Listeners.OnClientDisconnect>(
|
||||
disconnectFromServer);
|
||||
|
||||
Server.NextWorldUpdate(() => {
|
||||
foreach (var player in Utilities.GetPlayers()) {
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
var ev = new PlayerJoinEvent(gamePlayer);
|
||||
bus.Dispatch(ev);
|
||||
}
|
||||
});
|
||||
if (!hotReload) return;
|
||||
|
||||
foreach (var ev in Utilities.GetPlayers()
|
||||
.Select(player => converter.GetPlayer(player))
|
||||
.Select(gamePlayer => new PlayerJoinEvent(gamePlayer)))
|
||||
bus.Dispatch(ev);
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
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 +54,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,36 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
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, IIconManager {
|
||||
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>> icons =
|
||||
// new Dictionary<int, IEnumerable<CPointWorldText>>();
|
||||
private readonly IEnumerable<CPointWorldText>?[] icons =
|
||||
new IEnumerable<CPointWorldText>[64];
|
||||
|
||||
private readonly IPlayerConverter<CCSPlayerController> players =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
@@ -24,52 +38,80 @@ public class RoleIconsHandler(IServiceProvider provider)
|
||||
private readonly ITextSpawner? textSpawner =
|
||||
provider.GetService<ITextSpawner>();
|
||||
|
||||
private readonly IDictionary<int, IEnumerable<CPointWorldText>> traitorIcons =
|
||||
new Dictionary<int, IEnumerable<CPointWorldText>>();
|
||||
private readonly HashSet<int> traitorsThisRound = new();
|
||||
|
||||
private readonly ISet<int> traitors = new HashSet<int>();
|
||||
private readonly ulong[] visibilities = new ulong[64];
|
||||
|
||||
public void Dispose() { bus.UnregisterListener(this); }
|
||||
public ulong GetVisiblePlayers(int client) {
|
||||
if (client < 1 || client >= visibilities.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(client));
|
||||
return visibilities[client];
|
||||
}
|
||||
|
||||
public string Name => nameof(RoleIconsHandler);
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
public void SetVisiblePlayers(int client, ulong playersBitmask) {
|
||||
guardRange(client, nameof(client));
|
||||
visibilities[client] = playersBitmask;
|
||||
}
|
||||
|
||||
public void Start() { }
|
||||
public void RevealToAll(int client) {
|
||||
guardRange(client, nameof(client));
|
||||
for (var i = 0; i < visibilities.Length; i++)
|
||||
visibilities[i] |= 1UL << client;
|
||||
}
|
||||
|
||||
public void Start(BasePlugin? plugin) {
|
||||
public void AddVisiblePlayer(int client, int player) {
|
||||
guardRange(client, nameof(client));
|
||||
guardRange(player, nameof(player));
|
||||
visibilities[client] |= 1UL << player;
|
||||
}
|
||||
|
||||
public void RemoveVisiblePlayer(int client, int player) {
|
||||
guardRange(client, nameof(client));
|
||||
guardRange(player, nameof(player));
|
||||
visibilities[client] &= ~(1UL << player);
|
||||
}
|
||||
|
||||
public void ClearAllVisibility() {
|
||||
Array.Clear(visibilities, 0, visibilities.Length);
|
||||
}
|
||||
|
||||
public void Start(BasePlugin? plugin, bool hotReload) {
|
||||
plugin
|
||||
?.RegisterListener<CounterStrikeSharp.API.Core.Listeners.CheckTransmit>(
|
||||
onTransmit);
|
||||
|
||||
bus.RegisterListener(this);
|
||||
}
|
||||
|
||||
[GameEventHandler]
|
||||
public HookResult OnRoundEnd(EventRoundStart _, GameEventInfo _1) {
|
||||
foreach (var text in Utilities
|
||||
.FindAllEntitiesByDesignerName<CPointWorldText>("point_worldtext"))
|
||||
text.AcceptInput("Kill");
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[EventHandler(IgnoreCanceled = true)]
|
||||
public void OnRoundStart(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState != State.IN_PROGRESS) return;
|
||||
traitors.Clear();
|
||||
traitorIcons.Clear();
|
||||
for (var i = 0; i < icons.Length; i++) removeIcon(i);
|
||||
ClearAllVisibility();
|
||||
traitorsThisRound.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
|
||||
removeIcon(player.Slot);
|
||||
|
||||
player.SwitchTeam(ev.Role is DetectiveRole ?
|
||||
CsTeam.CounterTerrorist :
|
||||
@@ -79,21 +121,46 @@ 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);
|
||||
assignIcon(player, ev.Role);
|
||||
|
||||
if (ev.Role is InnocentRole) return;
|
||||
switch (ev.Role) {
|
||||
case DetectiveRole: {
|
||||
for (var i = 0; i < Server.MaxPlayers; i++)
|
||||
AddVisiblePlayer(i, player.Slot);
|
||||
break;
|
||||
}
|
||||
case TraitorRole: {
|
||||
traitorsThisRound.Add(player.Slot);
|
||||
|
||||
foreach (var traitor in traitorsThisRound) {
|
||||
AddVisiblePlayer(traitor, player.Slot);
|
||||
AddVisiblePlayer(player.Slot, traitor);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeIcon(int slot) {
|
||||
var existing = icons[slot];
|
||||
if (existing == null) return;
|
||||
foreach (var ent in existing) {
|
||||
if (!ent.IsValid) continue;
|
||||
ent.AcceptInput("Kill");
|
||||
}
|
||||
|
||||
icons[slot] = null;
|
||||
}
|
||||
|
||||
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;
|
||||
traitors.Add(player.Slot);
|
||||
traitorIcons[player.Slot] = roleIcon;
|
||||
icons[player.Slot] = roleIcon;
|
||||
}
|
||||
|
||||
[EventHandler(Priority = Priority.MONITOR)]
|
||||
@@ -101,26 +168,29 @@ public class RoleIconsHandler(IServiceProvider provider)
|
||||
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);
|
||||
removeIcon(gamePlayer.Slot);
|
||||
}
|
||||
|
||||
// ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable
|
||||
private void onTransmit(CCheckTransmitInfoList infoList) {
|
||||
foreach (var (info, player) in infoList) {
|
||||
if (player == null || !player.IsValid) continue;
|
||||
if (traitors.Contains(player.Slot)) continue;
|
||||
foreach (var icon in traitorIcons.Values.SelectMany(s => s))
|
||||
info.TransmitEntities.Remove(icon);
|
||||
hideIcons(info, player.Slot);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideIcons(CCheckTransmitInfo info, int source) {
|
||||
var visible = visibilities[source];
|
||||
if (visible == ulong.MaxValue) return;
|
||||
for (var i = 0; i < icons.Length; i++) {
|
||||
if ((visible & 1UL << i) != 0) continue;
|
||||
var iconList = icons[i];
|
||||
if (iconList == null) continue;
|
||||
foreach (var icon in iconList) info.TransmitEntities.Remove(icon);
|
||||
}
|
||||
}
|
||||
|
||||
private void guardRange(int index, string name) {
|
||||
if (index < 0 || index >= visibilities.Length)
|
||||
throw new ArgumentOutOfRangeException(name);
|
||||
}
|
||||
}
|
||||
@@ -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,30 +1,39 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Storage;
|
||||
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();
|
||||
|
||||
private readonly IPlayerFinder finder =
|
||||
provider.GetRequiredService<IPlayerFinder>();
|
||||
|
||||
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 count = finder.GetOnline().Count;
|
||||
if (count < config.RoundCfg.MinimumPlayers) 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,29 +37,45 @@ public class TextSpawner : ITextSpawner {
|
||||
return [one, two];
|
||||
}
|
||||
|
||||
public IEnumerable<CPointWorldText> CreateTextScreen(TextSetting setting,
|
||||
CCSPlayerController player) {
|
||||
var screen = spawnScreen(setting, player);
|
||||
return [screen];
|
||||
}
|
||||
|
||||
public CPointWorldText spawnScreen(TextSetting setting,
|
||||
CCSPlayerController player, float xOff = 0, float yOff = 0,
|
||||
float zDist = 50) {
|
||||
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 right = localAngle.ToRight();
|
||||
var up = localAngle.ToUp();
|
||||
var inFront = eyes + forward * zDist;
|
||||
var centered = inFront + right * xOff + up * yOff;
|
||||
|
||||
var ent = CreateText(setting, centered,
|
||||
new QAngle(localAngle.X + 180, localAngle.Y + 90, localAngle.Z + 270));
|
||||
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()!;
|
||||
position.Add(new Vector(0, 0, 72));
|
||||
rotation = new QAngle(rotation.X, rotation.Y + yRot, rotation.Z + 90);
|
||||
|
||||
position.Add(GetRightVector(rotation) * -10);
|
||||
position.Add(rotation.ToRight() * -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;
|
||||
}
|
||||
|
||||
public static Vector GetRightVector(QAngle rotation) {
|
||||
var forward = new Vector {
|
||||
X = (float)Math.Cos(rotation.Y * Math.PI / 180),
|
||||
Y = (float)Math.Sin(rotation.Y * Math.PI / 180),
|
||||
Z = 0
|
||||
};
|
||||
return forward;
|
||||
}
|
||||
}
|
||||
11
TTT/CS2/Items/Camouflage/CamoMsgs.cs
Normal file
11
TTT/CS2/Items/Camouflage/CamoMsgs.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using TTT.Locale;
|
||||
|
||||
namespace TTT.CS2.Items.Camouflage;
|
||||
|
||||
public class CamoMsgs {
|
||||
public static IMsg SHOP_ITEM_CAMO
|
||||
=> MsgFactory.Create(nameof(SHOP_ITEM_CAMO));
|
||||
|
||||
public static IMsg SHOP_ITEM_CAMO_DESC
|
||||
=> MsgFactory.Create(nameof(SHOP_ITEM_CAMO_DESC));
|
||||
}
|
||||
45
TTT/CS2/Items/Camouflage/CamouflageItem.cs
Normal file
45
TTT/CS2/Items/Camouflage/CamouflageItem.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Drawing;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ShopAPI;
|
||||
using ShopAPI.Configs;
|
||||
using TTT.API.Extensions;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Storage;
|
||||
using TTT.CS2.Extensions;
|
||||
|
||||
namespace TTT.CS2.Items.Camouflage;
|
||||
|
||||
public static class CamoServiceCollection {
|
||||
public static void AddCamoServices(this IServiceCollection collection) {
|
||||
collection.AddModBehavior<CamouflageItem>();
|
||||
}
|
||||
}
|
||||
|
||||
public class CamouflageItem(IServiceProvider provider) : BaseItem(provider) {
|
||||
private readonly CamoConfig config =
|
||||
provider.GetService<IStorage<CamoConfig>>()?.Load().GetAwaiter().GetResult()
|
||||
?? new CamoConfig();
|
||||
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
public override string Name => Locale[CamoMsgs.SHOP_ITEM_CAMO];
|
||||
public override string Description => Locale[CamoMsgs.SHOP_ITEM_CAMO_DESC];
|
||||
public override ShopItemConfig Config => config;
|
||||
|
||||
public override void OnPurchase(IOnlinePlayer player) {
|
||||
Server.NextWorldUpdate(() => {
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
var alpha = (int)Math.Round(config.CamoVisibility * 255);
|
||||
gamePlayer?.SetColor(Color.FromArgb(alpha, Color.White));
|
||||
});
|
||||
}
|
||||
|
||||
public override PurchaseResult CanPurchase(IOnlinePlayer player) {
|
||||
return Shop.HasItem<CamouflageItem>(player) ?
|
||||
PurchaseResult.ALREADY_OWNED :
|
||||
PurchaseResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
94
TTT/CS2/Items/DNA/DnaListener.cs
Normal file
94
TTT/CS2/Items/DNA/DnaListener.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ShopAPI;
|
||||
using ShopAPI.Configs;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Storage;
|
||||
using TTT.CS2.API;
|
||||
using TTT.CS2.Events;
|
||||
using TTT.Game.Events.Game;
|
||||
using TTT.Game.Listeners;
|
||||
|
||||
namespace TTT.CS2.Items.DNA;
|
||||
|
||||
public class DnaListener(IServiceProvider provider) : BaseListener(provider) {
|
||||
private static readonly TimeSpan cooldown = TimeSpan.FromSeconds(15);
|
||||
|
||||
private static readonly string[] missingDnaExplanations = {
|
||||
"the killer used gloves... for their bullets",
|
||||
"the killer was very careful", "the killer wiped the weapon clean",
|
||||
"the killer retrieved the bullets", "the bullets disintegrated on impact",
|
||||
"the killer was GOATed", "but no DNA was found",
|
||||
"but legal litigation caused the DNA to be lost",
|
||||
"and confirmed they were dead", "and they will remember that", "good job"
|
||||
};
|
||||
|
||||
private readonly IBodyTracker bodies =
|
||||
provider.GetRequiredService<IBodyTracker>();
|
||||
|
||||
private readonly DnaScannerConfig config = provider
|
||||
.GetService<IStorage<DnaScannerConfig>>()
|
||||
?.Load()
|
||||
.GetAwaiter()
|
||||
.GetResult() ?? new DnaScannerConfig();
|
||||
|
||||
private readonly Dictionary<string, DateTime> lastMessages = new();
|
||||
private readonly IShop shop = provider.GetRequiredService<IShop>();
|
||||
|
||||
// Low priority to allow body identification to happen first
|
||||
[UsedImplicitly]
|
||||
[EventHandler(Priority = Priority.LOW)]
|
||||
public void OnPropPickup(PropPickupEvent ev) {
|
||||
if (ev.Player is not IOnlinePlayer player) return;
|
||||
if (!shop.HasItem<DnaScanner>(player)) return;
|
||||
|
||||
if (!bodies.TryLookup(ev.Prop.Index.ToString(), out var body)) return;
|
||||
if (body == null) return;
|
||||
|
||||
var victimRole = Roles.GetRoles(body.OfPlayer).FirstOrDefault();
|
||||
if (victimRole == null) return;
|
||||
|
||||
if (lastMessages.TryGetValue(player.Id + "." + body.Id,
|
||||
out var lastMessageTime))
|
||||
if (DateTime.Now - lastMessageTime < cooldown)
|
||||
return;
|
||||
|
||||
lastMessages[player.Id + "." + body.Id] = DateTime.Now;
|
||||
|
||||
if (DateTime.Now - body.TimeOfDeath > config.DecayTime) {
|
||||
Messenger.Message(player,
|
||||
Locale[DnaMsgs.SHOP_ITEM_DNA_EXPIRED(victimRole, body.OfPlayer)]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.Killer == null) {
|
||||
var explanation =
|
||||
missingDnaExplanations[
|
||||
Random.Shared.Next(missingDnaExplanations.Length)];
|
||||
Messenger.Message(player,
|
||||
Locale[
|
||||
DnaMsgs.SHOP_ITEM_DNA_SCANNED_OTHER(victimRole, body.OfPlayer,
|
||||
explanation)]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.Killer == body.OfPlayer) {
|
||||
Messenger.Message(player,
|
||||
Locale[
|
||||
DnaMsgs.SHOP_ITEM_DNA_SCANNED_SUICIDE(victimRole, body.OfPlayer)]);
|
||||
return;
|
||||
}
|
||||
|
||||
Messenger.Message(player,
|
||||
Locale[
|
||||
DnaMsgs.SHOP_ITEM_DNA_SCANNED(victimRole, body.OfPlayer, body.Killer)]);
|
||||
}
|
||||
|
||||
[EventHandler]
|
||||
public void OnRoundEnd(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState != State.FINISHED) return;
|
||||
lastMessages.Clear();
|
||||
}
|
||||
}
|
||||
36
TTT/CS2/Items/DNA/DnaMsgs.cs
Normal file
36
TTT/CS2/Items/DNA/DnaMsgs.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Role;
|
||||
using TTT.Game.lang;
|
||||
using TTT.Locale;
|
||||
|
||||
namespace TTT.CS2.Items.DNA;
|
||||
|
||||
public class DnaMsgs {
|
||||
public static IMsg SHOP_ITEM_DNA => MsgFactory.Create(nameof(SHOP_ITEM_DNA));
|
||||
|
||||
public static IMsg SHOP_ITEM_DNA_DESC
|
||||
=> MsgFactory.Create(nameof(SHOP_ITEM_DNA_DESC));
|
||||
|
||||
public static IMsg SHOP_ITEM_DNA_SCANNED(IRole victimRole, IPlayer player,
|
||||
IPlayer killer) {
|
||||
return MsgFactory.Create(nameof(SHOP_ITEM_DNA_SCANNED),
|
||||
GameMsgs.GetRolePrefix(victimRole), player.Name, killer.Name);
|
||||
}
|
||||
|
||||
public static IMsg SHOP_ITEM_DNA_SCANNED_OTHER(IRole victimRole,
|
||||
IPlayer player, string explanation) {
|
||||
return MsgFactory.Create(nameof(SHOP_ITEM_DNA_SCANNED_OTHER),
|
||||
GameMsgs.GetRolePrefix(victimRole), player.Name, explanation);
|
||||
}
|
||||
|
||||
public static IMsg
|
||||
SHOP_ITEM_DNA_SCANNED_SUICIDE(IRole victimRole, IPlayer player) {
|
||||
return SHOP_ITEM_DNA_SCANNED_OTHER(victimRole, player,
|
||||
"they killed themselves");
|
||||
}
|
||||
|
||||
public static IMsg SHOP_ITEM_DNA_EXPIRED(IRole victimRole, IPlayer player) {
|
||||
return MsgFactory.Create(nameof(SHOP_ITEM_DNA_EXPIRED),
|
||||
GameMsgs.GetRolePrefix(victimRole), player.Name);
|
||||
}
|
||||
}
|
||||
36
TTT/CS2/Items/DNA/DnaScanner.cs
Normal file
36
TTT/CS2/Items/DNA/DnaScanner.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ShopAPI;
|
||||
using ShopAPI.Configs;
|
||||
using TTT.API.Extensions;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Storage;
|
||||
using TTT.Game.Roles;
|
||||
|
||||
namespace TTT.CS2.Items.DNA;
|
||||
|
||||
public static class DnaScannerServiceCollection {
|
||||
public static void AddDnaScannerServices(this IServiceCollection collection) {
|
||||
collection.AddModBehavior<DnaScanner>();
|
||||
collection.AddModBehavior<DnaListener>();
|
||||
}
|
||||
}
|
||||
|
||||
public class DnaScanner(IServiceProvider provider)
|
||||
: RoleRestrictedItem<DetectiveRole>(provider) {
|
||||
private readonly DnaScannerConfig config = provider
|
||||
.GetService<IStorage<DnaScannerConfig>>()
|
||||
?.Load()
|
||||
.GetAwaiter()
|
||||
.GetResult() ?? new DnaScannerConfig();
|
||||
|
||||
public override string Name => Locale[DnaMsgs.SHOP_ITEM_DNA];
|
||||
public override string Description => Locale[DnaMsgs.SHOP_ITEM_DNA_DESC];
|
||||
public override ShopItemConfig Config => config;
|
||||
|
||||
public override void OnPurchase(IOnlinePlayer player) { }
|
||||
|
||||
public override PurchaseResult CanPurchase(IOnlinePlayer player) {
|
||||
if (Shop.HasItem(player, this)) return PurchaseResult.ALREADY_OWNED;
|
||||
return base.CanPurchase(player);
|
||||
}
|
||||
}
|
||||
74
TTT/CS2/Items/Station/DamageStation.cs
Normal file
74
TTT/CS2/Items/Station/DamageStation.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ShopAPI.Configs;
|
||||
using TTT.API.Extensions;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Role;
|
||||
using TTT.API.Storage;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.Game.Roles;
|
||||
|
||||
namespace TTT.CS2.Items.Station;
|
||||
|
||||
public static class DamageStationCollection {
|
||||
public static void AddDamageStation(this IServiceCollection collection) {
|
||||
collection.AddModBehavior<DamageStation>();
|
||||
}
|
||||
}
|
||||
|
||||
public class DamageStation(IServiceProvider provider) : StationItem(provider,
|
||||
provider.GetService<IStorage<DamageStationConfig>>()
|
||||
?.Load()
|
||||
.GetAwaiter()
|
||||
.GetResult() ?? new DamageStationConfig()) {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
private readonly IPlayerFinder finder =
|
||||
provider.GetRequiredService<IPlayerFinder>();
|
||||
|
||||
private readonly IRoleAssigner roles =
|
||||
provider.GetRequiredService<IRoleAssigner>();
|
||||
|
||||
public override string Name => Locale[StationMsgs.SHOP_ITEM_STATION_HURT];
|
||||
|
||||
public override string Description
|
||||
=> Locale[StationMsgs.SHOP_ITEM_STATION_HURT_DESC];
|
||||
|
||||
override protected void onInterval() {
|
||||
var players = finder.GetOnline();
|
||||
foreach (var (prop, info) in props) {
|
||||
if (Math.Abs(info.HealthGiven) > Math.Abs(_Config.TotalHealthGiven)) {
|
||||
props.Remove(prop);
|
||||
continue;
|
||||
}
|
||||
|
||||
var propPos = prop.AbsOrigin;
|
||||
if (propPos == null) continue;
|
||||
|
||||
var playerMapping = players.Select(p
|
||||
=> (ApiPlayer: p, GamePlayer: converter.GetPlayer(p)))
|
||||
.Where(m => m.GamePlayer != null);
|
||||
|
||||
var playerDists = playerMapping
|
||||
.Where(t => !roles.GetRoles(t.ApiPlayer).OfType<TraitorRole>().Any())
|
||||
.Select(t => (t.ApiPlayer, Origin: t.GamePlayer!.Pawn.Value?.AbsOrigin,
|
||||
t.GamePlayer))
|
||||
.Where(t => t is { Origin: not null, ApiPlayer.IsAlive: true })
|
||||
.Select(t
|
||||
=> (t.ApiPlayer, Dist: t.Origin!.Distance(propPos), t.GamePlayer))
|
||||
.Where(t => t.Dist <= _Config.MaxRange)
|
||||
.ToList();
|
||||
|
||||
foreach (var (player, dist, gamePlayer) in playerDists) {
|
||||
var healthScale = 1.0 - dist / _Config.MaxRange;
|
||||
var damageAmount =
|
||||
(int)Math.Floor(_Config.HealthIncrements * healthScale);
|
||||
player.Health += damageAmount;
|
||||
info.HealthGiven += damageAmount;
|
||||
|
||||
gamePlayer.ExecuteClientCommand("play " + _Config.UseSound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
TTT/CS2/Items/Station/HealthStation.cs
Normal file
57
TTT/CS2/Items/Station/HealthStation.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ShopAPI.Configs;
|
||||
using TTT.API.Extensions;
|
||||
using TTT.API.Storage;
|
||||
using TTT.CS2.Extensions;
|
||||
|
||||
namespace TTT.CS2.Items.Station;
|
||||
|
||||
public static class HealthStationCollection {
|
||||
public static void AddHealthStation(this IServiceCollection collection) {
|
||||
collection.AddModBehavior<HealthStation>();
|
||||
}
|
||||
}
|
||||
|
||||
public class HealthStation(IServiceProvider provider) : StationItem(provider,
|
||||
provider.GetService<IStorage<HealthStationConfig>>()
|
||||
?.Load()
|
||||
.GetAwaiter()
|
||||
.GetResult() ?? new HealthStationConfig()) {
|
||||
public override string Name => Locale[StationMsgs.SHOP_ITEM_STATION_HEALTH];
|
||||
|
||||
public override string Description
|
||||
=> Locale[StationMsgs.SHOP_ITEM_STATION_HEALTH_DESC];
|
||||
|
||||
override protected void onInterval() {
|
||||
var players = Utilities.GetPlayers();
|
||||
foreach (var (prop, info) in props) {
|
||||
if (Math.Abs(info.HealthGiven) > _Config.TotalHealthGiven) {
|
||||
props.Remove(prop);
|
||||
continue;
|
||||
}
|
||||
|
||||
var propPos = prop.AbsOrigin;
|
||||
if (propPos == null) continue;
|
||||
|
||||
var playerDists = players
|
||||
.Select(p => (Player: p, Pos: p.Pawn.Value?.AbsOrigin))
|
||||
.Where(t => t is { Pos: not null, Player.Pawn.Value.Health: > 0 })
|
||||
.Select(t => (t.Player, Dist: t.Pos!.Distance(propPos)))
|
||||
.Where(t => t.Dist <= _Config.MaxRange)
|
||||
.ToList();
|
||||
|
||||
foreach (var (player, dist) in playerDists) {
|
||||
var maxHp = player.Pawn.Value?.MaxHealth ?? 100;
|
||||
var healthScale = 1.0 - dist / _Config.MaxRange;
|
||||
var healAmount =
|
||||
(int)Math.Ceiling(_Config.HealthIncrements * healthScale);
|
||||
var newHealth = Math.Min(player.GetHealth() + healAmount, maxHp);
|
||||
player.SetHealth(newHealth);
|
||||
info.HealthGiven += healAmount;
|
||||
|
||||
player.ExecuteClientCommand("play " + _Config.UseSound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
TTT/CS2/Items/Station/StationInfo.cs
Normal file
9
TTT/CS2/Items/Station/StationInfo.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
|
||||
namespace TTT.CS2.Items.Station;
|
||||
|
||||
public class StationInfo(CPhysicsPropMultiplayer prop, int health) {
|
||||
public readonly CPhysicsPropMultiplayer Prop = prop;
|
||||
public int Health = health;
|
||||
public int HealthGiven = 0;
|
||||
}
|
||||
141
TTT/CS2/Items/Station/StationItem.cs
Normal file
141
TTT/CS2/Items/Station/StationItem.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using System.Reactive.Concurrency;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ShopAPI;
|
||||
using ShopAPI.Configs;
|
||||
using TTT.API;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.Game.Roles;
|
||||
|
||||
namespace TTT.CS2.Items.Station;
|
||||
|
||||
public abstract class StationItem(IServiceProvider provider,
|
||||
StationConfig config)
|
||||
: RoleRestrictedItem<DetectiveRole>(provider), IPluginModule {
|
||||
private static readonly long PROP_SIZE_SQUARED = 500;
|
||||
protected readonly StationConfig _Config = config;
|
||||
|
||||
protected readonly IPlayerConverter<CCSPlayerController> Converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
protected readonly Dictionary<CPhysicsPropMultiplayer, StationInfo> props =
|
||||
new();
|
||||
|
||||
private readonly IScheduler scheduler =
|
||||
provider.GetRequiredService<IScheduler>();
|
||||
|
||||
private IDisposable? intervalHandle;
|
||||
|
||||
public override ShopItemConfig Config => _Config;
|
||||
|
||||
public override void Start() {
|
||||
base.Start();
|
||||
intervalHandle = scheduler.SchedulePeriodic(_Config.HealthInterval,
|
||||
() => Server.NextWorldUpdate(onInterval));
|
||||
}
|
||||
|
||||
public void Start(BasePlugin? plugin) {
|
||||
Start();
|
||||
plugin
|
||||
?.RegisterListener<
|
||||
CounterStrikeSharp.API.Core.Listeners.OnServerPrecacheResources>(m => {
|
||||
m.AddResource("models/props/cs_office/microwave.vmdl");
|
||||
});
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
base.Dispose();
|
||||
intervalHandle?.Dispose();
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[GameEventHandler]
|
||||
public HookResult OnBulletImpact(EventBulletImpact ev, GameEventInfo info) {
|
||||
var hitVec = new Vector(ev.X, ev.Y, ev.Z);
|
||||
|
||||
var nearest = props
|
||||
.Select(kv => (kv.Key, kv.Value,
|
||||
Distance: kv.Key.AbsOrigin!.DistanceSquared(hitVec)))
|
||||
.Where(t => t.Key is { IsValid: true, AbsOrigin: not null })
|
||||
.OrderBy(t => t.Distance)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (nearest.Key == null || nearest.Value == null
|
||||
|| nearest.Distance > PROP_SIZE_SQUARED)
|
||||
return HookResult.Continue;
|
||||
|
||||
var dmg = getBulletDamage(ev);
|
||||
nearest.Value.Health -= dmg;
|
||||
|
||||
if (nearest.Value.Health <= 0) {
|
||||
nearest.Key.AcceptInput("Kill");
|
||||
props.Remove(nearest.Key);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
nearest.Key.SetColor(
|
||||
_Config.GetColor(nearest.Value.Health / (float)_Config.StationHealth));
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private int getBulletDamage(EventBulletImpact ev) {
|
||||
var user = ev.Userid;
|
||||
var weapon = user?.Pawn.Value?.WeaponServices?.ActiveWeapon.Value;
|
||||
var dist =
|
||||
(user?.Pawn.Value?.AbsOrigin?.Distance(new Vector(ev.X, ev.Y, ev.Z))
|
||||
?? null) ?? 1;
|
||||
var distScale = Math.Clamp(256 / dist, 0.1, 1);
|
||||
var baseDamage = getBaseDamage(weapon?.DesignerName ?? "");
|
||||
var total = (int)(baseDamage * distScale);
|
||||
return Math.Max(total, 1);
|
||||
}
|
||||
|
||||
private int getBaseDamage(string designerWeapon) {
|
||||
return designerWeapon switch {
|
||||
"weapon_awp" => 115,
|
||||
"weapon_glock" => 8,
|
||||
"weapon_usp_silencer" => 20,
|
||||
"weapon_deagle" => 40,
|
||||
_ when Tag.PISTOLS.Contains(designerWeapon) => 10,
|
||||
_ when Tag.SMGS.Contains(designerWeapon) => 15,
|
||||
_ when Tag.SHOTGUNS.Contains(designerWeapon) => 25,
|
||||
_ when Tag.RIFLES.Contains(designerWeapon) => 45,
|
||||
_ => 5
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnPurchase(IOnlinePlayer player) {
|
||||
Server.NextWorldUpdate(() => {
|
||||
var prop =
|
||||
Utilities.CreateEntityByName<CPhysicsPropMultiplayer>(
|
||||
"prop_physics_multiplayer");
|
||||
|
||||
if (prop == null) return;
|
||||
|
||||
props[prop] = new StationInfo(prop, _Config.StationHealth);
|
||||
|
||||
prop.SetModel("models/props/cs_office/microwave.vmdl");
|
||||
prop.DispatchSpawn();
|
||||
|
||||
var gamePlayer = Converter.GetPlayer(player);
|
||||
if (gamePlayer == null || !gamePlayer.Pawn.IsValid
|
||||
|| gamePlayer.Pawn.Value == null)
|
||||
return;
|
||||
var spawnPos = gamePlayer.Pawn.Value.AbsOrigin.Clone();
|
||||
if (spawnPos != null && gamePlayer.PlayerPawn.Value != null) {
|
||||
var forward = gamePlayer.PlayerPawn.Value.EyeAngles.ToForward();
|
||||
forward.Z = 0;
|
||||
spawnPos += forward.Normalized() * 8;
|
||||
}
|
||||
|
||||
prop.Teleport(spawnPos);
|
||||
});
|
||||
}
|
||||
|
||||
abstract protected void onInterval();
|
||||
}
|
||||
17
TTT/CS2/Items/Station/StationMsgs.cs
Normal file
17
TTT/CS2/Items/Station/StationMsgs.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using TTT.Locale;
|
||||
|
||||
namespace TTT.CS2.Items.Station;
|
||||
|
||||
public class StationMsgs {
|
||||
public static IMsg SHOP_ITEM_STATION_HEALTH
|
||||
=> MsgFactory.Create(nameof(SHOP_ITEM_STATION_HEALTH));
|
||||
|
||||
public static IMsg SHOP_ITEM_STATION_HEALTH_DESC
|
||||
=> MsgFactory.Create(nameof(SHOP_ITEM_STATION_HEALTH_DESC));
|
||||
|
||||
public static IMsg SHOP_ITEM_STATION_HURT
|
||||
=> MsgFactory.Create(nameof(SHOP_ITEM_STATION_HURT));
|
||||
|
||||
public static IMsg SHOP_ITEM_STATION_HURT_DESC
|
||||
=> MsgFactory.Create(nameof(SHOP_ITEM_STATION_HURT_DESC));
|
||||
}
|
||||
@@ -1,89 +1,69 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using JetBrains.Annotations;
|
||||
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.lang;
|
||||
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>();
|
||||
|
||||
[UsedImplicitly]
|
||||
[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);
|
||||
if (identifyEvent.IsCanceled) return;
|
||||
Bus.Dispatch(identifyEvent);
|
||||
}
|
||||
|
||||
body.IsIdentified = true;
|
||||
var role = roles.GetRoles(body.OfPlayer);
|
||||
[UsedImplicitly]
|
||||
[EventHandler(IgnoreCanceled = true)]
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
116
TTT/CS2/Listeners/PlayerStatsTracker.cs
Normal file
116
TTT/CS2/Listeners/PlayerStatsTracker.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using CounterStrikeSharp.API;
|
||||
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;
|
||||
using TTT.Game.Events.Player;
|
||||
|
||||
namespace TTT.CS2.Listeners;
|
||||
|
||||
public class PlayerStatsTracker(IServiceProvider provider) : IListener {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
private readonly IPlayerFinder finder =
|
||||
provider.GetRequiredService<IPlayerFinder>();
|
||||
|
||||
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);
|
||||
if (gamePlayer == null || !gamePlayer.IsValid) return;
|
||||
|
||||
var stats = gamePlayer.ActionTrackingServices?.MatchStats;
|
||||
if (stats == null) return;
|
||||
|
||||
stats.Deaths++;
|
||||
Utilities.SetStateChanged(gamePlayer, "CCSPlayerController",
|
||||
"m_pActionTrackingServices");
|
||||
revealedDeaths.Add(gamePlayer.Slot);
|
||||
}
|
||||
|
||||
// Needs to be higher so we detect the kill before the game ends
|
||||
// in the case that this is the last player
|
||||
[EventHandler(Priority = Priority.HIGH)]
|
||||
public void OnKill(PlayerDeathEvent ev) {
|
||||
var killer = ev.Killer == null ? null : converter.GetPlayer(ev.Killer);
|
||||
var assister =
|
||||
ev.Assister == null ? null : converter.GetPlayer(ev.Assister);
|
||||
|
||||
if (killer != null) {
|
||||
roundKillsAndAssists.TryGetValue(killer.Slot, out var def);
|
||||
def.Item1++;
|
||||
roundKillsAndAssists[killer.Slot] = def;
|
||||
}
|
||||
|
||||
if (assister != null && assister != killer) {
|
||||
roundKillsAndAssists.TryGetValue(assister.Slot, out var def);
|
||||
def.Item2++;
|
||||
roundKillsAndAssists[assister.Slot] = def;
|
||||
}
|
||||
}
|
||||
|
||||
[EventHandler]
|
||||
public void OnRoundEnd(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState == State.IN_PROGRESS) {
|
||||
revealedDeaths.Clear();
|
||||
roundKillsAndAssists.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.NewState != State.FINISHED) return;
|
||||
revealDeaths();
|
||||
revealKills();
|
||||
}
|
||||
|
||||
private void revealDeaths() {
|
||||
var online = finder.GetOnline()
|
||||
.Where(p => !p.IsAlive)
|
||||
.Select(p => converter.GetPlayer(p))
|
||||
.OfType<CCSPlayerController>()
|
||||
.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;
|
||||
|
||||
stats.Deaths++;
|
||||
Utilities.SetStateChanged(player, "CCSPlayerController",
|
||||
"m_pActionTrackingServices");
|
||||
}
|
||||
}
|
||||
|
||||
private void revealKills() {
|
||||
var online = finder.GetOnline()
|
||||
.Select(p => converter.GetPlayer(p))
|
||||
.OfType<CCSPlayerController>()
|
||||
.Where(p => p.IsValid && roundKillsAndAssists.ContainsKey(p.Slot));
|
||||
|
||||
foreach (var player in online) {
|
||||
var stats = player.ActionTrackingServices?.MatchStats;
|
||||
if (stats == null) continue;
|
||||
|
||||
var (kills, assists) = roundKillsAndAssists[player.Slot];
|
||||
stats.Kills += kills;
|
||||
stats.Assists += assists;
|
||||
Utilities.SetStateChanged(player, "CCSPlayerController",
|
||||
"m_pActionTrackingServices");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using System.Drawing;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
@@ -7,22 +10,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()
|
||||
@@ -32,7 +31,10 @@ public class RoundTimerListener(IServiceProvider provider) : IListener {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
public void Dispose() { bus.UnregisterListener(this); }
|
||||
private readonly IScheduler scheduler = provider
|
||||
.GetRequiredService<IScheduler>();
|
||||
|
||||
private IDisposable? endTimer;
|
||||
|
||||
[UsedImplicitly]
|
||||
[EventHandler(IgnoreCanceled = true)]
|
||||
@@ -45,17 +47,29 @@ public class RoundTimerListener(IServiceProvider provider) : IListener {
|
||||
foreach (var player in Utilities.GetPlayers()
|
||||
.Where(p => p.LifeState != (int)LifeState_t.LIFE_ALIVE))
|
||||
player.Respawn();
|
||||
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
player.SetColor(Color.White);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.NewState == State.FINISHED) endTimer?.Dispose();
|
||||
if (ev.NewState != State.IN_PROGRESS) return;
|
||||
var duration = config.RoundCfg.RoundDuration(ev.Game.Players.Count);
|
||||
Messenger.DebugAnnounce("Total duration: {0} for {1} player", duration,
|
||||
ev.Game.Players.Count);
|
||||
Server.NextWorldUpdate(() => {
|
||||
RoundUtil.SetTimeRemaining((int)config.RoundCfg
|
||||
.RoundDuration(ev.Game.Players.Count)
|
||||
.TotalSeconds);
|
||||
Server.ExecuteCommand("mp_ignore_round_win_conditions 0");
|
||||
RoundUtil.SetTimeRemaining((int)duration.TotalSeconds);
|
||||
});
|
||||
|
||||
endTimer?.Dispose();
|
||||
endTimer = scheduler.Schedule(duration, () => {
|
||||
Server.NextWorldUpdate(() => {
|
||||
Messenger.DebugAnnounce("Time is up!");
|
||||
ev.Game.EndGame(EndReason.TIMEOUT(new InnocentRole(provider)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,29 +77,61 @@ public class RoundTimerListener(IServiceProvider provider) : IListener {
|
||||
[EventHandler(IgnoreCanceled = true)]
|
||||
public void OnRoundEnd(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState != State.FINISHED) return;
|
||||
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);
|
||||
}
|
||||
revealRoles(ev.Game);
|
||||
|
||||
foreach (var inno in ev.Game.GetAlive(typeof(InnocentRole))) {
|
||||
var player = converter.GetPlayer(inno);
|
||||
player?.SwitchTeam(CsTeam.CounterTerrorist);
|
||||
}
|
||||
Server.NextWorldUpdate(() => {
|
||||
var endReason = endRound(ev);
|
||||
|
||||
new EventNextlevelChanged(true).FireEvent(false);
|
||||
if (ev.Game.WinningRole != null)
|
||||
RoundUtil.AddTeamScore(
|
||||
endReason == RoundEndReason.CTsWin ?
|
||||
CsTeam.CounterTerrorist :
|
||||
CsTeam.Terrorist, 1);
|
||||
|
||||
var timer = Observable.Timer(
|
||||
config.RoundCfg.TimeBetweenRounds, Scheduler);
|
||||
timer.Subscribe(_ => Server.NextWorldUpdate(() => {
|
||||
Server.ExecuteCommand("mp_ignore_round_win_conditions 1");
|
||||
RoundUtil.EndRound(endReason);
|
||||
Server.ExecuteCommand("mp_ignore_round_win_conditions 0");
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
base.Dispose();
|
||||
|
||||
endTimer?.Dispose();
|
||||
}
|
||||
}
|
||||
27
TTT/CS2/Listeners/ScreenColorApplier.cs
Normal file
27
TTT/CS2/Listeners/ScreenColorApplier.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Drawing;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.CS2.Roles;
|
||||
using TTT.Game.Events.Player;
|
||||
using TTT.Game.Listeners;
|
||||
|
||||
namespace TTT.CS2.Listeners;
|
||||
|
||||
public class ScreenColorApplier(IServiceProvider provider)
|
||||
: BaseListener(provider) {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
[EventHandler]
|
||||
public void OnRoleAssign(PlayerRoleAssignEvent ev) {
|
||||
if (ev.Role is SpectatorRole) return;
|
||||
|
||||
var player = converter.GetPlayer(ev.Player);
|
||||
var alphaColor = Color.FromArgb(64, ev.Role.Color);
|
||||
if (player != null)
|
||||
player.ColorScreen(alphaColor, 3, 1, PlayerExtensions.FadeFlags.FADE_OUT);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ namespace TTT.CS2.Player;
|
||||
public class CCPlayerConverter : IPluginModule,
|
||||
IPlayerConverter<CCSPlayerController> {
|
||||
private readonly Dictionary<string, CS2Player> playerCache = new();
|
||||
private readonly Dictionary<string, CCSPlayerController> reverseCache = new();
|
||||
|
||||
public IPlayer GetPlayer(CCSPlayerController player) {
|
||||
if (playerCache.TryGetValue(player.SteamID.ToString(),
|
||||
@@ -28,18 +29,23 @@ public class CCPlayerConverter : IPluginModule,
|
||||
|
||||
public CCSPlayerController? GetPlayer(IPlayer player) {
|
||||
if (!ulong.TryParse(player.Id, out var steamId)) return null;
|
||||
if (reverseCache.TryGetValue(player.Id, out var cachedPlayer)) {
|
||||
if (cachedPlayer.IsValid) return cachedPlayer;
|
||||
|
||||
reverseCache.Remove(player.Id);
|
||||
}
|
||||
|
||||
CCSPlayerController? result = null;
|
||||
var gamePlayer = Utilities.GetPlayerFromSteamId(steamId);
|
||||
if (gamePlayer is { IsValid: true }) result = gamePlayer;
|
||||
|
||||
var bot = Utilities.GetPlayerFromIndex((int)steamId);
|
||||
if (bot is { IsValid: true }) result = bot;
|
||||
if (result != null) reverseCache[player.Id] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Dispose() { playerCache.Clear(); }
|
||||
public string Name => "PlayerConverter";
|
||||
public string Version => GitVersionInformation.FullSemVer;
|
||||
|
||||
public void Start() { }
|
||||
}
|
||||
63
TTT/CS2/Player/CS2AliveSpoofer.cs
Normal file
63
TTT/CS2/Player/CS2AliveSpoofer.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
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) {
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
FakeAlivePlayers.Add(player);
|
||||
}
|
||||
|
||||
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,4 +1,6 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using TTT.API;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.Extensions;
|
||||
@@ -7,49 +9,126 @@ 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;
|
||||
public Task GiveWeapon(IOnlinePlayer player, IWeapon weapon) {
|
||||
return Server.NextWorldUpdateAsync(() => {
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
|
||||
giveWeapon(gamePlayer, weapon);
|
||||
});
|
||||
}
|
||||
|
||||
public Task RemoveWeapon(IOnlinePlayer player, string weaponId) {
|
||||
return Server.NextWorldUpdateAsync(() => {
|
||||
if (!player.IsAlive) return;
|
||||
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
|
||||
var pawn = gamePlayer.Pawn.Value;
|
||||
|
||||
if (pawn == null || pawn.WeaponServices == null) return;
|
||||
|
||||
var matchedWeapon =
|
||||
pawn.WeaponServices.MyWeapons.FirstOrDefault(x
|
||||
=> x.Value?.DesignerName == weaponId);
|
||||
|
||||
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>();
|
||||
|
||||
gamePlayer.DropActiveWeapon();
|
||||
activeWeaponEntity?.AddEntityIOEvent("Kill", activeWeaponEntity);
|
||||
});
|
||||
}
|
||||
|
||||
public Task RemoveWeaponInSlot(IOnlinePlayer player, int slot) {
|
||||
return Server.NextWorldUpdateAsync(() => {
|
||||
if (!player.IsAlive) return;
|
||||
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
|
||||
clearSlot(gamePlayer, IntToSlot(slot));
|
||||
});
|
||||
}
|
||||
|
||||
public Task RemoveAllWeapons(IOnlinePlayer player) {
|
||||
return Server.NextWorldUpdateAsync(() => {
|
||||
if (!player.IsAlive) return;
|
||||
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
|
||||
gamePlayer.RemoveWeapons();
|
||||
});
|
||||
}
|
||||
|
||||
private void giveWeapon(CCSPlayerController player, IWeapon weapon) {
|
||||
if (player.Team is CsTeam.None or CsTeam.Spectator) return;
|
||||
|
||||
// Give the weapon
|
||||
player.GiveNamedItem(weapon.WeaponId);
|
||||
|
||||
// Set ammo if applicable
|
||||
var weaponBase = player.GetWeaponBase(weapon.WeaponId);
|
||||
if (weaponBase == null)
|
||||
if (weapon.WeaponId.Equals("weapon_revolver"))
|
||||
weaponBase = player.GetWeaponBase("weapon_deagle");
|
||||
|
||||
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;
|
||||
if (weapon.ReserveAmmo != null)
|
||||
weaponBase.ReserveAmmo[0] = weapon.ReserveAmmo.Value;
|
||||
Utilities.SetStateChanged(weaponBase, "CBasePlayerWeapon", "m_iClip1");
|
||||
Utilities.SetStateChanged(weaponBase, "CBasePlayerWeapon",
|
||||
"m_pReserveAmmo");
|
||||
}
|
||||
|
||||
public void RemoveWeapon(IOnlinePlayer player, string weaponId) {
|
||||
if (!player.IsAlive) return;
|
||||
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
|
||||
var pawn = gamePlayer.PlayerPawn.Value;
|
||||
|
||||
if (pawn == null || pawn.WeaponServices == null) return;
|
||||
|
||||
var matchedWeapon =
|
||||
pawn.WeaponServices.MyWeapons.FirstOrDefault(x
|
||||
=> x.Value?.DesignerName == weaponId);
|
||||
|
||||
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>();
|
||||
|
||||
gamePlayer.DropActiveWeapon();
|
||||
activeWeaponEntity?.AddEntityIOEvent("Kill", activeWeaponEntity);
|
||||
public static gear_slot_t IntToSlot(int slot) {
|
||||
return slot switch {
|
||||
0 => gear_slot_t.GEAR_SLOT_RIFLE,
|
||||
1 => gear_slot_t.GEAR_SLOT_PISTOL,
|
||||
2 => gear_slot_t.GEAR_SLOT_KNIFE,
|
||||
3 => gear_slot_t.GEAR_SLOT_UTILITY,
|
||||
4 => gear_slot_t.GEAR_SLOT_C4,
|
||||
_ => gear_slot_t.GEAR_SLOT_FIRST
|
||||
};
|
||||
}
|
||||
|
||||
public void RemoveAllWeapons(IOnlinePlayer player) {
|
||||
if (!player.IsAlive) return;
|
||||
public static int SlotToInt(gear_slot_t slot) {
|
||||
return slot switch {
|
||||
gear_slot_t.GEAR_SLOT_RIFLE => 0,
|
||||
gear_slot_t.GEAR_SLOT_PISTOL => 1,
|
||||
gear_slot_t.GEAR_SLOT_KNIFE => 2,
|
||||
gear_slot_t.GEAR_SLOT_UTILITY => 3,
|
||||
gear_slot_t.GEAR_SLOT_C4 => 4,
|
||||
_ => -1
|
||||
};
|
||||
}
|
||||
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
private void clearSlot(CCSPlayerController player,
|
||||
params gear_slot_t[] slots) {
|
||||
if (player.Team is CsTeam.None or CsTeam.Spectator) return;
|
||||
var weapons = player.Pawn.Value?.WeaponServices?.MyWeapons;
|
||||
if (weapons == null || weapons.Count == 0) return;
|
||||
|
||||
gamePlayer.RemoveWeapons();
|
||||
foreach (var weapon in weapons) {
|
||||
if (!weapon.IsValid || weapon.Value == null) continue;
|
||||
if (!weapon.Value.IsValid
|
||||
|| !weapon.Value.DesignerName.StartsWith("weapon_"))
|
||||
continue;
|
||||
if (weapon.Value.Entity == null) continue;
|
||||
var weaponBase = weapon.Value.As<CCSWeaponBase>();
|
||||
if (!weaponBase.IsValid || weaponBase.Entity == null) continue;
|
||||
var vdata = weaponBase.VData;
|
||||
if (vdata == null) continue;
|
||||
if (!slots.Contains(vdata.GearSlot)) continue;
|
||||
weapon.Value.AddEntityIOEvent("Kill", weapon.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,28 +35,41 @@ 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;
|
||||
Player.Pawn.Value.Health = value;
|
||||
Utilities.SetStateChanged(Player.Pawn.Value, "CBaseEntity", "m_iHealth");
|
||||
Server.NextWorldUpdate(() => {
|
||||
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 +85,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,9 +107,34 @@ public class CS2Player : IOnlinePlayer {
|
||||
return player.SteamID.ToString();
|
||||
}
|
||||
|
||||
public override string ToString() { return $"({getSuffix(Id, 5)}) {Name}"; }
|
||||
public override string ToString() { return createPaddedName(); }
|
||||
|
||||
private string getSuffix(string s, int len) {
|
||||
return s.Length <= len ? s : s[^len..];
|
||||
// Goal: Pad the name to a fixed width for better alignment in logs
|
||||
// Left-align ID, right-align name
|
||||
private string createPaddedName() {
|
||||
return CreatePaddedName(Id, Name, namePadding + 8);
|
||||
}
|
||||
|
||||
public static string CreatePaddedName(string id, string name, int len) {
|
||||
var suffix = id.Length > 5 ? id[^5..] : id.PadLeft(5, '0');
|
||||
var prefix = $"({suffix})";
|
||||
|
||||
var baseStr = $"{prefix} {name}";
|
||||
|
||||
if (baseStr.Length == len) return baseStr;
|
||||
|
||||
if (baseStr.Length < len) {
|
||||
// Pad spaces so the name ends up right-aligned
|
||||
var padding = len - (prefix.Length + name.Length);
|
||||
return prefix + new string(' ', padding + 1) + name;
|
||||
}
|
||||
|
||||
// Too long, cut off from the end of the name
|
||||
var availableForName = len - (prefix.Length + 1);
|
||||
if (availableForName < 0) availableForName = 0;
|
||||
var trimmedName = name.Length > availableForName ?
|
||||
name[..availableForName] :
|
||||
name;
|
||||
return $"{prefix} {trimmedName}";
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user