Compare commits

...

74 Commits

Author SHA1 Message Date
MSWS
50d078f78e feat: Refactor and enhance tripwire system functionality. +semver:minor
Enhance Tripwire System with New Configurations and API Interfaces

- Update `TripwireConfig.cs` to add new configurations for tripwire appearance and behavior, including multipliers for out-of-line-of-sight detection and settings for size, color, and thickness.
- Refactor `TripwireMovementListener.cs` to improve tripwire activation logic by adopting a new listener and tripwire tracking approach, hence enhancing code maintainability and operational checks.
- Introduce the `TripwireInstance.cs` file to define a record structure for managing tripwire-related properties, integrating with existing APIs.
- Implement new interfaces in `ITripwireActivator.cs` and `ITripwireTracker.cs` to support modular activation and tracking of tripwire instances, facilitating clear interaction definitions within the system.
- Revise `TripwireItem.cs` to refine tripwire management, incorporating new utility methods for vector-to-angle conversions and applying configuration-based settings for visual aspects of tripwires.
- Adjust the `VectorExtensions.cs` to include a new method for converting vectors to angles, improving calculations in tripwire functionalities.

Additionally, integrate changes across related files to enhance the overall functionality and maintainability of the Tripwire system within the platform.
2025-11-05 04:28:21 -08:00
MSWS
edbff4e17f feat: Add range limit and refund system to tripwires 2025-11-05 03:49:16 -08:00
MSWS
b4452d7ff3 refactor: Cleanup and reformat 2025-11-05 03:37:56 -08:00
MSWS
fd32744bf6 update: Licenses 2025-11-05 03:34:52 -08:00
MSWS
657306c1c7 feat: Add tripwire CS2 config, boost damage slightly 2025-11-04 18:33:23 -08:00
MSWS
2c800b471b Merge branch 'dev' of github.com:MSWS/TTT into dev 2025-11-03 23:15:22 -08:00
MSWS
2787823f86 fix: Fix how end pos is calculated for tripwires 2025-11-03 23:14:46 -08:00
Isaac
a5f419aad9 Merge branch 'main' into dev 2025-11-03 22:38:26 -08:00
MSWS
430a8c4a7f fix: Nerf tripwire damage, fix damage station giving health 2025-11-03 22:36:17 -08:00
Isaac
a7a44b50f9 feat: Tripwire Item, Pistol Round (#173)
### Features

* Added **Tripwire** item
* Added **[DETECTIVE]** role prefix support for **MAUL**

### Fixes

* Fixed a bug causing **negative damage** to be logged

### Updates

* **Reduced time granted per kill** in speed rounds from **10 → 8
seconds**
* **Rebalanced special round weights**
* **Adjusted killfeed visibility** for traitors
* **Modified hurt stations** to only play the hurt sound **to yourself**
2025-11-03 21:32:25 -08:00
MSWS
3b4bf490bc fix: Negative damage logging, server crashes 2025-11-03 21:26:56 -08:00
MSWS
abe75d0347 fix: Adjust detective role color 2025-11-03 21:13:01 -08:00
MSWS
eb79552ba3 fix: Fix speed round config 2025-11-03 21:11:35 -08:00
MSWS
e2011b8d24 feat: Add pistol rounds (resolves #169) 2025-11-03 21:05:43 -08:00
MSWS
ec41a6f367 feat: Add tripwire item (resolves #165) +semver:minor 2025-11-03 20:51:53 -08:00
MSWS
9b1bed6982 Begin work on tripwire item 2025-11-03 16:53:39 -08:00
MSWS
8584877739 feat: Replicate death events to fellow traitors (resolves #171) +semver:minor 2025-11-03 15:30:44 -08:00
MSWS
410dd407b3 update: Handle tag colors as well 2025-11-02 21:23:19 -08:00
MSWS
a0bba2c4ba Add warden tag 2025-11-02 21:16:40 -08:00
MSWS
8aa508bf6d feat: Add victim message to 1-hit weapon 2025-11-02 20:45:23 -08:00
MSWS
642155b1bc Revert "fix: Add check after roles assigned"
This reverts commit bacd288fe7.
2025-11-02 20:41:01 -08:00
MSWS
bacd288fe7 fix: Add check after roles assigned 2025-11-02 20:40:05 -08:00
MSWS
29e28038b8 update: Play poison sound only to player 2025-11-02 19:47:22 -08:00
Isaac
7ce5293ad3 Re-apply consistent values between cfgs and cs2 impl. +semver:patch (#172)
- Undid an unnecessary game hook for role assignments
- Make all rounds give 1 karma to all players
- Re-apply body paint price
- Reduce min requirement of players for special rounds from 8 -> 5
2025-11-02 01:26:22 -08:00
MSWS
b253d8ee12 fix: InvalidateOrder not purging lastUpdate map 2025-11-02 01:23:31 -08:00
MSWS
02575b51e2 Make all rounds give 1 karma 2025-11-02 01:19:24 -08:00
MSWS
d8d365b497 update: Body paint price to match (30) 2025-11-02 01:17:27 -08:00
MSWS
1ac38dc0ad update: Reduce min requirement for special round 8 -> 5 2025-11-02 01:28:16 -07:00
MSWS
62e57ffa97 fix: Reduce karma grants per round 2025-11-02 01:22:06 -07:00
MSWS
81e6b2e695 revert: Remove unnecessary delay in MapHook 2025-11-02 01:18:13 -07:00
Isaac
ae99fab18e Additional Game Balancing, Add +inspect support +semver:minor (#167)
### Features

* Added **Silent Rounds**
* Added **Suppressed Rounds**
* Players can now use `+inspect` (default F) to interact with objects as
an alternative to +use

### Fixes

* Shop now automatically **closes after a purchase**

### Updates

* **Healthshot** cost increased from **25 → 40**
* **Body / Player Compass** cost reduced from **70 → 60**
* **Body Paint** cost reduced from **40 → 30**
* **Camouflage** visibility reduced from **60% → 50%**
* **Speed Round** time capped at **90 seconds**
* **Default weapons removed**
* **Default Detective weapon** changed to **Silenced M4A1**
* **Detective Ratio** reduced during higher populations
2025-11-01 20:03:57 -07:00
MSWS
2ce0457346 fix: Players identifying themselves (resolves #164) 2025-11-01 19:59:35 -07:00
MSWS
ed90c54e53 update: Update suppressed round to only suppress pistols 2025-11-01 19:58:13 -07:00
MSWS
06d2d71f76 feat: Add suppressed and silent rounds, close shop after purchase 2025-11-01 18:59:54 -07:00
MSWS
c6ba041a6b Adjust detective ratio 2025-11-01 18:19:40 -07:00
MSWS
f283d7407e update: Increase speedround limit 2025-11-01 17:56:10 -07:00
MSWS
51ff4df545 update: Reduce camouflage visibility 2025-11-01 17:42:10 -07:00
MSWS
e0ee4bf325 fix: Don't give glocks by default 2025-11-01 17:21:16 -07:00
MSWS
4a4c7e0782 Reduce cost of body paint 2025-11-01 17:00:41 -07:00
MSWS
d4f67ced0c Reduce cost of compasses 2025-11-01 16:41:12 -07:00
MSWS
33ca0c8385 update: Allow inspect button to be used alongside use 2025-10-31 20:10:46 -07:00
MSWS
ff2e97a3ce update: Don't play hurt sound if Traitor is shifting 2025-10-31 20:06:19 -07:00
MSWS
a56cdc1285 Reformat and Cleanup 2025-10-31 20:01:20 -07:00
MSWS
ceda5cba64 fix: Roles not being assigned on first round of map 2025-10-31 19:59:09 -07:00
MSWS
7c203bcd91 update: Increase prices of stickers and healthshot, impl CS2 healthshot config 2025-10-31 19:53:46 -07:00
Isaac
99ed6bd69b update: Increase speedround weight +semver:patch (#166) 2025-10-31 19:10:12 -07:00
MSWS
f91fc54897 update: Increase speedround weight +semver:patch 2025-10-31 18:41:07 -07:00
MSWS
79ab6f9705 Force Build + Release 2025-10-31 17:57:49 -07:00
Isaac
80a9cb2af1 fix: Fake traitors getting damaged by hurt stations (#161) 2025-10-30 21:48:23 -07:00
MSWS
7ca4a6bef4 Update README 2025-10-30 21:46:49 -07:00
MSWS
d589a222c8 Format and Cleanup, make Ts sound like they're being damaged by hurtstation 2025-10-30 21:44:00 -07:00
MSWS
a3fdb590fd fix: Fake traitors getting damaged by hurt stations 2025-10-30 21:00:55 -07:00
Isaac
c4a73f9a24 Require on actual team to be alive (#160) 2025-10-30 18:17:33 -07:00
MSWS
987df197bc Fix concurrent dictionary issue 2025-10-30 18:14:27 -07:00
MSWS
acb3be9132 Require on actual team to be alive 2025-10-30 18:09:14 -07:00
Isaac
8fa2377e1e Game Balancing and Fixes (#159) 2025-10-30 18:03:51 -07:00
MSWS
bbcc998559 Up 1 knife item 2025-10-30 17:57:00 -07:00
MSWS
56781c6ae8 More item balancing, name updating, bug fix 2025-10-30 17:56:09 -07:00
Isaac
dbe664d18f Revert "Fetch playername from object if available" (#158)
This reverts commit 8cd8e14e18.
2025-10-29 16:30:34 -07:00
MSWS
0ca983943d Revert "Fetch playername from object if available"
This reverts commit 8cd8e14e18.
2025-10-29 16:28:56 -07:00
Isaac
5717ab612a Fetch playername from object if available +semver:patch (#157) 2025-10-29 15:36:29 -07:00
MSWS
8cd8e14e18 Fetch playername from object if available 2025-10-29 15:27:13 -07:00
Isaac
f6b79ef038 QoL tweaks +semver:patch (#156) 2025-10-29 01:45:55 -07:00
MSWS
57ef5e3e24 Use pretty name for rtd reward description 2025-10-28 20:31:04 -07:00
MSWS
9c99d316aa Revert "Revert "fix: Allow typing if dead even with muted roll""
This reverts commit e679c5193b.
2025-10-28 19:26:57 -07:00
MSWS
e679c5193b Revert "fix: Allow typing if dead even with muted roll"
This reverts commit daa24a0e87.
2025-10-28 19:26:43 -07:00
MSWS
6ece0450bb fix: Reduce volume of health station 2025-10-28 19:26:26 -07:00
MSWS
daa24a0e87 fix: Allow typing if dead even with muted roll 2025-10-28 19:20:42 -07:00
MSWS
1c8785b388 Add reminder for vanilla rounds 2025-10-28 18:50:09 -07:00
Isaac
cc52d19108 fix: Suppress damage stats (+semver:patch) (#155) 2025-10-28 15:32:06 -07:00
MSWS
a80c36e3c5 Suppress damage stats 2025-10-28 15:29:10 -07:00
MSWS
ba6b6c448f Change currency name to point 2025-10-28 14:06:29 -07:00
Isaac
a1d595ce8a feat: Map integration, Additional Configs, Teleport Item (resolves #152) +semver:minor (#154)
- Use weights for generating special rounds
- Add teleport decoy
- Map hooking for traitor rooms / buttons
- Prevent being able to buy m4a1-s or revolver before round start
- Clean up logs
- Add [BAD] prefix to bad actions
2025-10-28 13:50:57 -07:00
MSWS
23f502a381 Increment version +semver:minor 2025-10-26 23:18:57 -07:00
157 changed files with 2514 additions and 604 deletions

View File

@@ -1,20 +1,17 @@
| 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.Data.Sqlite | 9.0.9 | Expression | MIT | https://licenses.nuget.org/MIT | © Microsoft Corporation. All rights reserved. | Microsoft | https://docs.microsoft.com/dotnet/standard/data/sqlite/ |
| 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 20162024 Bradley Grainger | Bradley Grainger | https://mysqlconnector.net/ |
| SQLite | 3.13.0 | Unknown | | | Public Domain | SQLite Development Team | |
| 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 |
| 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.342 | 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.Data.Sqlite | 9.0.9 | Expression | MIT | https://licenses.nuget.org/MIT | © Microsoft Corporation. All rights reserved. | Microsoft | https://docs.microsoft.com/dotnet/standard/data/sqlite/ |
| 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 |
| 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 |

View File

@@ -16,7 +16,9 @@ survive while eliminating the traitors among them.
- [X] Innocents
- [X] Shop
- [X] Karma
- [ ] Statistics
- [X] Statistics
- [X] Map Integrations
- [X] Special Rounds
## Versioning

View File

@@ -1,22 +1,18 @@
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using TTT.API;
using SpecialRoundAPI.Configs;
using TTT.API.Events;
using TTT.Game.Events.Game;
using TTT.Game.Listeners;
using TTT.Locale;
namespace SpecialRoundAPI;
public abstract class AbstractSpecialRound(IServiceProvider provider)
: ITerrorModule, IListener {
protected readonly IServiceProvider Provider = provider;
: BaseListener(provider) {
protected readonly ISpecialRoundTracker Tracker =
provider.GetRequiredService<ISpecialRoundTracker>();
public void Dispose() { }
public void Start() { }
public abstract string Name { get; }
public abstract IMsg Description { get; }
public abstract SpecialRoundConfig Config { get; }

View File

@@ -1,5 +0,0 @@
namespace SpecialRoundAPI;
public record BhopRoundConfig : SpecialRoundConfig {
public override float Weight { get; init; } = 0.2f;
}

View File

@@ -0,0 +1,5 @@
namespace SpecialRoundAPI.Configs;
public record BhopRoundConfig : SpecialRoundConfig {
public override float Weight { get; init; } = 0.25f;
}

View File

@@ -0,0 +1,5 @@
namespace SpecialRoundAPI.Configs;
public record PistolRoundConfig : SpecialRoundConfig {
public override float Weight { get; init; } = 0.75f;
}

View File

@@ -0,0 +1,5 @@
namespace SpecialRoundAPI.Configs;
public record SilentRoundConfig : SpecialRoundConfig {
public override float Weight { get; init; } = 0.5f;
}

View File

@@ -1,4 +1,4 @@
namespace SpecialRoundAPI;
namespace SpecialRoundAPI.Configs;
public abstract record SpecialRoundConfig {
public abstract float Weight { get; init; }

View File

@@ -1,8 +1,9 @@
namespace SpecialRoundAPI;
namespace SpecialRoundAPI.Configs;
public record SpeedRoundConfig : SpecialRoundConfig {
public override float Weight { get; init; } = 0.4f;
public override float Weight { get; init; } = 1;
public TimeSpan InitialSeconds { get; init; } = TimeSpan.FromSeconds(60);
public TimeSpan SecondsPerKill { get; init; } = TimeSpan.FromSeconds(10);
public TimeSpan InitialSeconds { get; init; } = TimeSpan.FromSeconds(40);
public TimeSpan SecondsPerKill { get; init; } = TimeSpan.FromSeconds(8);
public TimeSpan MaxTimeEver { get; init; } = TimeSpan.FromSeconds(90);
}

View File

@@ -0,0 +1,5 @@
namespace SpecialRoundAPI.Configs;
public record SuppressedRoundConfig : SpecialRoundConfig {
public override float Weight { get; init; } = 0.75f;
}

View File

@@ -0,0 +1,5 @@
namespace SpecialRoundAPI.Configs;
public record VanillaRoundConfig : SpecialRoundConfig {
public override float Weight { get; init; } = 0.5f;
}

View File

@@ -2,9 +2,9 @@
public interface ISpecialRoundStarter {
/// <summary>
/// Attempts to start the given special round.
/// Will bypass most checks, but may still return null if starting the round
/// is not possible.
/// Attempts to start the given special round.
/// Will bypass most checks, but may still return null if starting the round
/// is not possible.
/// </summary>
/// <param name="round"></param>
/// <returns></returns>

View File

@@ -7,9 +7,9 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Locale\Locale.csproj" />
<ProjectReference Include="..\..\TTT\API\API.csproj" />
<ProjectReference Include="..\..\TTT\Game\Game.csproj" />
<ProjectReference Include="..\..\Locale\Locale.csproj"/>
<ProjectReference Include="..\..\TTT\API\API.csproj"/>
<ProjectReference Include="..\..\TTT\Game\Game.csproj"/>
</ItemGroup>
</Project>

View File

@@ -1,5 +0,0 @@
namespace SpecialRoundAPI;
public record VanillaRoundConfig : SpecialRoundConfig {
public override float Weight { get; init; } = 0.3f;
}

View File

@@ -18,6 +18,6 @@ public interface IActionLogger {
void PrintLogs();
void PrintLogs(IOnlinePlayer? player);
string[] MakeLogs();
}

View File

@@ -1,3 +1,3 @@
namespace TTT.API.Player;
public interface IMuted : ISet<string> { }

View File

@@ -7,7 +7,7 @@ public interface IPlayer : IEquatable<IPlayer> {
/// </summary>
string Id { get; }
string Name { get; }
string Name { get; set; }
bool IEquatable<IPlayer>.Equals(IPlayer? other) {
if (other is null) return false;

View File

@@ -0,0 +1,5 @@
namespace TTT.CS2.API.Items;
public interface ITripwireActivator {
public void ActivateTripwire(TripwireInstance tripwire);
}

View File

@@ -0,0 +1,7 @@
using TTT.CS2.Items.Tripwire;
namespace TTT.CS2.API.Items;
public interface ITripwireTracker {
public List<TripwireInstance> ActiveTripwires { get; }
}

View File

@@ -0,0 +1,8 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using TTT.API.Player;
namespace TTT.CS2.API.Items;
public record TripwireInstance(IOnlinePlayer owner, CEnvBeam Beam,
CDynamicProp TripwireProp, Vector StartPos, Vector EndPos);

View File

@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\SpecialRoundAPI\SpecialRoundAPI\SpecialRoundAPI.csproj" />
<ProjectReference Include="..\..\SpecialRoundAPI\SpecialRoundAPI\SpecialRoundAPI.csproj"/>
<ProjectReference Include="..\API\API.csproj"/>
<ProjectReference Include="..\Game\Game.csproj"/>
<ProjectReference Include="..\Karma\Karma.csproj"/>

View File

@@ -1,6 +1,6 @@
using System.Runtime.CompilerServices;
using CounterStrikeSharp.API.Core;
using Microsoft.Extensions.DependencyInjection;
using ShopAPI;
using ShopAPI.Configs;
using ShopAPI.Configs.Detective;
using ShopAPI.Configs.Traitor;
@@ -64,25 +64,28 @@ public static class CS2ServiceCollection {
collection
.AddModBehavior<IStorage<OneHitKnifeConfig>, CS2OneHitKnifeConfig>();
collection.AddModBehavior<IStorage<SilentAWPConfig>, CS2SilentAWPConfig>();
collection
.AddModBehavior<IStorage<HealthshotConfig>, CS2HealthshotConfig>();
collection.AddModBehavior<IStorage<TripwireConfig>, CS2TripwireConfig>();
// TTT - CS2 Specific optionals
collection.AddScoped<ITextSpawner, TextSpawner>();
// GameHandlers
collection.AddModBehavior<BodySpawner>();
collection.AddModBehavior<BombPlantSuppressor>();
collection.AddModBehavior<BuyMenuHandler>();
collection.AddModBehavior<CombatHandler>();
collection.AddModBehavior<DamageCanceler>();
collection.AddModBehavior<MapChangeCausesEndListener>();
collection.AddModBehavior<MapZoneRemover>();
collection.AddModBehavior<NameUpdater>();
collection.AddModBehavior<PlayerConnectionsHandler>();
collection.AddModBehavior<PlayerMuter>();
collection.AddModBehavior<PropMover>();
collection.AddModBehavior<RoundStart_GameStartHandler>();
collection.AddModBehavior<BombPlantSuppressor>();
collection.AddModBehavior<MapZoneRemover>();
collection.AddModBehavior<BuyMenuHandler>();
collection.AddModBehavior<TeamChangeHandler>();
collection.AddModBehavior<TraitorChatHandler>();
collection.AddModBehavior<PlayerMuter>();
collection.AddModBehavior<MapChangeCausesEndListener>();
// collection.AddModBehavior<EntityTargetHandlers>();
// Damage Cancelers
collection.AddModBehavior<OutOfRoundCanceler>();
@@ -92,13 +95,14 @@ public static class CS2ServiceCollection {
collection.AddModBehavior<AfkTimerListener>();
collection.AddModBehavior<BodyPickupListener>();
collection.AddModBehavior<IBodyTracker, BodyTracker>();
collection.AddModBehavior<KarmaBanner>();
collection.AddModBehavior<KarmaSyncer>();
collection.AddModBehavior<LateSpawnListener>();
collection.AddModBehavior<MapHookListener>();
collection.AddModBehavior<PlayerStatsTracker>();
collection.AddModBehavior<RoundTimerListener>();
collection.AddModBehavior<ScreenColorApplier>();
collection.AddModBehavior<KarmaBanner>();
collection.AddModBehavior<KarmaSyncer>();
collection.AddModBehavior<MapHookListener>();
collection.AddModBehavior<WardenTagAssigner>();
// Commands
collection.AddModBehavior<TestCommand>();

View File

@@ -6,7 +6,6 @@ using TTT.API;
using TTT.API.Command;
using TTT.API.Messages;
using TTT.API.Player;
using TTT.Game;
using TTT.Game.Commands;
using TTT.Game.lang;

View File

@@ -41,15 +41,15 @@ public class PlayerPingShopAlias(IServiceProvider provider) : IPluginModule {
private void onButton(CCSPlayerController? player, int index) {
if (player == null) return;
if (converter.GetPlayer(player) is not IOnlinePlayer gamePlayer) return;
if (converter.GetPlayer(player) is not IOnlinePlayer apiPlayer) return;
var lastUpdated = itemSorter.GetLastUpdate(gamePlayer);
var lastUpdated = itemSorter.GetLastUpdate(apiPlayer);
if (lastUpdated == null
|| DateTime.Now - lastUpdated > TimeSpan.FromSeconds(20))
return;
var cmdInfo = new CS2CommandInfo(provider, gamePlayer, 0, "css_shop", "buy",
(index - 1).ToString());
cmdInfo.CallingContext = CommandCallingContext.Chat;
var cmdInfo = new CS2CommandInfo(provider, apiPlayer, 0, "css_shop", "buy",
(index - 1).ToString()) { CallingContext = CommandCallingContext.Chat };
provider.GetRequiredService<ICommandManager>().ProcessCommand(cmdInfo);
itemSorter.InvalidateOrder(apiPlayer);
}
}

View File

@@ -48,7 +48,6 @@ public class GiveItemCommand(IServiceProvider provider) : ICommand {
var purchaseEv = new PlayerPurchaseItemEvent(target, item);
provider.GetRequiredService<IEventBus>().Dispatch(purchaseEv);
if (purchaseEv.IsCanceled) return;
shop.GiveItem(target, item);
info.ReplySync($"Gave item '{item.Name}' to {target.Name}.");

View File

@@ -9,17 +9,12 @@ namespace TTT.CS2.Command.Test;
public class ReloadModuleCommand(IServiceProvider provider)
: ICommand, IPluginModule {
private BasePlugin? plugin;
public void Dispose() { }
public void Start() { }
private BasePlugin? plugin;
public string Id => "reload";
public void Start(BasePlugin? plugin) {
if (plugin == null) return;
this.plugin = plugin;
}
public string[] Usage => ["<module>"];
public Task<CommandResult>
@@ -59,4 +54,9 @@ public class ReloadModuleCommand(IServiceProvider provider)
return Task.FromResult(CommandResult.SUCCESS);
}
public void Start(BasePlugin? plugin) {
if (plugin == null) return;
this.plugin = plugin;
}
}

View File

@@ -1,13 +1,8 @@
using System.Numerics;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory;
using Microsoft.Extensions.DependencyInjection;
using TTT.API.Command;
using TTT.API.Player;
using TTT.CS2.Extensions;
using TTT.CS2.Utils;
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
namespace TTT.CS2.Command.Test;

View File

@@ -11,6 +11,8 @@ public class SpecCommand(IServiceProvider provider) : ICommand {
public void Dispose() { }
public void Start() { }
public string Id => "spec";
public Task<CommandResult>
Execute(IOnlinePlayer? executor, ICommandInfo info) {
var target = executor;

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using CounterStrikeSharp.API;
using Microsoft.Extensions.DependencyInjection;
using SpecialRoundAPI;
using TTT.API;
using TTT.API.Command;
@@ -25,7 +26,7 @@ public class SpecialRoundCommand(IServiceProvider provider) : ICommand {
var rounds = provider.GetServices<ITerrorModule>()
.OfType<AbstractSpecialRound>()
.ToDictionary(r => r.GetType().Name.ToLower(), r => r);
.ToDictionary(r => r.Name.ToLower(), r => r);
var roundName = info.Args[1].ToLower();
if (!rounds.TryGetValue(roundName, out var round)) {
@@ -34,8 +35,10 @@ public class SpecialRoundCommand(IServiceProvider provider) : ICommand {
return Task.FromResult(CommandResult.INVALID_ARGS);
}
tracker.TryStartSpecialRound(round);
info.ReplySync($"Started special round '{roundName}'.");
Server.NextWorldUpdate(() => {
tracker.TryStartSpecialRound(round);
info.ReplySync($"Started special round '{roundName}'.");
});
return Task.FromResult(CommandResult.SUCCESS);
}
}

View File

@@ -63,21 +63,19 @@ public class CS2GameConfig : IStorage<TTTConfig>, IPluginModule {
public static readonly FakeConVar<string> CV_TRAITOR_WEAPONS = new(
"css_ttt_roleweapons_traitor",
"Weapons available to traitors at start of round",
"weapon_knife,weapon_glock", ConVarFlags.FCVAR_NONE,
new ItemValidator(allowMultiple: true));
"Weapons available to traitors at start of round", "",
ConVarFlags.FCVAR_NONE, new ItemValidator(allowMultiple: true));
public static readonly FakeConVar<string> CV_DETECTIVE_WEAPONS = new(
"css_ttt_roleweapons_detective",
"Weapons available to detectives at start of round",
"weapon_knife,weapon_taser,weapon_m4a1,weapon_revolver",
ConVarFlags.FCVAR_NONE, new ItemValidator(allowMultiple: true));
"weapon_taser,weapon_m4a1_silencer,weapon_revolver", ConVarFlags.FCVAR_NONE,
new ItemValidator(allowMultiple: true));
public static readonly FakeConVar<string> CV_INNOCENT_WEAPONS = new(
"css_ttt_roleweapons_innocent",
"Weapons available to innocents at start of round",
"weapon_knife,weapon_glock", ConVarFlags.FCVAR_NONE,
new ItemValidator(allowMultiple: true));
"Weapons available to innocents at start of round", "",
ConVarFlags.FCVAR_NONE, new ItemValidator(allowMultiple: true));
public static readonly FakeConVar<int> CV_TIME_BETWEEN_ROUNDS = new(
"css_ttt_time_between_rounds", "Time to wait between rounds in seconds", 1,

View File

@@ -74,13 +74,13 @@ public class CS2KarmaConfig : IStorage<KarmaConfig>, IPluginModule {
public static readonly FakeConVar<int> CV_KARMA_PER_ROUND = new(
"css_ttt_karma_per_round",
"Amount of karma a player will gain at the end of each round", 2,
"Amount of karma a player will gain at the end of each round", 1,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 50));
public static readonly FakeConVar<int> CV_KARMA_PER_ROUND_WIN = new(
"css_ttt_karma_per_round_win",
"Amount of karma a player will gain at the end of each round if their team won",
4, ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 50));
1, ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 50));
public void Dispose() { }

View File

@@ -11,7 +11,7 @@ namespace TTT.CS2.Configs.ShopItems;
public class CS2BodyPaintConfig : IStorage<BodyPaintConfig>, IPluginModule {
public static readonly FakeConVar<int> CV_PRICE = new(
"css_ttt_shop_bodypaint_price", "Price of the Body Paint item", 40,
"css_ttt_shop_bodypaint_price", "Price of the Body Paint item", 30,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 10000));
public static readonly FakeConVar<int> CV_MAX_USES = new(
@@ -22,7 +22,7 @@ public class CS2BodyPaintConfig : IStorage<BodyPaintConfig>, IPluginModule {
public static readonly FakeConVar<string> CV_COLOR = new(
"css_ttt_shop_bodypaint_color",
"Color to apply to the player's body (HTML hex or known color name)",
"GreenYellow", ConVarFlags.FCVAR_NONE);
"GreenYellow");
public void Dispose() { }

View File

@@ -16,7 +16,7 @@ public class CS2CamoConfig : IStorage<CamoConfig>, IPluginModule {
public static readonly FakeConVar<float> CV_CAMO_VISIBILITY = new(
"css_ttt_shop_camo_visibility",
"Player visibility multiplier while camouflaged (0 = invisible, 1 = fully visible)",
0.6f, ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 1f));
0.5f, ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 1f));
public void Dispose() { }

View File

@@ -12,7 +12,7 @@ public class CS2ClusterGrenadeConfig : IStorage<ClusterGrenadeConfig>,
IPluginModule {
public static readonly FakeConVar<int> CV_PRICE = new(
"css_ttt_shop_clustergrenade_price",
"Price of the Cluster Grenade item (Traitor)", 90, ConVarFlags.FCVAR_NONE,
"Price of the Cluster Grenade item (Traitor)", 100, ConVarFlags.FCVAR_NONE,
new RangeValidator<int>(0, 10000));
public static readonly FakeConVar<int> CV_GRENADE_COUNT = new(
@@ -22,8 +22,7 @@ public class CS2ClusterGrenadeConfig : IStorage<ClusterGrenadeConfig>,
public static readonly FakeConVar<string> CV_WEAPON_ID = new(
"css_ttt_shop_clustergrenade_weapon",
"Weapon entity ID used for the Cluster Grenade", "weapon_hegrenade",
ConVarFlags.FCVAR_NONE);
"Weapon entity ID used for the Cluster Grenade", "weapon_hegrenade");
public static readonly FakeConVar<float> CV_UP_FORCE = new(
"css_ttt_shop_clustergrenade_up_force",

View File

@@ -17,8 +17,7 @@ public class CS2HealthStationConfig : IStorage<HealthStationConfig>,
public static readonly FakeConVar<string> CV_USE_SOUND = new(
"css_ttt_shop_healthstation_use_sound",
"Sound played when using the Health Station", "sounds/buttons/blip1",
ConVarFlags.FCVAR_NONE);
"Sound played when using the Health Station", "sounds/buttons/blip1");
public static readonly FakeConVar<int> CV_HEALTH_INCREMENTS = new(
"css_ttt_shop_healthstation_increments",

View File

@@ -0,0 +1,43 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Cvars.Validators;
using ShopAPI;
using TTT.API;
using TTT.API.Storage;
namespace TTT.CS2.Configs.ShopItems;
public class CS2HealthshotConfig : IStorage<HealthshotConfig>, IPluginModule {
public static readonly FakeConVar<int> CV_PRICE = new(
"css_ttt_shop_healthshot_price", "Price of the Healthshot item", 40,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 10000));
public static readonly FakeConVar<int> CV_MAX_PURCHASES = new(
"css_ttt_shop_healthshot_max_purchases",
"Maximum number of times a player can purchase the Healthshot per round", 2,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(1, 100));
public static readonly FakeConVar<string> CV_WEAPON = new(
"css_ttt_shop_healthshot_weapon", "Weapon entity name for the Healthshot",
"weapon_healthshot");
public void Dispose() { }
public void Start() { }
public void Start(BasePlugin? plugin) {
ArgumentNullException.ThrowIfNull(plugin, nameof(plugin));
plugin.RegisterFakeConVars(this);
}
public Task<HealthshotConfig?> Load() {
var cfg = new HealthshotConfig {
Price = CV_PRICE.Value,
MaxPurchases = CV_MAX_PURCHASES.Value,
Weapon = CV_WEAPON.Value
};
return Task.FromResult<HealthshotConfig?>(cfg);
}
}

View File

@@ -16,8 +16,7 @@ public class CS2OneHitKnifeConfig : IStorage<OneHitKnifeConfig>, IPluginModule {
public static readonly FakeConVar<bool> CV_FRIENDLY_FIRE = new(
"css_ttt_shop_onehitknife_friendly_fire",
"Whether the One-Hit Knife can damage teammates", false,
ConVarFlags.FCVAR_NONE);
"Whether the One-Hit Knife can damage teammates");
public void Dispose() { }

View File

@@ -12,7 +12,7 @@ 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", 125,
"css_ttt_shop_onedeagle_price", "Price of the One-Shot Deagle item", 130,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 10000));
public static readonly FakeConVar<bool> CV_FRIENDLY_FIRE = new(

View File

@@ -19,7 +19,7 @@ public class CS2SilentAWPConfig : IStorage<SilentAWPConfig>, IPluginModule {
public static readonly FakeConVar<string> CV_WEAPON_ID = new(
"css_ttt_shop_silentawp_weapon", "Weapon entity ID for the Silent AWP",
"weapon_awp", ConVarFlags.FCVAR_NONE);
"weapon_awp");
public static readonly FakeConVar<int> CV_RESERVE_AMMO = new(
"css_ttt_shop_silentawp_reserve_ammo",

View File

@@ -10,7 +10,7 @@ namespace TTT.CS2.Configs.ShopItems;
public class CS2StickersConfig : IStorage<StickersConfig>, IPluginModule {
public static readonly FakeConVar<int> CV_PRICE = new(
"css_ttt_shop_stickers_price", "Price of the Stickers item (Detective)", 25,
"css_ttt_shop_stickers_price", "Price of the Stickers item (Detective)", 45,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 10000));
public void Dispose() { }

View File

@@ -0,0 +1,104 @@
using System.Drawing;
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;
namespace TTT.CS2.Configs.ShopItems;
public class CS2TripwireConfig : IStorage<TripwireConfig>, IPluginModule {
public static readonly FakeConVar<int> CV_PRICE = new(
"css_ttt_shop_tripwire_price", "Price of the Tripwire item (Traitor)", 45,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 10000));
public static readonly FakeConVar<int> CV_EXPLOSION_POWER = new(
"css_ttt_shop_tripwire_explosion_power",
"Explosion power of the Tripwire in damage units", 1000,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(1, 10000));
public static readonly FakeConVar<float> CV_FALLOFF_DELAY = new(
"css_ttt_shop_tripwire_falloff_delay",
"Damage falloff of tripwire explosion, higher = quicker falloff", 0.015f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 1f));
public static readonly FakeConVar<float> CV_FF_MULTIPLIER = new(
"css_ttt_shop_tripwire_friendlyfire_multiplier",
"Damage multiplier applied to friendly fire from Tripwire", 0.5f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 1f));
public static readonly FakeConVar<float> CV_OOLOS_MULTIPLIER = new(
"css_ttt_shop_tripwire_outoflineofsight_multiplier",
"Damage multiplier for players out of line of sight", 0.3f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 1f));
public static readonly FakeConVar<bool> CV_FF_TRIGGERS = new(
"css_ttt_shop_tripwire_friendlyfire_triggers",
"Whether Tripwires can be triggered by teammates", true);
public static readonly FakeConVar<float> CV_MAX_DISTANCE_SQUARED = new(
"css_ttt_shop_tripwire_max_distance_squared",
"Maximum placement distance squared for Tripwire", 400f * 400f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 1000000f));
public static readonly FakeConVar<float> CV_INITIATION_TIME = new(
"css_ttt_shop_tripwire_initiation_time",
"Seconds before Tripwire becomes active", 2f, ConVarFlags.FCVAR_NONE,
new RangeValidator<float>(0f, 10f));
public static readonly FakeConVar<float> CV_SIZE_SQUARED = new(
"css_ttt_shop_tripwire_size_squared",
"Size of tripwire for the purposes of bullet-detection", 500f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(1f, 100000f));
public static readonly FakeConVar<int> CV_COLOR_R = new(
"css_ttt_shop_tripwire_color_r", "Tripwire color red channel (0255)", 255,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 255));
public static readonly FakeConVar<int> CV_COLOR_G = new(
"css_ttt_shop_tripwire_color_g", "Tripwire color green channel (0255)", 0,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 255));
public static readonly FakeConVar<int> CV_COLOR_B = new(
"css_ttt_shop_tripwire_color_b", "Tripwire color blue channel (0255)", 0,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 255));
public static readonly FakeConVar<int> CV_COLOR_A = new(
"css_ttt_shop_tripwire_color_a", "Tripwire color alpha (0255)", 64,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 255));
public static readonly FakeConVar<float> CV_THICKNESS = new(
"css_ttt_shop_tripwire_thickness", "Visual thickness of the Tripwire beam",
0.5f, ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0.01f, 5f));
public void Dispose() { }
public void Start() { }
public void Start(BasePlugin? plugin) {
ArgumentNullException.ThrowIfNull(plugin, nameof(plugin));
plugin.RegisterFakeConVars(this);
}
public Task<TripwireConfig?> Load() {
var cfg = new TripwireConfig {
Price = CV_PRICE.Value,
ExplosionPower = CV_EXPLOSION_POWER.Value,
FalloffDelay = CV_FALLOFF_DELAY.Value,
FriendlyFireMultiplier = CV_FF_MULTIPLIER.Value,
OutOfLineOfSightMultiplier = CV_OOLOS_MULTIPLIER.Value,
FriendlyFireTriggers = CV_FF_TRIGGERS.Value,
MaxPlacementDistanceSquared = CV_MAX_DISTANCE_SQUARED.Value,
TripwireInitiationTime = TimeSpan.FromSeconds(CV_INITIATION_TIME.Value),
TripwireSizeSquared = CV_SIZE_SQUARED.Value,
TripwireColor =
Color.FromArgb(CV_COLOR_A.Value, CV_COLOR_R.Value, CV_COLOR_G.Value,
CV_COLOR_B.Value),
TripwireThickness = CV_THICKNESS.Value
};
return Task.FromResult<TripwireConfig?>(cfg);
}
}

View File

@@ -119,8 +119,11 @@ public static class PlayerExtensions {
SELF(player.Slot), 0.2f, 1);
}
private static RecipientFilter SELF(int slot) => new(slot);
private static RecipientFilter SELF(int slot) {
return new RecipientFilter(slot);
}
private static RecipientFilter OTHERS(int slot)
=> new(ulong.MaxValue & ~(1ul << slot));
private static RecipientFilter OTHERS(int slot) {
return new RecipientFilter(ulong.MaxValue & ~(1ul << slot));
}
}

View File

@@ -97,4 +97,11 @@ public static class VectorExtensions {
public static Vector toVector(this Vector3 vec) {
return new Vector(vec.X, vec.Y, vec.Z);
}
public static QAngle toAngle(this Vector vec) {
var pitch = (float)(Math.Atan2(-vec.Z,
Math.Sqrt(vec.X * vec.X + vec.Y * vec.Y)) * (180.0 / Math.PI));
var yaw = (float)(Math.Atan2(vec.Y, vec.X) * (180.0 / Math.PI));
return new QAngle(pitch, yaw, 0);
}
}

View File

@@ -1,5 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Commands;
using JetBrains.Annotations;
@@ -32,7 +31,7 @@ public class BuyMenuHandler(IServiceProvider provider) : IPluginModule {
{ "weapon_mp5sd", "M4A1" },
{ "weapon_awp", "AWP" },
{ "weapon_hegrenade", "Cluster" },
{ "weapon_decoy", "Teleport Decoy" },
{ "weapon_decoy", "Teleport Decoy" }
};
public void Dispose() { }

View File

@@ -7,8 +7,10 @@ using TTT.API;
using TTT.API.Events;
using TTT.API.Game;
using TTT.API.Player;
using TTT.API.Role;
using TTT.CS2.API;
using TTT.Game.Events.Player;
using TTT.Game.Roles;
namespace TTT.CS2.GameHandlers;
@@ -21,6 +23,9 @@ public class CombatHandler(IServiceProvider provider) : IPluginModule {
private readonly IGameManager games =
provider.GetRequiredService<IGameManager>();
private readonly IRoleAssigner roles =
provider.GetRequiredService<IRoleAssigner>();
private readonly IAliveSpoofer spoofer =
provider.GetRequiredService<IAliveSpoofer>();
@@ -45,13 +50,36 @@ public class CombatHandler(IServiceProvider provider) : IPluginModule {
if (games.ActiveGame is not { State: State.IN_PROGRESS })
return HookResult.Continue;
if (ev.Attacker != null) ev.FireEventToClient(ev.Attacker);
if (ev.Attacker != null) {
ev.FireEventToClient(ev.Attacker);
var apiPlayer = converter.GetPlayer(ev.Attacker);
var role = roles.GetRoles(apiPlayer);
if (role.Any(r => r is TraitorRole))
foreach (var p in Utilities.GetPlayers()) {
var apiP = converter.GetPlayer(p);
if (apiP.Id == apiPlayer.Id) continue;
var r = roles.GetRoles(converter.GetPlayer(p));
if (role.Intersect(r).Any()) ev.FireEventToClient(p);
}
}
info.DontBroadcast = true;
spoofer.SpoofAlive(player);
Server.NextWorldUpdateAsync(() => bus.Dispatch(deathEvent));
return HookResult.Continue;
}
[UsedImplicitly]
[GameEventHandler(HookMode.Pre)]
public HookResult OnPlayerDamage(EventPlayerHurt ev, GameEventInfo info) {
var player = ev.Userid;
if (player == null) return HookResult.Continue;
hideAndTrackStats(ev);
return HookResult.Continue;
}
private void hideAndTrackStats(EventPlayerDeath ev,
CCSPlayerController player) {
var victimStats = player.ActionTrackingServices?.MatchStats;
@@ -89,6 +117,16 @@ public class CombatHandler(IServiceProvider provider) : IPluginModule {
"m_pActionTrackingServices");
}
private void hideAndTrackStats(EventPlayerHurt ev) {
var attackerStats = ev.Attacker?.ActionTrackingServices?.MatchStats;
if (attackerStats == null) return;
if (ev.Attacker == null) return;
attackerStats.Damage -= ev.DmgHealth;
Utilities.SetStateChanged(ev.Attacker, "CCSPlayerController",
"m_pActionTrackingServices");
}
[UsedImplicitly]
[GameEventHandler]
public HookResult OnPlayerHurt(EventPlayerHurt ev, GameEventInfo _) {

View File

@@ -0,0 +1,22 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using TTT.API.Events;
using TTT.API.Player;
using TTT.Game.Events.Game;
using TTT.Game.Listeners;
namespace TTT.CS2.GameHandlers;
public class NameUpdater(IServiceProvider provider) : BaseListener(provider) {
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
[UsedImplicitly]
[EventHandler]
public void OnGameInit(GameInitEvent ev) {
foreach (var player in Utilities.GetPlayers())
converter.GetPlayer(player).Name = player.PlayerName;
}
}

View File

@@ -18,15 +18,15 @@ public class PlayerMuter(IServiceProvider provider) : IPluginModule {
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly IGameManager game =
provider.GetRequiredService<IGameManager>();
private readonly IMsgLocalizer locale =
provider.GetRequiredService<IMsgLocalizer>();
private readonly IMessenger messenger =
provider.GetRequiredService<IMessenger>();
private readonly IGameManager game =
provider.GetRequiredService<IGameManager>();
public void Dispose() { }
public void Start() { }
@@ -66,8 +66,6 @@ public class PlayerMuter(IServiceProvider provider) : IPluginModule {
public void OnGameEvent(GameStateUpdateEvent ev) {
if (ev.NewState != State.FINISHED) return;
foreach (var p in Utilities.GetPlayers()) {
p.VoiceFlags &= ~VoiceFlags.Muted;
}
foreach (var p in Utilities.GetPlayers()) p.VoiceFlags &= ~VoiceFlags.Muted;
}
}

View File

@@ -51,7 +51,9 @@ public class PropMover(IServiceProvider provider) : IPluginModule {
return;
}
if (!pressed.HasFlag(PlayerButtons.Use)) return;
if (!pressed.HasFlag(PlayerButtons.Use)
&& !pressed.HasFlag(PlayerButtons.Inspect))
return;
onStartUse(player);
}

View File

@@ -6,7 +6,6 @@ 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;

View File

@@ -12,18 +12,18 @@ namespace TTT.CS2.GameHandlers;
public class RoundStart_GameStartHandler(IServiceProvider provider)
: IPluginModule {
private 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>();
private TTTConfig config
=> provider.GetService<IStorage<TTTConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new TTTConfig();
public void Dispose() { }
public void Start() { }

View File

@@ -11,12 +11,14 @@ using TTT.API.Game;
using TTT.API.Player;
using TTT.CS2.API;
using TTT.CS2.Extensions;
using TTT.Game;
using TTT.Game.Events.Player;
namespace TTT.CS2.GameHandlers;
public class TeamChangeHandler(IServiceProvider provider) : IPluginModule {
private readonly IBodyTracker bodies =
provider.GetRequiredService<IBodyTracker>();
private readonly IEventBus bus = provider.GetRequiredService<IEventBus>();
private readonly IPlayerConverter<CCSPlayerController> converter =
@@ -25,9 +27,6 @@ public class TeamChangeHandler(IServiceProvider provider) : IPluginModule {
private readonly IGameManager games =
provider.GetRequiredService<IGameManager>();
private readonly IBodyTracker bodies =
provider.GetRequiredService<IBodyTracker>();
public void Dispose() { }
public void Start() { }

View File

@@ -29,11 +29,11 @@ public class TraitorChatHandler(IServiceProvider provider) : IPluginModule {
private readonly IMessenger messenger =
provider.GetRequiredService<IMessenger>();
private readonly IMuted? mutedPlayers = provider.GetService<IMuted>();
private readonly IRoleAssigner roles =
provider.GetRequiredService<IRoleAssigner>();
private readonly IMuted? mutedPlayers = provider.GetService<IMuted>();
private IActain? maulService;
public void Start(BasePlugin? plugin) {
@@ -79,7 +79,7 @@ public class TraitorChatHandler(IServiceProvider provider) : IPluginModule {
private HookResult onSay(CCSPlayerController? player,
CommandInfo commandInfo) {
if (mutedPlayers != null
if (mutedPlayers != null && player != null && player.GetHealth() > 0
&& mutedPlayers.Contains(player?.SteamID.ToString() ?? ""))
return HookResult.Handled;

View File

@@ -16,15 +16,15 @@ public static class ArmorItemServicesCollection {
}
public class ArmorItem(IServiceProvider provider) : BaseItem(provider) {
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private ArmorConfig config
=> Provider.GetService<IStorage<ArmorConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new ArmorConfig();
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
public override string Name => Locale[ArmorMsgs.SHOP_ITEM_ARMOR];
public override string Description => Locale[ArmorMsgs.SHOP_ITEM_ARMOR_DESC];
public override ShopItemConfig Config => config;

View File

@@ -17,16 +17,16 @@ public class BodyPaintListener(IServiceProvider provider)
private readonly IBodyTracker bodies =
provider.GetRequiredService<IBodyTracker>();
private readonly IShop shop = provider.GetRequiredService<IShop>();
private readonly Dictionary<IPlayer, int> uses = new();
private BodyPaintConfig config
=> Provider.GetService<IStorage<BodyPaintConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new BodyPaintConfig();
private readonly IShop shop = provider.GetRequiredService<IShop>();
private readonly Dictionary<IPlayer, int> uses = new();
[UsedImplicitly]
[EventHandler(Priority = Priority.HIGH)]
public void BodyIdentify(BodyIdentifyEvent ev) {

View File

@@ -18,15 +18,15 @@ public static class CamoServiceCollection {
}
public class CamouflageItem(IServiceProvider provider) : BaseItem(provider) {
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private 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;

View File

@@ -1,9 +1,6 @@
using System.Reactive.Concurrency;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
@@ -11,23 +8,25 @@ using ShopAPI;
using ShopAPI.Configs.Traitor;
using TTT.API;
using TTT.API.Player;
using TTT.API.Role;
using TTT.API.Storage;
using TTT.CS2.Utils;
namespace TTT.CS2.Items.ClusterGrenade;
public class ClusterGrenadeListener(IServiceProvider provider) : IPluginModule {
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly IShop shop = provider.GetRequiredService<IShop>();
private ClusterGrenadeConfig config
=> provider.GetService<IStorage<ClusterGrenadeConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new ClusterGrenadeConfig();
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly IShop shop = provider.GetRequiredService<IShop>();
public void Dispose() { }
public void Start() { }
[UsedImplicitly]
[GameEventHandler]
@@ -60,7 +59,4 @@ public class ClusterGrenadeListener(IServiceProvider provider) : IPluginModule {
return HookResult.Continue;
}
public void Dispose() { }
public void Start() { }
}

View File

@@ -1,5 +1,3 @@
using System.Linq;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.Utils;
@@ -10,7 +8,6 @@ using ShopAPI.Configs;
using ShopAPI.Configs.Traitor;
using TTT.API;
using TTT.API.Events;
using TTT.API.Extensions;
using TTT.API.Game;
using TTT.API.Player;
using TTT.API.Role;
@@ -18,17 +15,15 @@ using TTT.API.Storage;
using TTT.CS2.Extensions;
using TTT.CS2.Utils;
using TTT.Game.Events.Game;
using TTT.Game.Roles;
namespace TTT.CS2.Items.Compass;
/// <summary>
/// Base compass that renders a heading toward the nearest target returned by GetTargets.
/// Child classes decide which targets to expose and who owns the item.
/// Base compass that renders a heading toward the nearest target returned by GetTargets.
/// Child classes decide which targets to expose and who owns the item.
/// </summary>
public abstract class AbstractCompassItem<TRole> : RoleRestrictedItem<TRole>,
IListener, IPluginModule where TRole : class, IRole {
protected CompassConfig _Config { get; }
protected readonly IPlayerConverter<CCSPlayerController> Converter;
protected readonly ISet<IPlayer> Owners = new HashSet<IPlayer>();
@@ -42,6 +37,8 @@ public abstract class AbstractCompassItem<TRole> : RoleRestrictedItem<TRole>,
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
}
protected CompassConfig _Config { get; }
public override ShopItemConfig Config => _Config;
public void Start(BasePlugin? plugin) {
@@ -56,14 +53,14 @@ public abstract class AbstractCompassItem<TRole> : RoleRestrictedItem<TRole>,
}
/// <summary>
/// Return world positions to point at for this player.
/// Return world positions to point at for this player.
/// </summary>
protected abstract IList<Vector> GetTargets(IOnlinePlayer requester);
abstract protected IList<Vector> GetTargets(IOnlinePlayer requester);
/// <summary>
/// Whether this player currently owns/has this compass effect.
/// Whether this player currently owns/has this compass effect.
/// </summary>
protected abstract bool OwnsItem(IOnlinePlayer player);
abstract protected bool OwnsItem(IOnlinePlayer player);
public override void OnPurchase(IOnlinePlayer player) { Owners.Add(player); }

View File

@@ -27,11 +27,11 @@ public class BodyCompassItem(IServiceProvider provider)
=> Locale[CompassMsgs.SHOP_ITEM_COMPASS_BODY_DESC];
/// <summary>
/// For innocents: point to nearest traitor.
/// For traitors: point to nearest non-traitor (ally list in original code).
/// Returns target world positions as vectors.
/// For innocents: point to nearest traitor.
/// For traitors: point to nearest non-traitor (ally list in original code).
/// Returns target world positions as vectors.
/// </summary>
protected override IList<Vector> GetTargets(IOnlinePlayer requester) {
override protected IList<Vector> GetTargets(IOnlinePlayer requester) {
if (Games.ActiveGame is not { State: State.IN_PROGRESS or State.FINISHED })
return Array.Empty<Vector>();

View File

@@ -23,11 +23,11 @@ public class InnoCompassItem(IServiceProvider provider)
=> Locale[CompassMsgs.SHOP_ITEM_COMPASS_PLAYER_DESC];
/// <summary>
/// For innocents: point to nearest traitor.
/// For traitors: point to nearest non-traitor (ally list in original code).
/// Returns target world positions as vectors.
/// For innocents: point to nearest traitor.
/// For traitors: point to nearest non-traitor (ally list in original code).
/// Returns target world positions as vectors.
/// </summary>
protected override IList<Vector> GetTargets(IOnlinePlayer requester) {
override protected IList<Vector> GetTargets(IOnlinePlayer requester) {
if (Games.ActiveGame is not { State: State.IN_PROGRESS or State.FINISHED })
return Array.Empty<Vector>();

View File

@@ -28,15 +28,15 @@ public class DnaListener(IServiceProvider provider) : BaseListener(provider) {
private readonly IBodyTracker bodies =
provider.GetRequiredService<IBodyTracker>();
private readonly Dictionary<string, DateTime> lastMessages = new();
private readonly IShop shop = provider.GetRequiredService<IShop>();
private 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)]

View File

@@ -1,6 +1,5 @@
using TTT.API.Player;
using TTT.API.Role;
using TTT.Game;
using TTT.Game.lang;
using TTT.Locale;

View File

@@ -4,7 +4,6 @@ using ShopAPI;
using ShopAPI.Configs.Traitor;
using TTT.API.Events;
using TTT.API.Game;
using TTT.API.Player;
using TTT.API.Storage;
using TTT.Game.Events.Player;
using TTT.Game.Listeners;
@@ -13,14 +12,14 @@ namespace TTT.CS2.Items.OneHitKnife;
public class OneHitKnifeListener(IServiceProvider provider)
: BaseListener(provider) {
private readonly IShop shop = provider.GetRequiredService<IShop>();
private OneHitKnifeConfig config
=> Provider.GetService<IStorage<OneHitKnifeConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new OneHitKnifeConfig();
private readonly IShop shop = provider.GetRequiredService<IShop>();
[UsedImplicitly]
[EventHandler]
public void OnDamage(PlayerDamagedEvent ev) {

View File

@@ -24,15 +24,11 @@ public class PoisonShotsListener(IServiceProvider provider)
: BaseListener(provider), IPluginModule {
private readonly IEventBus bus = provider.GetRequiredService<IEventBus>();
private PoisonShotsConfig config
=> Provider.GetService<IStorage<PoisonShotsConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new PoisonShotsConfig();
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly Dictionary<string, IPlayer> killedWithPoison = new();
private readonly Dictionary<IPlayer, int> poisonShots = new();
private readonly List<IDisposable> poisonTimers = [];
@@ -42,7 +38,11 @@ public class PoisonShotsListener(IServiceProvider provider)
private readonly IShop shop = provider.GetRequiredService<IShop>();
private readonly Dictionary<string, IPlayer> killedWithPoison = new();
private PoisonShotsConfig config
=> Provider.GetService<IStorage<PoisonShotsConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new PoisonShotsConfig();
public override void Dispose() {
base.Dispose();
@@ -156,13 +156,6 @@ public class PoisonShotsListener(IServiceProvider provider)
return 0;
}
private class PoisonEffect(IPlayer player, IPlayer shooter) {
public IPlayer Player { get; } = player;
public IPlayer Shooter { get; } = shooter;
public int Ticks { get; set; }
public int DamageGiven { get; set; }
}
[UsedImplicitly]
[EventHandler]
@@ -173,4 +166,11 @@ public class PoisonShotsListener(IServiceProvider provider)
return;
ev.Body.Killer = shooter as IOnlinePlayer;
}
private class PoisonEffect(IPlayer player, IPlayer shooter) {
public IPlayer Player { get; } = player;
public IPlayer Shooter { get; } = shooter;
public int Ticks { get; set; }
public int DamageGiven { get; set; }
}
}

View File

@@ -12,7 +12,6 @@ using TTT.API;
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.Game.Events.Body;
@@ -25,20 +24,20 @@ namespace TTT.CS2.Items.PoisonSmoke;
public class PoisonSmokeListener(IServiceProvider provider)
: BaseListener(provider), IPluginModule {
private PoisonSmokeConfig config
=> Provider.GetService<IStorage<PoisonSmokeConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new PoisonSmokeConfig();
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly ISet<string> killedWithPoison = new HashSet<string>();
private readonly List<IDisposable> poisonSmokes = [];
private readonly IShop shop = provider.GetRequiredService<IShop>();
private readonly ISet<string> killedWithPoison = new HashSet<string>();
private PoisonSmokeConfig config
=> Provider.GetService<IStorage<PoisonSmokeConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new PoisonSmokeConfig();
public override void Dispose() {
base.Dispose();
@@ -129,15 +128,6 @@ public class PoisonSmokeListener(IServiceProvider provider)
return effect.DamageGiven < config.PoisonConfig.TotalDamage;
}
private class PoisonEffect(CSmokeGrenadeProjectile projectile,
IOnlinePlayer attacker) {
public int Ticks { get; set; }
public int DamageGiven { get; set; }
public Vector Origin { get; } = projectile.AbsOrigin.Clone()!;
public CSmokeGrenadeProjectile Projectile { get; } = projectile;
public IPlayer Attacker { get; } = attacker;
}
[UsedImplicitly]
[EventHandler]
public void OnGameEnd(GameStateUpdateEvent ev) {
@@ -153,4 +143,13 @@ public class PoisonSmokeListener(IServiceProvider provider)
if (ev.Body.Killer == null || ev.Body.Killer.Id == ev.Body.OfPlayer.Id)
ev.IsCanceled = true;
}
private class PoisonEffect(CSmokeGrenadeProjectile projectile,
IOnlinePlayer attacker) {
public int Ticks { get; set; }
public int DamageGiven { get; set; }
public Vector Origin { get; } = projectile.AbsOrigin.Clone()!;
public CSmokeGrenadeProjectile Projectile { get; } = projectile;
public IPlayer Attacker { get; } = attacker;
}
}

View File

@@ -24,18 +24,18 @@ public static class SilentAWPServiceCollection {
public class SilentAWPItem(IServiceProvider provider)
: RoleRestrictedItem<TraitorRole>(provider), IPluginModule {
private SilentAWPConfig config
=> Provider.GetService<IStorage<SilentAWPConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new SilentAWPConfig();
private readonly IPlayerConverter<CCSPlayerController> playerConverter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly IDictionary<string, int> silentShots =
new Dictionary<string, int>();
private SilentAWPConfig config
=> Provider.GetService<IStorage<SilentAWPConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new SilentAWPConfig();
public override string Name => Locale[SilentAWPMsgs.SHOP_ITEM_SILENT_AWP];
public override string Description

View File

@@ -1,5 +1,5 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using ShopAPI.Configs.Traitor;
@@ -7,10 +7,8 @@ using TTT.API.Events;
using TTT.API.Extensions;
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.Events.Body;
using TTT.Game.Events.Game;
using TTT.Game.Events.Player;
@@ -38,20 +36,23 @@ public class DamageStation(IServiceProvider provider)
private readonly IPlayerFinder finder =
provider.GetRequiredService<IPlayerFinder>();
private readonly IRoleAssigner roles =
provider.GetRequiredService<IRoleAssigner>();
private readonly Dictionary<string, StationInfo> killedWithStation = new();
public override string Name => Locale[StationMsgs.SHOP_ITEM_STATION_HURT];
public override string Description
=> Locale[StationMsgs.SHOP_ITEM_STATION_HURT_DESC];
private Dictionary<string, StationInfo> killedWithStation =
new Dictionary<string, StationInfo>();
override protected void onInterval() {
var players = finder.GetOnline();
var toRemove = new List<CPhysicsPropMultiplayer>();
var playerMapping = players
.Select(p => (ApiPlayer: p, GamePlayer: converter.GetPlayer(p)))
.Where(m
=> m.GamePlayer != null
&& !Roles.GetRoles(m.ApiPlayer).Any(r => r is TraitorRole))
.ToList();
foreach (var (prop, info) in props) {
if (_Config.TotalHealthGiven != 0 && Math.Abs(info.HealthGiven)
> Math.Abs(_Config.TotalHealthGiven)) {
@@ -66,12 +67,7 @@ public class DamageStation(IServiceProvider provider)
var propPos = prop.AbsOrigin;
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 })
@@ -83,14 +79,14 @@ public class DamageStation(IServiceProvider provider)
foreach (var (player, dist, gamePlayer) in playerDists) {
var healthScale = 1.0 - dist / _Config.MaxRange;
var damageAmount =
(int)Math.Floor(_Config.HealthIncrements * healthScale);
Math.Abs((int)Math.Floor(_Config.HealthIncrements * healthScale));
var dmgEvent = new PlayerDamagedEvent(player,
info.Owner as IOnlinePlayer, damageAmount) { Weapon = $"[{Name}]" };
bus.Dispatch(dmgEvent);
damageAmount = -dmgEvent.DmgDealt;
damageAmount = dmgEvent.DmgDealt;
if (player.Health + damageAmount <= 0) {
killedWithStation[player.Id] = info;
@@ -100,16 +96,19 @@ public class DamageStation(IServiceProvider provider)
bus.Dispatch(playerDeath);
}
player.Health += damageAmount;
gamePlayer.EmitSound("Player.DamageFall", SELF(gamePlayer.Slot), 0.2f);
player.Health -= damageAmount;
info.HealthGiven += damageAmount;
gamePlayer.EmitSound("Player.DamageFall", null, 0.2f);
}
}
foreach (var prop in toRemove) props.Remove(prop);
}
private static RecipientFilter SELF(int slot) {
return new RecipientFilter(slot);
}
[UsedImplicitly]
[EventHandler]
public void OnGameEnd(GameStateUpdateEvent ev) {

View File

@@ -10,7 +10,8 @@ using TTT.Game.Roles;
namespace TTT.CS2.Items.Station;
public static class HealthStationCollection {
public static void AddHealthStationServices(this IServiceCollection collection) {
public static void
AddHealthStationServices(this IServiceCollection collection) {
collection.AddModBehavior<HealthStation>();
}
}

View File

@@ -20,10 +20,10 @@ public abstract class StationItem<T>(IServiceProvider provider,
: RoleRestrictedItem<T>(provider), IPluginModule where T : IRole {
protected readonly StationConfig _Config = config;
protected readonly IPlayerConverter<CCSPlayerController> Converter =
protected readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly long PROP_SIZE_SQUARED = 500;
private readonly long PROP_SIZE_SQUARED = 700;
protected readonly Dictionary<CPhysicsPropMultiplayer, StationInfo> props =
new();
@@ -105,8 +105,8 @@ public abstract class StationItem<T>(IServiceProvider provider,
"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,
_ when Tag.SHOTGUNS.Contains(designerWeapon) => 15,
_ when Tag.RIFLES.Contains(designerWeapon) => 35,
_ => 5
};
}
@@ -124,7 +124,7 @@ public abstract class StationItem<T>(IServiceProvider provider,
prop.SetModel("models/props/cs_office/microwave.vmdl");
prop.DispatchSpawn();
var gamePlayer = Converter.GetPlayer(player);
var gamePlayer = converter.GetPlayer(player);
if (gamePlayer == null || !gamePlayer.Pawn.IsValid
|| gamePlayer.Pawn.Value == null)
return;

View File

@@ -1,5 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Utils;
using JetBrains.Annotations;

View File

@@ -0,0 +1,47 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Utils;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using ShopAPI.Configs.Traitor;
using TTT.API;
using TTT.API.Storage;
using TTT.CS2.API.Items;
using TTT.CS2.Extensions;
namespace TTT.CS2.Items.Tripwire;
public class TripwireDamageListener(IServiceProvider provider) : IPluginModule {
public void Dispose() { }
public void Start() { }
private readonly ITripwireTracker? tripwires =
provider.GetService<ITripwireTracker>();
private readonly ITripwireActivator? tripwireActivator =
provider.GetRequiredService<ITripwireActivator>();
private TripwireConfig config
=> provider.GetService<IStorage<TripwireConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new TripwireConfig();
[UsedImplicitly]
[GameEventHandler]
public HookResult OnBulletImpact(EventBulletImpact ev, GameEventInfo info) {
if (tripwires == null) return HookResult.Continue;
var hitVec = new Vector(ev.X, ev.Y, ev.Z);
var nearest = tripwires.ActiveTripwires
.OrderBy(wire => wire.TripwireProp.AbsOrigin.DistanceSquared(hitVec))
.FirstOrDefault();
if (nearest == null) return HookResult.Continue;
var distSquared = nearest.TripwireProp.AbsOrigin.DistanceSquared(hitVec);
if (distSquared > config.TripwireSizeSquared) return HookResult.Continue;
tripwireActivator?.ActivateTripwire(nearest);
return HookResult.Continue;
}
}

View File

@@ -0,0 +1,159 @@
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Reactive.Concurrency;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using ShopAPI;
using ShopAPI.Configs;
using ShopAPI.Configs.Traitor;
using TTT.API;
using TTT.API.Events;
using TTT.API.Extensions;
using TTT.API.Game;
using TTT.API.Player;
using TTT.API.Storage;
using TTT.CS2.API.Items;
using TTT.CS2.Extensions;
using TTT.CS2.RayTrace.Class;
using TTT.CS2.RayTrace.Enum;
using TTT.CS2.RayTrace.Struct;
using TTT.Game.Events.Game;
using TTT.Game.Roles;
namespace TTT.CS2.Items.Tripwire;
public static class TripwireServiceCollection {
public static void AddTripwireServices(this IServiceCollection services) {
services.AddModBehavior<ITripwireTracker, TripwireItem>();
services.AddModBehavior<ITripwireActivator, TripwireMovementListener>();
services.AddModBehavior<TripwireDamageListener>();
}
}
public class TripwireItem(IServiceProvider provider)
: RoleRestrictedItem<TraitorRole>(provider), IPluginModule, ITripwireTracker {
private TripwireConfig config
=> Provider.GetService<IStorage<TripwireConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new TripwireConfig();
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly IScheduler scheduler =
provider.GetRequiredService<IScheduler>();
public List<TripwireInstance> ActiveTripwires { get; } = [];
public override string Name => Locale[TripwireMsgs.SHOP_ITEM_TRIPWIRE];
public override string Description
=> Locale[TripwireMsgs.SHOP_ITEM_TRIPWIRE_DESC];
public override ShopItemConfig Config => config;
public void Start(BasePlugin? plugin) {
Start();
plugin
?.RegisterListener<
CounterStrikeSharp.API.Core.Listeners.OnServerPrecacheResources>(
onPrecache);
}
private void onPrecache(ResourceManifest manifest) {
manifest.AddResource(
"models/generic/conveyor_control_panel_01/conveyor_button_02.vmdl");
}
[UsedImplicitly]
[EventHandler]
public void OnGameEvent(GameStateUpdateEvent ev) {
if (ev.NewState != State.FINISHED) return;
ActiveTripwires.Clear();
}
public override void OnPurchase(IOnlinePlayer player) {
Server.NextWorldUpdate(() => {
if (!placeTripwire(player, out var originTrace, out var endTrace,
out var tripwire))
return;
scheduler.Schedule(config.TripwireInitiationTime,
() => {
Server.NextWorldUpdate(() => {
createTripwireBeam(player, tripwire,
originTrace.Value.EndPos.toVector(),
endTrace.Value.EndPos.toVector());
});
});
});
}
private bool placeTripwire(IOnlinePlayer player,
[NotNullWhen(true)] out CGameTrace? originTrace,
[NotNullWhen(true)] out CGameTrace? endTrace,
[NotNullWhen(true)] out CDynamicProp? tripwire) {
tripwire = null;
originTrace = null;
endTrace = null;
var gamePlayer = converter.GetPlayer(player);
var playerPawn = gamePlayer?.PlayerPawn.Value;
if (gamePlayer == null || playerPawn == null) return false;
originTrace = gamePlayer.GetGameTraceByEyePosition(TraceMask.MaskSolid,
Contents.NoDraw, gamePlayer);
var origin = gamePlayer.GetEyePosition();
if (origin == null || originTrace == null) return false;
if (origin.DistanceSquared(originTrace.Value.EndPos.toVector())
> config.MaxPlacementDistanceSquared) {
Shop.AddBalance(player, config.Price, "Refund");
Messenger.Message(player, Locale[TripwireMsgs.SHOP_ITEM_TRIPWIRE_TOOFAR]);
return false;
}
var angles = originTrace.Value.Normal.toVector().toAngle();
endTrace = TraceRay.TraceShape(originTrace.Value.EndPos.toVector(), angles,
TraceMask.MaskSolid, Contents.NoDraw, gamePlayer);
tripwire = Utilities.CreateEntityByName<CDynamicProp>("prop_dynamic");
if (tripwire == null) return false;
tripwire.SetModel(
"models/generic/conveyor_control_panel_01/conveyor_button_02.vmdl");
tripwire.DispatchSpawn();
tripwire.Teleport(originTrace.Value.EndPos.toVector(),
originTrace.Value.Normal.toVector().toAngle());
tripwire.EmitSound("Weapon_ELITE.Clipout");
return true;
}
private void createTripwireBeam(IOnlinePlayer owner, CDynamicProp prop,
Vector start, Vector end) {
prop.EmitSound("C4.ExplodeTriggerTrip");
var beam = createBeamEnt(start, end);
if (beam == null) return;
var instance = new TripwireInstance(owner, beam, prop, start, end);
ActiveTripwires.Add(instance);
}
private CEnvBeam? createBeamEnt(Vector start, Vector end) {
var beam = Utilities.CreateEntityByName<CEnvBeam>("env_beam");
if (beam == null) return null;
beam.RenderMode = RenderMode_t.kRenderTransAlpha;
beam.Width = config.TripwireThickness;
beam.Render = config.TripwireColor;
beam.EndPos.X = end.X;
beam.EndPos.Y = end.Y;
beam.EndPos.Z = end.Z;
beam.Teleport(start);
return beam;
}
}

View File

@@ -0,0 +1,156 @@
using System.Diagnostics.CodeAnalysis;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.Utils;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using ShopAPI.Configs.Traitor;
using TTT.API;
using TTT.API.Events;
using TTT.API.Game;
using TTT.API.Player;
using TTT.API.Storage;
using TTT.CS2.API.Items;
using TTT.CS2.Extensions;
using TTT.CS2.RayTrace.Class;
using TTT.CS2.RayTrace.Enum;
using TTT.Game.Events.Body;
using TTT.Game.Events.Game;
using TTT.Game.Events.Player;
using TTT.Game.Listeners;
using TTT.Game.Roles;
namespace TTT.CS2.Items.Tripwire;
public class TripwireMovementListener(IServiceProvider provider)
: BaseListener(provider), IPluginModule, ITripwireActivator {
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly ITripwireTracker? tripwireTracker =
provider.GetService<ITripwireTracker>();
private readonly Dictionary<string, TripwireInstance> killedWithTripwire =
new();
private TripwireConfig config
=> Provider.GetService<IStorage<TripwireConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new TripwireConfig();
public void Start(BasePlugin? plugin) {
if (tripwireTracker == null) return;
plugin?.AddTimer(0.2f, checkTripwires, TimerFlags.REPEAT);
}
private void checkTripwires() {
if (tripwireTracker == null) return;
foreach (var wire in new List<TripwireInstance>(tripwireTracker
.ActiveTripwires)) {
var ray = TraceRay.TraceShape(wire.StartPos, wire.EndPos, Contents.Player,
wire.TripwireProp.Handle);
if (!ray.DidHit() || !ray.HitPlayer(out var player)) continue;
if (!config.FriendlyFireTriggers && player != null) {
var apiPlayer = converter.GetPlayer(player);
var role = Roles.GetRoles(apiPlayer);
if (role.Any(r => r is TraitorRole)) continue;
}
ActivateTripwire(wire);
}
}
private void removeTripwire(TripwireInstance wire) {
tripwireTracker?.ActiveTripwires.Remove(wire);
wire.Beam.Remove();
wire.TripwireProp.Remove();
}
private float getDamage(float distance) {
return config.ExplosionPower
* MathF.Pow(MathF.E, -distance * config.FalloffDelay);
}
private int getDamage(CCSPlayerController gamePlayer, IOnlinePlayer player,
Vector tripwire) {
var origin = gamePlayer.Pawn.Value?.AbsOrigin;
if (origin == null) return 0;
var distance = tripwire.Distance(origin);
var damage = getDamage(distance);
Messenger.DebugAnnounce($"Base damage: {damage} at distance {distance}");
if (Roles.GetRoles(player).Any(r => r is TraitorRole)) {
damage *= config.FriendlyFireMultiplier;
Messenger.DebugAnnounce($"Applied friendly fire multiplier: {damage}");
}
var angleToPlayer = (origin - tripwire).Normalized().toAngle();
var losRay = TraceRay.TraceShape(tripwire, angleToPlayer, Contents.Player,
gamePlayer);
var los = losRay.HitPlayer(out _);
if (!los) {
damage *= config.OutOfLineOfSightMultiplier;
Messenger.DebugAnnounce(
$"Applied out of line of sight multiplier: {damage}");
}
return (int)damage;
}
[UsedImplicitly]
[EventHandler]
public void OnGameEnd(GameStateUpdateEvent ev) {
if (ev.NewState != State.FINISHED) return;
killedWithTripwire.Clear();
}
[UsedImplicitly]
[EventHandler]
public void OnRagdollSpawn(BodyCreateEvent ev) {
if (!killedWithTripwire.TryGetValue(ev.Body.Id, out var info)) return;
if (ev.Body.Killer != null && ev.Body.Killer.Id != ev.Body.OfPlayer.Id)
return;
ev.Body.Killer = info.owner;
}
public void ActivateTripwire(TripwireInstance instance) {
removeTripwire(instance);
instance.TripwireProp.EmitSound("Flashbang.ExplodeDistant");
foreach (var player in Finder.GetOnline()) {
if (dealTripwireDamage(instance, player, out var gamePlayer)) continue;
gamePlayer?.EmitSound("Player.BurnDamage");
}
}
private bool dealTripwireDamage(TripwireInstance instance,
IOnlinePlayer player,
[NotNullWhen(true)] out CCSPlayerController? gamePlayer) {
gamePlayer = null;
if (!player.IsAlive) return false;
gamePlayer = converter.GetPlayer(player);
if (gamePlayer == null) return false;
var damage = getDamage(gamePlayer, player, instance.StartPos);
if (damage < 1) return false;
Event ev;
if (player.Health - damage <= 0) {
killedWithTripwire[player.Id] = instance;
ev = new PlayerDeathEvent(player).WithKiller(instance.owner)
.WithWeapon("[Tripwire]");
} else {
ev = new PlayerDamagedEvent(player, instance.owner, damage) {
Weapon = "[Tripwire]"
};
}
Bus.Dispatch(ev);
player.Health -= damage;
return true;
}
}

View File

@@ -0,0 +1,14 @@
using TTT.Locale;
namespace TTT.CS2.Items.Tripwire;
public class TripwireMsgs {
public static IMsg SHOP_ITEM_TRIPWIRE
=> MsgFactory.Create(nameof(SHOP_ITEM_TRIPWIRE));
public static IMsg SHOP_ITEM_TRIPWIRE_DESC
=> MsgFactory.Create(nameof(SHOP_ITEM_TRIPWIRE_DESC));
public static IMsg SHOP_ITEM_TRIPWIRE_TOOFAR
=> MsgFactory.Create(nameof(SHOP_ITEM_TRIPWIRE_TOOFAR));
}

View File

@@ -1,5 +1,4 @@
using System.Drawing;
using System.Reactive.Concurrency;
using System.Reactive.Concurrency;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
@@ -11,27 +10,25 @@ using TTT.API.Player;
using TTT.API.Storage;
using TTT.CS2.Extensions;
using TTT.CS2.lang;
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 AfkTimerListener(IServiceProvider provider)
: BaseListener(provider) {
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private IDisposable? specTimer, specWarnTimer;
private TTTConfig config
=> Provider.GetRequiredService<IStorage<TTTConfig>>()
.Load()
.GetAwaiter()
.GetResult() ?? new TTTConfig();
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private IDisposable? specTimer, specWarnTimer;
public override void Dispose() {
base.Dispose();

View File

@@ -8,7 +8,6 @@ using TTT.API.Player;
using TTT.CS2.API;
using TTT.CS2.Events;
using TTT.CS2.Extensions;
using TTT.Game;
using TTT.Game.Events.Body;
using TTT.Game.lang;
using TTT.Game.Listeners;
@@ -35,6 +34,8 @@ public class BodyPickupListener(IServiceProvider provider)
if (ev.Player is not IOnlinePlayer online)
throw new InvalidOperationException("Player is not an online player.");
if (ev.Player.Id == body.OfPlayer.Id) return;
var identifyEvent = new BodyIdentifyEvent(body, online);
Bus.Dispatch(identifyEvent);

View File

@@ -15,12 +15,6 @@ using TTT.Karma.lang;
namespace TTT.CS2.Listeners;
public class KarmaBanner(IServiceProvider provider) : BaseListener(provider) {
private KarmaConfig config
=> Provider.GetService<IStorage<KarmaConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new KarmaConfig();
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
@@ -31,6 +25,12 @@ public class KarmaBanner(IServiceProvider provider) : BaseListener(provider) {
private readonly Dictionary<IPlayer, DateTime> lastWarned = new();
private KarmaConfig config
=> Provider.GetService<IStorage<KarmaConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new KarmaConfig();
[UsedImplicitly]
[EventHandler(Priority = Priority.MONITOR, IgnoreCanceled = true)]
public void OnKarmaUpdate(KarmaUpdateEvent ev) {

View File

@@ -20,8 +20,8 @@ public class PlayerStatsTracker(IServiceProvider provider) : IListener {
private readonly ISet<int> revealedDeaths = new HashSet<int>();
private readonly IDictionary<int, (int, int)> roundKillsAndAssists =
new Dictionary<int, (int, int)>();
private readonly IDictionary<int, RoundData> roundStats =
new Dictionary<int, RoundData>();
public void Dispose() { }
@@ -50,24 +50,39 @@ public class PlayerStatsTracker(IServiceProvider provider) : IListener {
ev.Assister == null ? null : converter.GetPlayer(ev.Assister);
if (killer != null) {
roundKillsAndAssists.TryGetValue(killer.Slot, out var def);
def.Item1++;
roundKillsAndAssists[killer.Slot] = def;
roundStats.TryGetValue(killer.Slot, out var def);
def ??= new RoundData();
def.Kills++;
roundStats[killer.Slot] = def;
}
if (assister != null && assister != killer) {
roundKillsAndAssists.TryGetValue(assister.Slot, out var def);
def.Item2++;
roundKillsAndAssists[assister.Slot] = def;
roundStats.TryGetValue(assister.Slot, out var def);
def ??= new RoundData();
def.Assists++;
roundStats[assister.Slot] = def;
}
}
[UsedImplicitly]
[EventHandler(Priority = Priority.HIGH)]
public void OnDamage(PlayerDamagedEvent ev) {
var attacker =
ev.Attacker == null ? null : converter.GetPlayer(ev.Attacker);
if (attacker == null) return;
roundStats.TryGetValue(attacker.Slot, out var def);
def ??= new RoundData();
def.Damage += ev.DmgDealt;
roundStats[attacker.Slot] = def;
}
[UsedImplicitly]
[EventHandler]
public void OnRoundEnd(GameStateUpdateEvent ev) {
if (ev.NewState == State.IN_PROGRESS) {
revealedDeaths.Clear();
roundKillsAndAssists.Clear();
roundStats.Clear();
return;
}
@@ -100,17 +115,24 @@ public class PlayerStatsTracker(IServiceProvider provider) : IListener {
var online = finder.GetOnline()
.Select(p => converter.GetPlayer(p))
.OfType<CCSPlayerController>()
.Where(p => p.IsValid && roundKillsAndAssists.ContainsKey(p.Slot));
.Where(p => p.IsValid && roundStats.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;
if (!roundStats.TryGetValue(player.Slot, out var data)) continue;
stats.Kills += data.Kills;
stats.Assists += data.Assists;
Utilities.SetStateChanged(player, "CCSPlayerController",
"m_pActionTrackingServices");
}
}
private record RoundData {
public int Assists;
public int Damage;
public int Kills;
}
}

View File

@@ -22,17 +22,17 @@ namespace TTT.CS2.Listeners;
public class RoundTimerListener(IServiceProvider provider)
: BaseListener(provider) {
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
public IDisposable? EndTimer;
private TTTConfig config
=> Provider.GetRequiredService<IStorage<TTTConfig>>()
.Load()
.GetAwaiter()
.GetResult() ?? new TTTConfig();
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
public IDisposable? EndTimer;
[UsedImplicitly]
[EventHandler(IgnoreCanceled = true)]
public void OnRoundStart(GameStateUpdateEvent ev) {

View File

@@ -0,0 +1,54 @@
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.Player;
using TTT.CS2.ThirdParties.eGO;
using TTT.Game.Events.Player;
using TTT.Game.Listeners;
using TTT.Game.Roles;
namespace TTT.CS2.Listeners;
public class WardenTagAssigner(IServiceProvider provider)
: BaseListener(provider) {
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly Dictionary<string, (string, char)> oldTags = new();
[UsedImplicitly]
[EventHandler]
public void OnRoleAssign(PlayerRoleAssignEvent ev) {
var maul = EgoApi.MAUL.Get();
if (maul == null) return;
Server.NextWorldUpdate(() => {
var gamePlayer = converter.GetPlayer(ev.Player);
if (gamePlayer == null) return;
Task.Run(async () => {
if (ev.Role is DetectiveRole) {
var oldTag = await maul.getTagService().GetTag(gamePlayer.SteamID);
var oldTagColor =
await maul.getTagService().GetTagColor(gamePlayer.SteamID);
oldTags[ev.Player.Id] = (oldTag, oldTagColor);
}
await Server.NextWorldUpdateAsync(() => {
if (ev.Role is DetectiveRole) {
maul.getTagService().SetTag(gamePlayer, "[DETECTIVE]", false);
maul.getTagService()
.SetTagColor(gamePlayer, ChatColors.DarkBlue, false);
} else if (oldTags.TryGetValue(ev.Player.Id, out var oldTag)) {
maul.getTagService().SetTag(gamePlayer, oldTag.Item1, false);
maul.getTagService().SetTagColor(gamePlayer, oldTag.Item2, false);
oldTags.Remove(ev.Player.Id);
}
});
});
});
}
}

View File

@@ -9,8 +9,8 @@ namespace TTT.CS2.Player;
public class CS2AliveSpoofer : IAliveSpoofer, IPluginModule {
private readonly HashSet<CCSPlayerController> _fakeAlivePlayers = new();
private BasePlugin? plugin;
public ISet<CCSPlayerController> FakeAlivePlayers => _fakeAlivePlayers;
private BasePlugin? plugin = null;
public void SpoofAlive(CCSPlayerController player) {
if (player.IsBot) {

View File

@@ -1,5 +1,6 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using TTT.API.Player;
namespace TTT.CS2.Player;
@@ -53,7 +54,7 @@ public class CS2Player : IOnlinePlayer, IEquatable<CS2Player> {
}
public string Id { get; }
public string Name { get; }
public string Name { get; set; }
public int Health {
get => Player?.Pawn.Value != null ? Player.Pawn.Value.Health : 0;
@@ -96,7 +97,11 @@ public class CS2Player : IOnlinePlayer, IEquatable<CS2Player> {
}
public bool IsAlive {
get => Player != null && Player.Pawn.Value is { Health: > 0 };
get
=> Player != null && Player is {
Team : CsTeam.CounterTerrorist or CsTeam.Terrorist,
Pawn.Value.Health: > 0
};
set
=> throw new NotSupportedException(

View File

@@ -1,7 +1,4 @@
using System.Net;
using System.Numerics;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory;
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;

View File

@@ -18,13 +18,13 @@ public class GrenadeDataHelper {
heGrenadeSignature);
}
private delegate int CHEGrenadeProjectile_CreateDelegate(IntPtr position,
IntPtr angle, IntPtr velocity, IntPtr velocityAngle, IntPtr thrower,
int weaponId, byte team);
public static int CreateGrenade(Vector position, QAngle angle,
Vector velocity, Vector velocityAngle, IntPtr thrower, CsTeam team) {
return CHEGrenadeProjectile_CreateFunc(position.Handle, angle.Handle,
velocity.Handle, velocityAngle.Handle, thrower, 44, (byte)team);
}
private delegate int CHEGrenadeProjectile_CreateDelegate(IntPtr position,
IntPtr angle, IntPtr velocity, IntPtr velocityAngle, IntPtr thrower,
int weaponId, byte team);
}

View File

@@ -1,6 +1,5 @@
using TTT.API.Player;
using TTT.API.Role;
using TTT.Game;
using TTT.Game.lang;
using TTT.Locale;
@@ -13,6 +12,8 @@ public static class CS2Msgs {
public static IMsg DEAD_MUTE_REMINDER
=> MsgFactory.Create(nameof(DEAD_MUTE_REMINDER));
public static IMsg AFK_MOVED => MsgFactory.Create(nameof(AFK_MOVED));
public static IMsg TASER_SCANNED(IPlayer scannedPlayer, IRole role) {
var rolePrefix = GameMsgs.GetRolePrefix(role);
return MsgFactory.Create(nameof(TASER_SCANNED),
@@ -23,8 +24,6 @@ public static class CS2Msgs {
return MsgFactory.Create(nameof(AFK_WARNING), span.TotalSeconds);
}
public static IMsg AFK_MOVED => MsgFactory.Create(nameof(AFK_MOVED));
public static IMsg TRAITOR_CHAT_FORMAT(IOnlinePlayer player, string msg) {
return MsgFactory.Create(nameof(TRAITOR_CHAT_FORMAT), player.Name, msg);
}

View File

@@ -52,4 +52,8 @@ SHOP_ITEM_CLUSTER_GRENADE: "Cluster Grenade"
SHOP_ITEM_CLUSTER_GRENADE_DESC: "A grenade that splits into multiple smaller grenades."
SHOP_ITEM_TELEPORT_DECOY: "Teleport Decoy"
SHOP_ITEM_TELEPORT_DECOY_DESC: "A decoy that teleports you to it upon explosion."
SHOP_ITEM_TELEPORT_DECOY_DESC: "A decoy that teleports you to it upon explosion."
SHOP_ITEM_TRIPWIRE: "Tripwire"
SHOP_ITEM_TRIPWIRE_DESC: "A tripwire that explodes when triggered."
SHOP_ITEM_TRIPWIRE_TOOFAR: "%PREFIX%You are too far away to place the tripwire."

View File

@@ -8,7 +8,7 @@ namespace TTT.Game.Events.Player;
public class PlayerDamagedEvent(IOnlinePlayer player, IOnlinePlayer? attacker,
int originalHp, int hpLeft) : PlayerEvent(player), ICancelableEvent {
public PlayerDamagedEvent(IOnlinePlayer player, IOnlinePlayer? attacker,
int damageDealt) : this(player, attacker, player.Health - damageDealt,
int damageDealt) : this(player, attacker, player.Health + damageDealt,
player.Health) { }
public PlayerDamagedEvent(IPlayerConverter<CCSPlayerController> converter,

View File

@@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Listeners\Stats\" />
<Folder Include="Listeners\Stats\"/>
</ItemGroup>
</Project>

View File

@@ -11,7 +11,7 @@ namespace TTT.Game.Listeners;
public class GameRestartListener(IServiceProvider provider)
: BaseListener(provider) {
private TTTConfig config =
private readonly TTTConfig config =
provider.GetService<IStorage<TTTConfig>>()?.Load().GetAwaiter().GetResult()
?? new TTTConfig();

View File

@@ -10,12 +10,6 @@ using TTT.Locale;
namespace TTT.Game.Roles;
public abstract class BaseRole(IServiceProvider provider) : IRole {
protected TTTConfig Config
=> Provider.GetRequiredService<IStorage<TTTConfig>>()
.Load()
.GetAwaiter()
.GetResult() ?? new TTTConfig();
protected readonly IInventoryManager Inventory =
provider.GetRequiredService<IInventoryManager>();
@@ -29,6 +23,12 @@ public abstract class BaseRole(IServiceProvider provider) : IRole {
protected readonly IRoleAssigner Roles =
provider.GetRequiredService<IRoleAssigner>();
protected TTTConfig Config
=> Provider.GetRequiredService<IStorage<TTTConfig>>()
.Load()
.GetAwaiter()
.GetResult() ?? new TTTConfig();
public abstract string Id { get; }
public abstract string Name { get; }
public abstract Color Color { get; }

View File

@@ -8,8 +8,10 @@ using TTT.Game.Events.Player;
namespace TTT.Game.Roles;
public class RoleAssigner(IServiceProvider provider) : IRoleAssigner {
private readonly IDictionary<IPlayer, ICollection<IRole>> assignedRoles =
new Dictionary<IPlayer, ICollection<IRole>>();
private static readonly Random rng = new();
private readonly IDictionary<string, ICollection<IRole>> assignedRoles =
new Dictionary<string, ICollection<IRole>>();
private readonly IEventBus bus = provider.GetRequiredService<IEventBus>();
@@ -18,18 +20,18 @@ public class RoleAssigner(IServiceProvider provider) : IRoleAssigner {
public void AssignRoles(ISet<IOnlinePlayer> players, IList<IRole> roles) {
assignedRoles.Clear();
var shuffled = players.OrderBy(_ => Guid.NewGuid()).ToHashSet();
var shuffled = players.OrderBy(_ => rng.NextDouble()).ToHashSet();
bool roleAssigned;
do { roleAssigned = tryAssignRole(shuffled, roles); } while (roleAssigned);
}
public Task<ICollection<IRole>?> Load(IPlayer key) {
assignedRoles.TryGetValue(key, out var roles);
assignedRoles.TryGetValue(key.Id, out var roles);
return Task.FromResult(roles);
}
public Task Write(IPlayer key, ICollection<IRole> newData) {
assignedRoles[key] = newData;
assignedRoles[key.Id] = newData;
return Task.CompletedTask;
}
@@ -44,9 +46,9 @@ public class RoleAssigner(IServiceProvider provider) : IRoleAssigner {
if (ev.IsCanceled) continue;
if (!assignedRoles.ContainsKey(player))
assignedRoles[player] = new List<IRole>();
assignedRoles[player].Add(ev.Role);
if (!assignedRoles.ContainsKey(player.Id))
assignedRoles[player.Id] = new List<IRole>();
assignedRoles[player.Id].Add(ev.Role);
ev.Role.OnAssign(player);
onlineMessenger?.Debug(

View File

@@ -16,12 +16,6 @@ using TTT.Locale;
namespace TTT.Game;
public class RoundBasedGame(IServiceProvider provider) : IGame {
private TTTConfig config
=> provider.GetRequiredService<IStorage<TTTConfig>>()
.Load()
.GetAwaiter()
.GetResult() ?? new TTTConfig();
protected readonly IMsgLocalizer Locale =
provider.GetRequiredService<IMsgLocalizer>();
@@ -29,6 +23,12 @@ public class RoundBasedGame(IServiceProvider provider) : IGame {
protected State state = State.WAITING;
private TTTConfig config
=> provider.GetRequiredService<IStorage<TTTConfig>>()
.Load()
.GetAwaiter()
.GetResult() ?? new TTTConfig();
public virtual IList<IRole> Roles { get; } = [
new InnocentRole(provider), new TraitorRole(provider),
new DetectiveRole(provider)

View File

@@ -9,11 +9,12 @@ public record TTTConfig {
public Func<int, int> TraitorCount { get; init; } =
p => (int)Math.Ceiling((p - 1) / 5f);
public Func<int, int> DetectiveCount { get; init; } =
p => (int)Math.Floor(p / 8f);
public Func<int, int> DetectiveCount { get; init; } = p
=> (int)Math.Ceiling(Math.Floor(p / 8f) / 1.5f);
public Func<int, int> InnocentCount { get; init; } = p
=> p - (int)Math.Ceiling((p - 1) / 5f) - (int)Math.Floor(p / 8f);
=> p - (int)Math.Ceiling((p - 1) / 5f)
- (int)Math.Ceiling(Math.Floor(p / 8f) / 1.5f);
}
public record RoleConfig {

View File

@@ -15,12 +15,6 @@ namespace TTT.Karma;
public class KarmaListener(IServiceProvider provider) : BaseListener(provider) {
private readonly Dictionary<string, int> badKills = new();
private KarmaConfig config
=> Provider.GetService<IStorage<KarmaConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new KarmaConfig();
private readonly IGameManager games =
provider.GetRequiredService<IGameManager>();
@@ -34,6 +28,12 @@ public class KarmaListener(IServiceProvider provider) : BaseListener(provider) {
public bool GiveKarmaOnRoundEnd = true;
private KarmaConfig config
=> Provider.GetService<IStorage<KarmaConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new KarmaConfig();
[EventHandler]
[UsedImplicitly]
public void OnRoundStart(GameStateUpdateEvent ev) { badKills.Clear(); }

View File

@@ -1,9 +1,5 @@
using System.Collections.Concurrent;
using System.Data;
using System.Diagnostics;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using TTT.API.Events;
using TTT.API.Player;
@@ -28,7 +24,7 @@ public sealed class KarmaStorage(IServiceProvider provider) : IKarmaService {
if (!result.IsSuccessStatusCode) return config.DefaultKarma;
var content = await result.Content.ReadAsStringAsync();
var json = System.Text.Json.JsonDocument.Parse(content);
var json = JsonDocument.Parse(content);
if (!json.RootElement.TryGetProperty("karma", out var karmaElement))
return config.DefaultKarma;
@@ -42,9 +38,8 @@ public sealed class KarmaStorage(IServiceProvider provider) : IKarmaService {
if (karmaUpdateEvent.IsCanceled) return;
var data = new { steam_id = key.Id, karma = karmaUpdateEvent.Karma };
var payload = new StringContent(
System.Text.Json.JsonSerializer.Serialize(data),
System.Text.Encoding.UTF8, "application/json");
var payload = new StringContent(JsonSerializer.Serialize(data),
Encoding.UTF8, "application/json");
await client.PatchAsync("user/" + key.Id, payload);
}

View File

@@ -64,5 +64,4 @@ public class TTT(IServiceProvider provider) : BasePlugin {
base.Dispose(disposing);
}
}

View File

@@ -1,7 +1,6 @@
using System.Reactive.Concurrency;
using CounterStrikeSharp.API.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SpecialRound;
using Stats;
using TTT.CS2;

View File

@@ -1,13 +1,10 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Commands;
using JetBrains.Annotations;
using MAULActainShared.plugin.models;
using Microsoft.Extensions.DependencyInjection;
using TTT.API;
using TTT.API.Command;
using TTT.API.Events;
using TTT.API.Game;
using TTT.API.Messages;
using TTT.API.Player;
using TTT.CS2.Command;
@@ -19,18 +16,19 @@ using TTT.RTD.lang;
namespace TTT.RTD;
public class AutoRTDCommand(IServiceProvider provider) : ICommand, IListener {
public string Id => "autortd";
private ICookie? autoRtdCookie;
private readonly ICommandManager commands =
provider.GetRequiredService<ICommandManager>();
private readonly IPlayerFinder finder =
provider.GetRequiredService<IPlayerFinder>();
private readonly ICommandManager commands =
provider.GetRequiredService<ICommandManager>();
private readonly IMsgLocalizer localizer =
provider.GetRequiredService<IMsgLocalizer>();
private readonly Dictionary<string, bool> playerStatuses = new();
private ICookie? autoRtdCookie;
public string Id => "autortd";
public bool MustBeOnMainThread => true;
public void Dispose() { }
@@ -47,7 +45,6 @@ public class AutoRTDCommand(IServiceProvider provider) : ICommand, IListener {
}
public string[] RequiredFlags => ["@ttt/autortd"];
private Dictionary<string, bool> playerStatuses = new();
public async Task<CommandResult> Execute(IOnlinePlayer? executor,
ICommandInfo info) {

View File

@@ -8,11 +8,11 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\API\API.csproj" />
<ProjectReference Include="..\CS2\CS2.csproj" />
<ProjectReference Include="..\Game\Game.csproj" />
<ProjectReference Include="..\ShopAPI\ShopAPI.csproj" />
<ProjectReference Include="..\Shop\Shop.csproj" />
<ProjectReference Include="..\API\API.csproj"/>
<ProjectReference Include="..\CS2\CS2.csproj"/>
<ProjectReference Include="..\Game\Game.csproj"/>
<ProjectReference Include="..\ShopAPI\ShopAPI.csproj"/>
<ProjectReference Include="..\Shop\Shop.csproj"/>
</ItemGroup>
<ItemGroup>

View File

@@ -13,8 +13,6 @@ namespace TTT.RTD;
public class RewardGenerator(IServiceProvider provider)
: IRewardGenerator, IPluginModule {
private readonly List<(IRtdReward, float)> rewards = new();
private const float PROB_LOTTERY = 1 / 5000f;
private const float PROB_EXTREMELY_LOW = 1 / 800f;
private const float PROB_VERY_LOW = 1 / 100f;
@@ -22,28 +20,7 @@ public class RewardGenerator(IServiceProvider provider)
private const float PROB_MEDIUM = 1 / 10f;
private const float PROB_OFTEN = 1 / 5f;
private const float PROB_VERY_OFTEN = 1 / 2f;
public IEnumerator<(IRtdReward, float)> GetEnumerator() {
return rewards.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public int Count => rewards.Count;
public IRtdReward GetReward() {
var totalWeight = 0f;
foreach (var (_, weight) in rewards) { totalWeight += weight; }
var randomValue = Random.Shared.NextSingle() * totalWeight;
var cumulativeWeight = 0f;
foreach (var (reward, weight) in rewards) {
cumulativeWeight += weight;
if (randomValue <= cumulativeWeight) { return reward; }
}
return rewards[^1].Item1;
}
private readonly List<(IRtdReward, float)> rewards = new();
public void Start() {
rewards.AddRange([
@@ -65,7 +42,7 @@ public class RewardGenerator(IServiceProvider provider)
(new ShopItemReward<HealthStation>(provider), PROB_EXTREMELY_LOW),
(new HealthReward(provider, 1), PROB_EXTREMELY_LOW),
(new CreditReward(provider, 100), PROB_EXTREMELY_LOW),
(new HealthReward(provider, 200), PROB_EXTREMELY_LOW),
(new HealthReward(provider, 200), PROB_EXTREMELY_LOW)
]);
rewards.ForEach(r => r.Item1.Start());
@@ -73,10 +50,32 @@ public class RewardGenerator(IServiceProvider provider)
public void Start(BasePlugin? plugin) {
Start();
foreach (var (reward, _) in rewards) {
if (reward is IPluginModule module) { module.Start(plugin); }
}
foreach (var (reward, _) in rewards)
if (reward is IPluginModule module)
module.Start(plugin);
}
public void Dispose() { }
public IEnumerator<(IRtdReward, float)> GetEnumerator() {
return rewards.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public int Count => rewards.Count;
public IRtdReward GetReward() {
var totalWeight = 0f;
foreach (var (_, weight) in rewards) totalWeight += weight;
var randomValue = Random.Shared.NextSingle() * totalWeight;
var cumulativeWeight = 0f;
foreach (var (reward, weight) in rewards) {
cumulativeWeight += weight;
if (randomValue <= cumulativeWeight) return reward;
}
return rewards[^1].Item1;
}
}

View File

@@ -1,21 +1,18 @@
using CounterStrikeSharp.API;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using ShopAPI;
using TTT.API.Player;
using TTT.RTD.lang;
using TTT.Shop;
namespace TTT.RTD.Rewards;
public class CreditReward(IServiceProvider provider, int amo)
: RoundStartReward(provider) {
private readonly IShop shop = provider.GetRequiredService<IShop>();
public override string Name => Locale[RtdMsgs.CREDITS_REWARD(amo)];
public override string Description
=> Locale[RtdMsgs.CREDITS_REWARD_DESC(amo)];
private readonly IShop shop = provider.GetRequiredService<IShop>();
public override void GiveOnRound(IOnlinePlayer player) {
shop.AddBalance(player, amo, "RTD Reward");
}

Some files were not shown because too many files have changed in this diff Show More