Compare commits

...

24 Commits

Author SHA1 Message Date
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
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
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
Isaac
99ed6bd69b update: Increase speedround weight +semver:patch (#166) 2025-10-31 19:10:12 -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
Isaac
c4a73f9a24 Require on actual team to be alive (#160) 2025-10-30 18:17:33 -07:00
Isaac
8fa2377e1e Game Balancing and Fixes (#159) 2025-10-30 18:03:51 -07:00
Isaac
dbe664d18f Revert "Fetch playername from object if available" (#158)
This reverts commit 8cd8e14e18.
2025-10-29 16:30:34 -07:00
Isaac
5717ab612a Fetch playername from object if available +semver:patch (#157) 2025-10-29 15:36:29 -07:00
Isaac
f6b79ef038 QoL tweaks +semver:patch (#156) 2025-10-29 01:45:55 -07:00
Isaac
cc52d19108 fix: Suppress damage stats (+semver:patch) (#155) 2025-10-28 15:32:06 -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
18 changed files with 1252 additions and 163 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

@@ -1,7 +1,6 @@
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using SpecialRoundAPI.Configs;
using TTT.API;
using TTT.API.Events;
using TTT.Game.Events.Game;
using TTT.Game.Listeners;

View File

@@ -66,6 +66,7 @@ public static class CS2ServiceCollection {
collection.AddModBehavior<IStorage<SilentAWPConfig>, CS2SilentAWPConfig>();
collection
.AddModBehavior<IStorage<HealthshotConfig>, CS2HealthshotConfig>();
collection.AddModBehavior<IStorage<TripwireConfig>, CS2TripwireConfig>();
// TTT - CS2 Specific optionals
collection.AddScoped<ITextSpawner, TextSpawner>();

View File

@@ -0,0 +1,68 @@
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)", 50,
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, higher means faster falloff", 0.15f,
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<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_PLACEMENT_DISTANCE_SQUARED =
new("css_ttt_shop_tripwire_max_placement_distance_squared",
"Maximum distance squared from player to place Tripwire", 400f * 400f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(100f, 100000f));
public static readonly FakeConVar<int> CV_INITIATION_TIME_MS = new(
"css_ttt_shop_tripwire_initiation_time_ms",
"Time in milliseconds to initiate Tripwire placement", 2000,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 10000));
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,
FriendlyFireTriggers = CV_FF_TRIGGERS.Value,
MaxPlacementDistanceSquared = CV_MAX_PLACEMENT_DISTANCE_SQUARED.Value,
TripwireInitiationTime =
TimeSpan.FromMilliseconds(CV_INITIATION_TIME_MS.Value)
};
return Task.FromResult<TripwireConfig?>(cfg);
}
}

View File

@@ -23,12 +23,12 @@ public class CombatHandler(IServiceProvider provider) : IPluginModule {
private readonly IGameManager games =
provider.GetRequiredService<IGameManager>();
private readonly IAliveSpoofer spoofer =
provider.GetRequiredService<IAliveSpoofer>();
private readonly IRoleAssigner roles =
provider.GetRequiredService<IRoleAssigner>();
private readonly IAliveSpoofer spoofer =
provider.GetRequiredService<IAliveSpoofer>();
public void Start() { }
public void Dispose() { }

View File

@@ -1,5 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
@@ -80,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;
@@ -98,7 +97,7 @@ public class DamageStation(IServiceProvider provider)
}
gamePlayer.EmitSound("Player.DamageFall", SELF(gamePlayer.Slot), 0.2f);
player.Health += damageAmount;
player.Health -= damageAmount;
info.HealthGiven += damageAmount;
}
}

View File

@@ -1,4 +1,5 @@
using System.Drawing;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Reactive.Concurrency;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
@@ -14,6 +15,7 @@ using TTT.API.Storage;
using TTT.CS2.Extensions;
using TTT.CS2.RayTrace.Class;
using TTT.CS2.RayTrace.Enum;
using TTT.CS2.RayTrace.Struct;
using TTT.Game.Roles;
namespace TTT.CS2.Items.Tripwire;
@@ -27,30 +29,26 @@ public static class TripwireServiceCollection {
public class TripwireItem(IServiceProvider provider)
: RoleRestrictedItem<TraitorRole>(provider), IPluginModule {
public override string Name => Locale[TripwireMsgs.SHOP_ITEM_TRIPWIRE];
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>();
protected readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
public record TripwireInstance(IOnlinePlayer owner, CEnvBeam Beam,
CDynamicProp TripwireProp, Vector StartPos, Vector EndPos);
private TripwireConfig config = provider
.GetService<IStorage<TripwireConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new TripwireConfig();
public readonly List<TripwireInstance> ActiveTripwires = [];
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 List<TripwireInstance> ActiveTripwires = new();
public void Start(BasePlugin? plugin) {
Start();
plugin
@@ -64,76 +62,66 @@ public class TripwireItem(IServiceProvider provider)
"models/generic/conveyor_control_panel_01/conveyor_button_02.vmdl");
}
public override PurchaseResult CanPurchase(IOnlinePlayer player) {
PurchaseResult result = base.CanPurchase(player);
if (result != PurchaseResult.SUCCESS) return result;
Server.NextWorldUpdateAsync(() => {
var gamePlayer = converter.GetPlayer(player);
if (gamePlayer == null) {
result = PurchaseResult.UNKNOWN_ERROR;
return;
}
var trace = gamePlayer.GetGameTraceByEyePosition(TraceMask.MaskSolid,
Contents.NoDraw, gamePlayer);
if (trace == null) {
result = PurchaseResult.TRIPWIRE_TOO_FAR;
return;
}
var endPos = trace.Value.EndPos;
var distance = gamePlayer.GetEyePosition()
.DistanceSquared(endPos.toVector());
if (distance > 2500) {
result = PurchaseResult.TRIPWIRE_TOO_FAR;
return;
}
})
.GetAwaiter()
.GetResult();
return result;
}
public override void OnPurchase(IOnlinePlayer player) {
Server.NextWorldUpdate(() => {
var gamePlayer = converter.GetPlayer(player);
var playerPawn = gamePlayer?.PlayerPawn.Value;
if (gamePlayer == null || playerPawn == null) return;
var originTrace =
gamePlayer.GetGameTraceByEyePosition(TraceMask.MaskSolid,
Contents.NoDraw, gamePlayer);
var origin = gamePlayer.GetEyePosition();
if (origin == null || originTrace == null) return;
if (!placeTripwire(player, out var originTrace, out var endTrace,
out var tripwire))
return;
var angles = vectorToAngle(originTrace.Value.Normal.toVector());
var endTrace = TraceRay.TraceShape(origin, angles, TraceMask.MaskSolid,
Contents.NoDraw, gamePlayer);
var tripwire = Utilities.CreateEntityByName<CDynamicProp>("prop_dynamic");
if (tripwire == null) return;
tripwire.SetModel(
"models/generic/conveyor_control_panel_01/conveyor_button_02.vmdl");
tripwire.DispatchSpawn();
tripwire.Teleport(originTrace.Value.EndPos.toVector(),
vectorToAngle(originTrace.Value.Normal.toVector()));
tripwire.EmitSound("Weapon_ELITE.Clipout");
scheduler.Schedule(TimeSpan.FromSeconds(2), () => {
Server.NextWorldUpdate(() => {
if (!gamePlayer.IsValid) return;
createBeam(player, tripwire, originTrace.Value.EndPos.toVector(),
endTrace.EndPos.toVector());
scheduler.Schedule(config.TripwireInitiationTime,
() => {
Server.NextWorldUpdate(() => {
createTripwireBeam(player, tripwire,
originTrace.Value.EndPos.toVector(),
endTrace.Value.EndPos.toVector());
});
});
});
});
}
private void createBeam(IOnlinePlayer owner, CDynamicProp prop, Vector start,
Vector end) {
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 = vectorToAngle(originTrace.Value.Normal.toVector());
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(),
vectorToAngle(originTrace.Value.Normal.toVector()));
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;
@@ -161,4 +149,7 @@ public class TripwireItem(IServiceProvider provider)
beam.Teleport(start);
return beam;
}
public record TripwireInstance(IOnlinePlayer owner, CEnvBeam Beam,
CDynamicProp TripwireProp, Vector StartPos, Vector EndPos);
}

View File

@@ -1,34 +1,47 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
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.Role;
using TTT.API.Storage;
using TTT.CS2.Extensions;
using TTT.CS2.RayTrace.Class;
using TTT.CS2.RayTrace.Enum;
using TTT.CS2.Utils;
using TTT.Game.Events.Body;
using TTT.Game.Events.Game;
using TTT.Game.Events.Player;
using TTT.Game.Roles;
namespace TTT.CS2.Items.Tripwire;
public class TripwireMovementListener(IServiceProvider provider)
: IPluginModule, IListener {
private readonly TripwireItem? item = provider.GetService<TripwireItem>();
private readonly IPlayerFinder finder =
provider.GetRequiredService<IPlayerFinder>();
private readonly IEventBus bus = provider.GetRequiredService<IEventBus>();
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly IEventBus bus = provider.GetRequiredService<IEventBus>();
private readonly IPlayerFinder finder =
provider.GetRequiredService<IPlayerFinder>();
private readonly TripwireItem? item = provider.GetService<TripwireItem>();
private readonly Dictionary<string, TripwireItem.TripwireInstance>
killedWithTripwire = new();
private readonly IRoleAssigner roles =
provider.GetRequiredService<IRoleAssigner>();
private TripwireConfig config
=> provider.GetService<IStorage<TripwireConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new TripwireConfig();
public void Dispose() { }
public void Start() { }
@@ -38,16 +51,19 @@ public class TripwireMovementListener(IServiceProvider provider)
plugin?.AddTimer(0.1f, checkTripwires, TimerFlags.REPEAT);
}
private readonly Dictionary<string, TripwireItem.TripwireInstance>
killedWithTripwire = new();
private void checkTripwires() {
if (item == null) return;
var toRemove = new List<TripwireItem.TripwireInstance>();
foreach (var wire in item.ActiveTripwires) {
var ray = TraceRay.TraceShape(wire.StartPos, wire.EndPos, Contents.Player,
wire.TripwireProp.Handle);
if (!ray.DidHit() || !ray.HitPlayer(out _)) continue;
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;
}
toRemove.Add(wire);
@@ -65,6 +81,7 @@ public class TripwireMovementListener(IServiceProvider provider)
private void doExplosion(TripwireItem.TripwireInstance instance) {
foreach (var player in finder.GetOnline()) {
if (!player.IsAlive) continue;
var gamePlayer = converter.GetPlayer(player);
if (gamePlayer == null || gamePlayer.Pawn.Value == null) continue;
if (gamePlayer.Pawn.Value.AbsOrigin == null) continue;
@@ -72,6 +89,10 @@ public class TripwireMovementListener(IServiceProvider provider)
var distance =
instance.StartPos.Distance(gamePlayer.Pawn.Value.AbsOrigin);
var damage = (int)Math.Round(getDamage(distance));
if (roles.GetRoles(player).Any(r => r is TraitorRole))
damage = (int)(damage * config.FriendlyFireMultiplier);
if (damage < 1) continue;
if (player.Health - damage <= 0) {
@@ -92,10 +113,9 @@ public class TripwireMovementListener(IServiceProvider provider)
}
}
private static readonly float fallofDelay = 0.01f;
private float getDamage(float distance) {
return 1000.0f * MathF.Pow(MathF.E, -distance * fallofDelay);
return config.ExplosionPower
* MathF.Pow(MathF.E, -distance * config.FalloffDelay);
}
[UsedImplicitly]

View File

@@ -8,4 +8,7 @@ public class TripwireMsgs {
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

@@ -14,12 +14,11 @@ namespace TTT.CS2.Listeners;
public class WardenTagAssigner(IServiceProvider provider)
: BaseListener(provider) {
private Dictionary<string, (string, char)> oldTags =
new Dictionary<string, (string, char)>();
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly Dictionary<string, (string, char)> oldTags = new();
[UsedImplicitly]
[EventHandler]
public void OnRoleAssign(PlayerRoleAssignEvent ev) {
@@ -41,7 +40,7 @@ public class WardenTagAssigner(IServiceProvider provider)
if (ev.Role is DetectiveRole) {
maul.getTagService().SetTag(gamePlayer, "[DETECTIVE]", false);
maul.getTagService()
.SetTagColor(gamePlayer, ChatColors.LightBlue, false);
.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);

View File

@@ -55,4 +55,5 @@ SHOP_ITEM_TELEPORT_DECOY: "Teleport Decoy"
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_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

@@ -2,8 +2,8 @@
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xml:space="preserve">
<s:Boolean
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=items_005Coneshotdeagle/@EntryIndexedValue">True</s:Boolean>
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=items_005Coneshotdeagle/@EntryIndexedValue">True</s:Boolean>
<s:Boolean
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=lang/@EntryIndexedValue">True</s:Boolean>
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=lang/@EntryIndexedValue">True</s:Boolean>
<s:Boolean
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shop/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shop/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -2,5 +2,10 @@
public record TripwireConfig : ShopItemConfig {
public override int Price { get; init; } = 60;
public int ExplosionPower { get; init; } = 100;
public int ExplosionPower { get; init; } = 1000;
public float FalloffDelay { get; init; } = 0.02f;
public float FriendlyFireMultiplier { get; init; } = 0.5f;
public bool FriendlyFireTriggers { get; init; } = true;
public float MaxPlacementDistanceSquared { get; init; } = 400f * 400f;
public TimeSpan TripwireInitiationTime { get; init; } = TimeSpan.FromSeconds(2);
}

View File

@@ -41,11 +41,6 @@ public enum PurchaseResult {
/// The item cannot be purchased multiple times, and the player already owns it.
/// </summary>
ALREADY_OWNED,
/// <summary>
/// The tripwire placement is too far from the player.
/// </summary>
TRIPWIRE_TOO_FAR
}
public static class PurchaseResultExtensions {
@@ -64,8 +59,6 @@ public static class PurchaseResultExtensions {
"You do not have the required role to purchase this item",
PurchaseResult.ALREADY_OWNED =>
"You have purchased the maximum amount of this item",
PurchaseResult.TRIPWIRE_TOO_FAR =>
"The tripwire placement is too far from you",
_ => "An unexpected error occurred"
};
}

View File

@@ -17,9 +17,12 @@ namespace SpecialRound.Rounds;
public class PistolRound(IServiceProvider provider)
: AbstractSpecialRound(provider), IPluginModule {
private readonly IInventoryManager inventory = provider
.GetRequiredService<IInventoryManager>();
private BasePlugin? plugin;
public override string Name => "Pistol";
public override IMsg Description => RoundMsgs.SPECIAL_ROUND_PISTOL;
private BasePlugin? plugin;
private PistolRoundConfig config
=> Provider.GetService<IStorage<PistolRoundConfig>>()
@@ -27,9 +30,6 @@ public class PistolRound(IServiceProvider provider)
.GetAwaiter()
.GetResult() ?? new PistolRoundConfig();
private readonly IInventoryManager inventory = provider
.GetRequiredService<IInventoryManager>();
public override SpecialRoundConfig Config => config;
public void Start(BasePlugin? newPluing) { plugin = newPluing; }

View File

@@ -1,5 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.UserMessages;
using Microsoft.Extensions.DependencyInjection;
using SpecialRound.lang;
@@ -15,23 +14,6 @@ namespace SpecialRound.Rounds;
public class SuppressedRound(IServiceProvider provider)
: AbstractSpecialRound(provider), IPluginModule {
public override string Name => "Suppressed";
public override IMsg Description => RoundMsgs.SPECIAL_ROUND_SUPPRESSED;
public override SpecialRoundConfig Config => config;
private BasePlugin? plugin;
private SuppressedRoundConfig config
=> Provider.GetService<IStorage<SuppressedRoundConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new SuppressedRoundConfig();
public void Start(BasePlugin? newPlugin) { plugin ??= newPlugin; }
public override void ApplyRoundEffects() {
plugin?.HookUserMessage(452, onWeaponSound);
}
private static readonly HashSet<uint> silencedWeapons = new() {
1, // deagle
2, // dual berettas
@@ -45,6 +27,23 @@ public class SuppressedRound(IServiceProvider provider)
64 // r8 revolver
};
private BasePlugin? plugin;
public override string Name => "Suppressed";
public override IMsg Description => RoundMsgs.SPECIAL_ROUND_SUPPRESSED;
public override SpecialRoundConfig Config => config;
private SuppressedRoundConfig config
=> Provider.GetService<IStorage<SuppressedRoundConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new SuppressedRoundConfig();
public void Start(BasePlugin? newPlugin) { plugin ??= newPlugin; }
public override void ApplyRoundEffects() {
plugin?.HookUserMessage(452, onWeaponSound);
}
private HookResult onWeaponSound(UserMessage msg) {
var defIndex = msg.ReadUInt("item_def_index");

File diff suppressed because it is too large Load Diff