mirror of
https://github.com/MSWS/TTT.git
synced 2025-12-06 06:22:44 -08:00
Compare commits
93 Commits
1.0.1-dev.
...
2.0.1-dev.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfe86b0242 | ||
|
|
eff68897a0 | ||
|
|
63afe31e3b | ||
|
|
a7fa2afe15 | ||
|
|
7749deabd3 | ||
|
|
f8b67c5194 | ||
|
|
77281aa8c6 | ||
|
|
20497bbb4d | ||
|
|
133083003d | ||
|
|
ad3603c833 | ||
|
|
6d7149a3f5 | ||
|
|
24cd1295b6 | ||
|
|
125daa515e | ||
|
|
722c29bde7 | ||
|
|
3c0fd74c2a | ||
|
|
60de8b54db | ||
|
|
e7dfbca35d | ||
|
|
3a463b29c6 | ||
|
|
e11a8e20e5 | ||
|
|
b0a7ec60e0 | ||
|
|
0aa28b1076 | ||
|
|
b53920282b | ||
|
|
5167291ab4 | ||
|
|
d76f93c0c7 | ||
|
|
39da6e8702 | ||
|
|
d628df6116 | ||
|
|
da232907f7 | ||
|
|
703144f04b | ||
|
|
3e947959ac | ||
|
|
aed5b4ace4 | ||
|
|
0abbb0c07a | ||
|
|
8131a85bb6 | ||
|
|
8d068b675b | ||
|
|
84d81fc3db | ||
|
|
0531d3afb5 | ||
|
|
eb48bc68f3 | ||
|
|
40b153d938 | ||
|
|
e35551b830 | ||
|
|
bb132b987b | ||
|
|
f92088882e | ||
|
|
3cdf2510bf | ||
|
|
61b8d4eba9 | ||
|
|
1ff4eabd45 | ||
|
|
a0cf6b38f2 | ||
|
|
9145e16b90 | ||
|
|
b3000ecc4e | ||
|
|
f2fde12737 | ||
|
|
e7ed74de0c | ||
|
|
33535abdd7 | ||
|
|
0d8643dfe3 | ||
|
|
50d078f78e | ||
|
|
edbff4e17f | ||
|
|
b4452d7ff3 | ||
|
|
fd32744bf6 | ||
|
|
657306c1c7 | ||
|
|
2c800b471b | ||
|
|
2787823f86 | ||
|
|
29693f99c5 | ||
|
|
a5f419aad9 | ||
|
|
430a8c4a7f | ||
|
|
a7a44b50f9 | ||
|
|
3b4bf490bc | ||
|
|
abe75d0347 | ||
|
|
eb79552ba3 | ||
|
|
e2011b8d24 | ||
|
|
ec41a6f367 | ||
|
|
9b1bed6982 | ||
|
|
8584877739 | ||
|
|
410dd407b3 | ||
|
|
a0bba2c4ba | ||
|
|
8aa508bf6d | ||
|
|
642155b1bc | ||
|
|
bacd288fe7 | ||
|
|
29e28038b8 | ||
|
|
7ce5293ad3 | ||
|
|
b253d8ee12 | ||
|
|
02575b51e2 | ||
|
|
d8d365b497 | ||
|
|
1ac38dc0ad | ||
|
|
62e57ffa97 | ||
|
|
81e6b2e695 | ||
|
|
ae99fab18e | ||
|
|
99ed6bd69b | ||
|
|
79ab6f9705 | ||
|
|
80a9cb2af1 | ||
|
|
c4a73f9a24 | ||
|
|
8fa2377e1e | ||
|
|
dbe664d18f | ||
|
|
5717ab612a | ||
|
|
f6b79ef038 | ||
|
|
cc52d19108 | ||
|
|
a1d595ce8a | ||
|
|
23f502a381 |
36
LICENSES.MD
36
LICENSES.MD
@@ -1,20 +1,16 @@
|
||||
| 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 2016–2024 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.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 |
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.332"/>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.3"/>
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5"/>
|
||||
<PackageReference Include="YamlDotNet" Version="16.3.0"/>
|
||||
|
||||
2
TTT.sln
2
TTT.sln
@@ -29,7 +29,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stats", "TTT\Stats\Stats.cs
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpecialRound", "TTT\SpecialRound\SpecialRound.csproj", "{5092069A-3CFA-41C8-B685-341040AB435C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpecialRoundAPI", "SpecialRoundAPI\SpecialRoundAPI\SpecialRoundAPI.csproj", "{360FEF16-54DA-42EE-995A-3D31C699287D}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpecialRoundAPI", "TTT\SpecialRoundAPI\SpecialRoundAPI.csproj", "{360FEF16-54DA-42EE-995A-3D31C699287D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
||||
@@ -21,4 +21,55 @@ public interface IPlayerFinder {
|
||||
var matches = GetOnline().Where(p => p.Name.Contains(name)).ToList();
|
||||
return matches.Count == 1 ? matches[0] : null;
|
||||
}
|
||||
|
||||
List<IOnlinePlayer> GetMulti(string query, out string name,
|
||||
IOnlinePlayer? executor = null) {
|
||||
var result = query switch {
|
||||
"@all" => GetOnline().ToList(),
|
||||
"@me" => executor != null ? new List<IOnlinePlayer> { executor } : [],
|
||||
"@!me" => executor != null ?
|
||||
GetOnline().Where(p => p.Id != executor.Id).ToList() :
|
||||
GetOnline().ToList(),
|
||||
_ => GetSingle(query) != null ?
|
||||
new List<IOnlinePlayer> { GetSingle(query)! } : []
|
||||
};
|
||||
|
||||
name = "no players found";
|
||||
name = query switch {
|
||||
"@all" => "all players",
|
||||
"@me" => executor != null ? executor.Name : "no one",
|
||||
"@!me" => executor != null ?
|
||||
$"all players except {executor.Name}" :
|
||||
"all players",
|
||||
_ => GetSingle(query) != null ?
|
||||
GetSingle(query)!.Name :
|
||||
"no players found"
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
IOnlinePlayer? GetSingle(string query) {
|
||||
if (query.StartsWith("#")) {
|
||||
var id = query[1..];
|
||||
var byId = GetPlayerById(id);
|
||||
if (byId != null) return byId;
|
||||
var byName = GetOnline().FirstOrDefault(p => p.Name == id);
|
||||
return byName;
|
||||
}
|
||||
|
||||
var byNameExact = GetOnline().FirstOrDefault(p => p.Name == query);
|
||||
if (byNameExact != null) return byNameExact;
|
||||
|
||||
var contains = GetOnline().Where(p => p.Name.Contains(query)).ToList();
|
||||
|
||||
if (contains.Count == 1) return contains[0];
|
||||
|
||||
contains = GetOnline()
|
||||
.Where(p
|
||||
=> p.Name.Contains(query, StringComparison.InvariantCultureIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
return contains.Count == 1 ? contains[0] : null;
|
||||
}
|
||||
}
|
||||
5
TTT/CS2/API/Items/ITripwireActivator.cs
Normal file
5
TTT/CS2/API/Items/ITripwireActivator.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace TTT.CS2.API.Items;
|
||||
|
||||
public interface ITripwireActivator {
|
||||
public void ActivateTripwire(TripwireInstance tripwire);
|
||||
}
|
||||
6
TTT/CS2/API/Items/ITripwireTracker.cs
Normal file
6
TTT/CS2/API/Items/ITripwireTracker.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace TTT.CS2.API.Items;
|
||||
|
||||
public interface ITripwireTracker {
|
||||
public List<TripwireInstance> ActiveTripwires { get; }
|
||||
void RemoveTripwire(TripwireInstance instance);
|
||||
}
|
||||
13
TTT/CS2/API/Items/TripwireInstance.cs
Normal file
13
TTT/CS2/API/Items/TripwireInstance.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
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) {
|
||||
public override string ToString() {
|
||||
return
|
||||
$"TripwireInstance(Owner={owner}, StartPos={StartPos}, EndPos={EndPos})";
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SpecialRoundAPI\SpecialRoundAPI\SpecialRoundAPI.csproj"/>
|
||||
<ProjectReference Include="..\API\API.csproj"/>
|
||||
<ProjectReference Include="..\Game\Game.csproj"/>
|
||||
<ProjectReference Include="..\Karma\Karma.csproj"/>
|
||||
<ProjectReference Include="..\ShopAPI\ShopAPI.csproj"/>
|
||||
<ProjectReference Include="..\SpecialRoundAPI\SpecialRoundAPI.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -66,26 +66,26 @@ 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>();
|
||||
|
||||
// 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<NameUpdater>();
|
||||
// collection.AddModBehavior<EntityTargetHandlers>();
|
||||
|
||||
// Damage Cancelers
|
||||
collection.AddModBehavior<OutOfRoundCanceler>();
|
||||
@@ -95,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>();
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ShopAPI;
|
||||
using ShopAPI.Events;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Player;
|
||||
|
||||
namespace TTT.CS2.Command.Test;
|
||||
@@ -33,25 +31,15 @@ public class GiveItemCommand(IServiceProvider provider) : ICommand {
|
||||
return Task.FromResult(CommandResult.ERROR);
|
||||
}
|
||||
|
||||
var target = executor;
|
||||
List<IOnlinePlayer> targets = [executor];
|
||||
|
||||
Server.NextWorldUpdateAsync(() => {
|
||||
if (info.ArgCount == 3) {
|
||||
var result = finder.GetPlayerByName(info.Args[2]);
|
||||
if (result == null) {
|
||||
info.ReplySync($"Player '{info.Args[2]}' not found.");
|
||||
return;
|
||||
}
|
||||
var name = executor.Name;
|
||||
if (info.ArgCount == 3)
|
||||
targets = finder.GetMulti(info.Args[2], out name, executor);
|
||||
foreach (var player in targets) shop.GiveItem(player, item);
|
||||
|
||||
target = result;
|
||||
}
|
||||
|
||||
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}.");
|
||||
info.ReplySync($"Gave item '{item.Name}' to {name}.");
|
||||
});
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ public class SetRoleCommand(IServiceProvider provider) : ICommand {
|
||||
|
||||
private readonly IEventBus bus = provider.GetRequiredService<IEventBus>();
|
||||
|
||||
private readonly IPlayerFinder finder =
|
||||
provider.GetRequiredService<IPlayerFinder>();
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public string Id => "setrole";
|
||||
@@ -24,7 +27,10 @@ public class SetRoleCommand(IServiceProvider provider) : ICommand {
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
if (executor == null) return Task.FromResult(CommandResult.PLAYER_ONLY);
|
||||
|
||||
IRole roleToAssign = new TraitorRole(provider);
|
||||
// IOnlinePlayer targetPlayer = executor;
|
||||
List<IOnlinePlayer> targets = [executor];
|
||||
var targetName = executor.Name;
|
||||
IRole roleToAssign = new TraitorRole(provider);
|
||||
if (info.ArgCount == 2)
|
||||
switch (info.Args[1].ToLowerInvariant()) {
|
||||
case "d" or "det" or "detective" or "ct":
|
||||
@@ -33,18 +39,29 @@ public class SetRoleCommand(IServiceProvider provider) : ICommand {
|
||||
case "i" or "inn" or "innocent":
|
||||
roleToAssign = new InnocentRole(provider);
|
||||
break;
|
||||
default:
|
||||
targets = finder.GetMulti(info.Args[1], out targetName, executor);
|
||||
break;
|
||||
}
|
||||
|
||||
if (info.ArgCount == 3)
|
||||
targets = finder.GetMulti(info.Args[2], out targetName, executor);
|
||||
|
||||
Server.NextWorldUpdate(() => {
|
||||
var ev = new PlayerRoleAssignEvent(executor, roleToAssign);
|
||||
bus.Dispatch(ev);
|
||||
if (ev.IsCanceled) {
|
||||
info.ReplySync("Role assignment was canceled.");
|
||||
return;
|
||||
foreach (var player in targets) {
|
||||
var ev = new PlayerRoleAssignEvent(player, roleToAssign);
|
||||
bus.Dispatch(ev);
|
||||
if (ev.IsCanceled) {
|
||||
info.ReplySync("Role assignment was canceled.");
|
||||
return;
|
||||
}
|
||||
|
||||
assigner.Write(player, [ev.Role]);
|
||||
ev.Role.OnAssign(player);
|
||||
}
|
||||
|
||||
assigner.Write(executor, [ev.Role]);
|
||||
ev.Role.OnAssign(executor);
|
||||
info.ReplySync(
|
||||
"Assigned " + roleToAssign.Name + " to " + targetName + ".");
|
||||
});
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ using TTT.API.Player;
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class SpecialRoundCommand(IServiceProvider provider) : ICommand {
|
||||
private readonly ISpecialRoundStarter tracker =
|
||||
provider.GetRequiredService<ISpecialRoundStarter>();
|
||||
private readonly ISpecialRoundStarter? tracker =
|
||||
provider.GetService<ISpecialRoundStarter>();
|
||||
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
@@ -18,17 +18,22 @@ public class SpecialRoundCommand(IServiceProvider provider) : ICommand {
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
if (tracker == null) {
|
||||
info.ReplySync("Special round tracker is not available.");
|
||||
return Task.FromResult(CommandResult.ERROR);
|
||||
}
|
||||
|
||||
if (info.ArgCount == 1) {
|
||||
tracker.TryStartSpecialRound(null);
|
||||
Server.NextWorldUpdate(() => tracker.TryStartSpecialRound());
|
||||
info.ReplySync("Started a random special round.");
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
|
||||
var rounds = provider.GetServices<ITerrorModule>()
|
||||
.OfType<AbstractSpecialRound>()
|
||||
.ToDictionary(r => r.Name.ToLower(), r => r);
|
||||
.ToDictionary(r => r.Name.ToLower().Replace(" ", ""), r => r);
|
||||
|
||||
var roundName = info.Args[1].ToLower();
|
||||
var roundName = string.Join("", info.Args.Skip(1)).ToLower();
|
||||
if (!rounds.TryGetValue(roundName, out var round)) {
|
||||
info.ReplySync($"No special round found with name '{roundName}'.");
|
||||
foreach (var name in rounds.Keys) info.ReplySync($"- {name}");
|
||||
@@ -36,7 +41,7 @@ public class SpecialRoundCommand(IServiceProvider provider) : ICommand {
|
||||
}
|
||||
|
||||
Server.NextWorldUpdate(() => {
|
||||
tracker.TryStartSpecialRound(round);
|
||||
tracker.TryStartSpecialRound([round]);
|
||||
info.ReplySync($"Started special round '{roundName}'.");
|
||||
});
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
|
||||
@@ -48,17 +48,17 @@ public class CS2GameConfig : IStorage<TTTConfig>, IPluginModule {
|
||||
|
||||
public static readonly FakeConVar<int> CV_TRAITOR_ARMOR = new(
|
||||
"css_ttt_rolearmor_traitor",
|
||||
"Amount of armor to give to traitors at start of round", 100,
|
||||
"Amount of armor to give to traitors at start of round", 0,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 1000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_DETECTIVE_ARMOR = new(
|
||||
"css_ttt_rolearmor_detective",
|
||||
"Amount of armor to give to detectives at start of round", 100,
|
||||
"Amount of armor to give to detectives at start of round", 0,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 1000));
|
||||
|
||||
public static readonly FakeConVar<int> CV_INNOCENT_ARMOR = new(
|
||||
"css_ttt_rolearmor_innocent",
|
||||
"Amount of armor to give to innocents at start of round", 100,
|
||||
"Amount of armor to give to innocents at start of round", 0,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 1000));
|
||||
|
||||
public static readonly FakeConVar<string> CV_TRAITOR_WEAPONS = new(
|
||||
|
||||
@@ -44,7 +44,7 @@ public class CS2KarmaConfig : IStorage<KarmaConfig>, IPluginModule {
|
||||
// Karma deltas
|
||||
public static readonly FakeConVar<int> CV_INNO_ON_TRAITOR = new(
|
||||
"css_ttt_karma_inno_on_traitor",
|
||||
"Karma gained when Innocent kills a Traitor", 4, ConVarFlags.FCVAR_NONE,
|
||||
"Karma gained when Innocent kills a Traitor", 2, ConVarFlags.FCVAR_NONE,
|
||||
new RangeValidator<int>(-50, 50));
|
||||
|
||||
public static readonly FakeConVar<int> CV_TRAITOR_ON_DETECTIVE = new(
|
||||
@@ -55,32 +55,32 @@ public class CS2KarmaConfig : IStorage<KarmaConfig>, IPluginModule {
|
||||
public static readonly FakeConVar<int> CV_INNO_ON_INNO_VICTIM = new(
|
||||
"css_ttt_karma_inno_on_inno_victim",
|
||||
"Karma gained or lost when Innocent kills another Innocent who was a victim",
|
||||
-1, ConVarFlags.FCVAR_NONE, new RangeValidator<int>(-50, 50));
|
||||
-2, ConVarFlags.FCVAR_NONE, new RangeValidator<int>(-50, 50));
|
||||
|
||||
public static readonly FakeConVar<int> CV_INNO_ON_INNO = new(
|
||||
"css_ttt_karma_inno_on_inno",
|
||||
"Karma lost when Innocent kills another Innocent", -5,
|
||||
"Karma lost when Innocent kills another Innocent", -8,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(-50, 50));
|
||||
|
||||
public static readonly FakeConVar<int> CV_TRAITOR_ON_TRAITOR = new(
|
||||
"css_ttt_karma_traitor_on_traitor",
|
||||
"Karma lost when Traitor kills another Traitor", -6, ConVarFlags.FCVAR_NONE,
|
||||
new RangeValidator<int>(-50, 50));
|
||||
"Karma lost when Traitor kills another Traitor", -12,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(-50, 50));
|
||||
|
||||
public static readonly FakeConVar<int> CV_INNO_ON_DETECTIVE = new(
|
||||
"css_ttt_karma_inno_on_detective",
|
||||
"Karma lost when Innocent kills a Detective", -8, ConVarFlags.FCVAR_NONE,
|
||||
"Karma lost when Innocent kills a Detective", -15, ConVarFlags.FCVAR_NONE,
|
||||
new RangeValidator<int>(-50, 50));
|
||||
|
||||
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() { }
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace TTT.CS2.Configs.ShopItems;
|
||||
|
||||
public class CS2TaserConfig : IStorage<TaserConfig>, IPluginModule {
|
||||
public static readonly FakeConVar<int> CV_PRICE = new(
|
||||
"css_ttt_shop_taser_price", "Price of the Taser item", 120,
|
||||
"css_ttt_shop_taser_price", "Price of the Taser item", 110,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 10000));
|
||||
|
||||
public static readonly FakeConVar<string> CV_WEAPON = new(
|
||||
|
||||
116
TTT/CS2/Configs/ShopItems/CS2TripwireConfig.cs
Normal file
116
TTT/CS2/Configs/ShopItems/CS2TripwireConfig.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
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<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", 50000f,
|
||||
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/defuse-detection", 10f,
|
||||
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 (0–255)", 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 (0–255)", 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 (0–255)", 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 (0–255)", 32,
|
||||
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 static readonly FakeConVar<float> CV_DEFUSE_TIME = new(
|
||||
"css_ttt_shop_tripwire_defuse_time",
|
||||
"Time required to fully defuse the Tripwire (in seconds)", 6f,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 30f));
|
||||
|
||||
public static readonly FakeConVar<float> CV_DEFUSE_RATE = new(
|
||||
"css_ttt_shop_tripwire_defuse_rate",
|
||||
"Rate at which Tripwire defuses are processed (in seconds)", 0.5f,
|
||||
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0.01f, 5f));
|
||||
|
||||
public static readonly FakeConVar<int> CV_DEFUSE_REWARD = new(
|
||||
"css_ttt_shop_tripwire_defuse_reward",
|
||||
"Amount of money rewarded to a player for successfully defusing a tripwire",
|
||||
20, 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_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,
|
||||
DefuseTime = TimeSpan.FromSeconds(CV_DEFUSE_TIME.Value),
|
||||
DefuseRate = TimeSpan.FromSeconds(CV_DEFUSE_RATE.Value),
|
||||
DefuseReward = CV_DEFUSE_REWARD.Value
|
||||
};
|
||||
|
||||
return Task.FromResult<TripwireConfig?>(cfg);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,7 +50,19 @@ 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));
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Game;
|
||||
@@ -20,7 +19,8 @@ public class MapChangeCausesEndListener(IServiceProvider provider)
|
||||
}
|
||||
|
||||
private void onMapChange(string mapName) {
|
||||
if (games.ActiveGame is not { State: State.IN_PROGRESS or State.COUNTDOWN })
|
||||
return;
|
||||
games.ActiveGame?.EndGame(new EndReason("Map Change"));
|
||||
Server.PrintToConsole("Detected map change, ending active game.");
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,20 @@ using System.Reactive.Concurrency;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Modules.UserMessages;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ShopAPI;
|
||||
using ShopAPI.Configs.Traitor;
|
||||
using SpecialRoundAPI;
|
||||
using TTT.API;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Storage;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.CS2.RayTrace.Class;
|
||||
using TTT.Game.Events.Body;
|
||||
using TTT.Game.Events.Game;
|
||||
using TTT.Game.Events.Player;
|
||||
@@ -49,11 +53,16 @@ public class PoisonShotsListener(IServiceProvider provider)
|
||||
foreach (var timer in poisonTimers) timer.Dispose();
|
||||
}
|
||||
|
||||
public void Start(BasePlugin? plugin) {
|
||||
base.Start();
|
||||
plugin?.HookUserMessage(452, onWeaponSound);
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[GameEventHandler]
|
||||
public HookResult OnFire(EventWeaponFire ev, GameEventInfo _) {
|
||||
if (ev.Userid == null) return HookResult.Continue;
|
||||
if (!Tag.GUNS.Contains(ev.Weapon)) return HookResult.Continue;
|
||||
if (!Tag.PISTOLS.Contains(ev.Weapon)) return HookResult.Continue;
|
||||
if (converter.GetPlayer(ev.Userid) is not IOnlinePlayer player)
|
||||
return HookResult.Continue;
|
||||
var remainingShots = usePoisonShot(player);
|
||||
@@ -156,6 +165,47 @@ public class PoisonShotsListener(IServiceProvider provider)
|
||||
return 0;
|
||||
}
|
||||
|
||||
private HookResult onWeaponSound(UserMessage msg) {
|
||||
var defIndex = msg.ReadUInt("item_def_index");
|
||||
|
||||
if (!WeaponSoundIndex.PISTOLS.Contains(defIndex))
|
||||
return HookResult.Continue;
|
||||
var splits = msg.DebugString.Split("\n");
|
||||
if (splits.Length < 5) return HookResult.Continue;
|
||||
var angleLines = msg.DebugString.Split("\n")[1..4]
|
||||
.Select(s => s.Trim())
|
||||
.ToList();
|
||||
if (!angleLines[0].Contains('x') || !angleLines[1].Contains('y')
|
||||
|| !angleLines[2].Contains('z'))
|
||||
return HookResult.Continue;
|
||||
var x = float.Parse(angleLines[0].Split(' ')[1]);
|
||||
var y = float.Parse(angleLines[1].Split(' ')[1]);
|
||||
var z = float.Parse(angleLines[2].Split(' ')[1]);
|
||||
var vec = new Vector(x, y, z);
|
||||
var player = findPlayerByCoord(vec);
|
||||
|
||||
if (player == null) return HookResult.Continue;
|
||||
if (converter.GetPlayer(player) is not IOnlinePlayer apiPlayer)
|
||||
return HookResult.Continue;
|
||||
|
||||
if (poisonShots.TryGetValue(apiPlayer, out var shots) && shots > 0) {
|
||||
msg.Recipients.Clear();
|
||||
return HookResult.Handled;
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private CCSPlayerController? findPlayerByCoord(Vector vec) {
|
||||
foreach (var pl in Utilities.GetPlayers()) {
|
||||
var origin = pl.GetEyePosition();
|
||||
if (origin == null) continue;
|
||||
var dist = vec.DistanceSquared(origin);
|
||||
if (dist < 1) return pl;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[EventHandler]
|
||||
|
||||
@@ -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;
|
||||
@@ -30,9 +30,6 @@ public class DamageStation(IServiceProvider provider)
|
||||
.GetResult() ?? new DamageStationConfig()), IListener {
|
||||
private readonly IEventBus bus = provider.GetRequiredService<IEventBus>();
|
||||
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
private readonly IPlayerFinder finder =
|
||||
provider.GetRequiredService<IPlayerFinder>();
|
||||
|
||||
@@ -46,24 +43,28 @@ public class DamageStation(IServiceProvider provider)
|
||||
override protected void onInterval() {
|
||||
var players = finder.GetOnline();
|
||||
var toRemove = new List<CPhysicsPropMultiplayer>();
|
||||
foreach (var (prop, info) in props) {
|
||||
if (_Config.TotalHealthGiven != 0 && Math.Abs(info.HealthGiven)
|
||||
> Math.Abs(_Config.TotalHealthGiven)) {
|
||||
toRemove.Add(prop);
|
||||
continue;
|
||||
}
|
||||
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();
|
||||
|
||||
if (!prop.IsValid || prop.AbsOrigin == null) {
|
||||
// accumulate contributions per player: ApiPlayer -> list of (stationInfo, damage, gamePlayer)
|
||||
var playerDamageMap =
|
||||
new Dictionary<IOnlinePlayer, List<(StationInfo info, int damage,
|
||||
CCSPlayerController gamePlayer)>>();
|
||||
|
||||
foreach (var (prop, info) in Props) {
|
||||
if (_Config.TotalHealthGiven != 0 && Math.Abs(info.HealthGiven)
|
||||
> Math.Abs(_Config.TotalHealthGiven) || !prop.IsValid
|
||||
|| prop.AbsOrigin == null) {
|
||||
toRemove.Add(prop);
|
||||
continue;
|
||||
}
|
||||
|
||||
var propPos = prop.AbsOrigin;
|
||||
|
||||
var playerMapping = players.Select(p
|
||||
=> (ApiPlayer: p, GamePlayer: converter.GetPlayer(p)))
|
||||
.Where(m => m.GamePlayer != null);
|
||||
|
||||
var playerDists = playerMapping
|
||||
.Select(t => (t.ApiPlayer, Origin: t.GamePlayer!.Pawn.Value?.AbsOrigin,
|
||||
t.GamePlayer))
|
||||
@@ -74,36 +75,75 @@ public class DamageStation(IServiceProvider provider)
|
||||
.ToList();
|
||||
|
||||
foreach (var (player, dist, gamePlayer) in playerDists) {
|
||||
if ((gamePlayer.Buttons & PlayerButtons.Walk) != PlayerButtons.Walk) {
|
||||
gamePlayer.EmitSound("Player.DamageFall", null, 0.2f);
|
||||
if (Roles.GetRoles(player).Any(r => r is TraitorRole)) continue;
|
||||
}
|
||||
|
||||
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}]" };
|
||||
if (damageAmount <= 0) continue;
|
||||
|
||||
bus.Dispatch(dmgEvent);
|
||||
|
||||
damageAmount = -dmgEvent.DmgDealt;
|
||||
|
||||
if (player.Health + damageAmount <= 0) {
|
||||
killedWithStation[player.Id] = info;
|
||||
var playerDeath = new PlayerDeathEvent(player)
|
||||
.WithKiller(info.Owner as IOnlinePlayer)
|
||||
.WithWeapon($"[{Name}]");
|
||||
bus.Dispatch(playerDeath);
|
||||
if (!playerDamageMap.TryGetValue(player, out var list)) {
|
||||
list = [];
|
||||
playerDamageMap[player] = list;
|
||||
}
|
||||
|
||||
player.Health += damageAmount;
|
||||
info.HealthGiven += damageAmount;
|
||||
list.Add((info, damageAmount, gamePlayer));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var prop in toRemove) props.Remove(prop);
|
||||
// Apply accumulated damage per player once
|
||||
applyDamage(playerDamageMap);
|
||||
|
||||
// remove invalid/expired props
|
||||
foreach (var prop in toRemove) Props.Remove(prop);
|
||||
}
|
||||
|
||||
private void applyDamage(
|
||||
Dictionary<IOnlinePlayer, List<(StationInfo info, int damage,
|
||||
CCSPlayerController gamePlayer)>> playerDamageMap) {
|
||||
foreach (var kv in playerDamageMap) {
|
||||
var player = kv.Key;
|
||||
var contribs = kv.Value;
|
||||
|
||||
var totalDamage = contribs.Sum(c => c.damage);
|
||||
|
||||
if (totalDamage <= 0) continue;
|
||||
|
||||
// choose the station that contributed the most damage to attribute the kill to
|
||||
var dominantInfo = contribs.OrderByDescending(c => c.damage).First().info;
|
||||
var gamePlayer = contribs.First().gamePlayer;
|
||||
|
||||
// dispatch single PlayerDamagedEvent with total damage
|
||||
var dmgEvent = new PlayerDamagedEvent(player,
|
||||
dominantInfo.Owner as IOnlinePlayer, totalDamage) {
|
||||
Weapon = $"[{Name}]"
|
||||
};
|
||||
|
||||
bus.Dispatch(dmgEvent);
|
||||
|
||||
totalDamage = dmgEvent.DmgDealt;
|
||||
|
||||
// if this will kill the player, attribute death to the dominant station
|
||||
if (player.Health - totalDamage <= 0) {
|
||||
killedWithStation[player.Id] = dominantInfo;
|
||||
var playerDeath = new PlayerDeathEvent(player)
|
||||
.WithKiller(dominantInfo.Owner as IOnlinePlayer)
|
||||
.WithWeapon($"[{Name}]");
|
||||
bus.Dispatch(playerDeath);
|
||||
}
|
||||
|
||||
gamePlayer.EmitSound("Player.DamageFall", SELF(gamePlayer.Slot), 0.2f);
|
||||
|
||||
// apply damage to player's health
|
||||
player.Health -= totalDamage;
|
||||
|
||||
// update each station's HealthGiven by its own contribution
|
||||
foreach (var (info, damage, _) in contribs) info.HealthGiven += damage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static RecipientFilter SELF(int slot) {
|
||||
return new RecipientFilter(slot);
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
|
||||
@@ -30,7 +30,29 @@ public class HealthStation(IServiceProvider provider)
|
||||
override protected void onInterval() {
|
||||
var players = Utilities.GetPlayers();
|
||||
var toRemove = new List<CPhysicsPropMultiplayer>();
|
||||
foreach (var (prop, info) in props) {
|
||||
|
||||
// build per-player potential heal contributions and gather props to remove
|
||||
var perPlayerContrib = BuildPerPlayerContributions(players, toRemove);
|
||||
|
||||
// apply the accumulated heals in a single pass per player
|
||||
applyAccumulatedHeals(perPlayerContrib);
|
||||
|
||||
// remove invalid/expired props
|
||||
foreach (var prop in toRemove) Props.Remove(prop);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scan all props and build a map: Player -> list of (StationInfo, potentialHeal).
|
||||
/// Also fills toRemove for invalid/expired props.
|
||||
/// </summary>
|
||||
private
|
||||
Dictionary<CCSPlayerController, List<(StationInfo info, int potential)>>
|
||||
BuildPerPlayerContributions(IReadOnlyList<CCSPlayerController> players,
|
||||
List<CPhysicsPropMultiplayer> toRemove) {
|
||||
var result =
|
||||
new Dictionary<CCSPlayerController, List<(StationInfo, int)>>();
|
||||
|
||||
foreach (var (prop, info) in Props) {
|
||||
if (_Config.TotalHealthGiven != 0
|
||||
&& Math.Abs(info.HealthGiven) > _Config.TotalHealthGiven) {
|
||||
toRemove.Add(prop);
|
||||
@@ -52,19 +74,130 @@ public class HealthStation(IServiceProvider provider)
|
||||
.ToList();
|
||||
|
||||
foreach (var (player, dist) in playerDists) {
|
||||
var maxHp = player.Pawn.Value?.MaxHealth ?? 100;
|
||||
var healthScale = 1.0 - dist / _Config.MaxRange;
|
||||
var maxHealAmo =
|
||||
(int)Math.Ceiling(_Config.HealthIncrements * healthScale);
|
||||
var newHealth = Math.Min(player.GetHealth() + maxHealAmo, maxHp);
|
||||
var healthGiven = newHealth - player.GetHealth();
|
||||
player.SetHealth(newHealth);
|
||||
info.HealthGiven += healthGiven;
|
||||
var potentialHeal = ComputePotentialHeal(dist);
|
||||
if (potentialHeal <= 0) continue;
|
||||
|
||||
if (healthGiven > 0) player.EmitSound("HealthShot.Pickup", null, 0.1f);
|
||||
if (!result.TryGetValue(player, out var list)) {
|
||||
list = [];
|
||||
result[player] = list;
|
||||
}
|
||||
|
||||
list.Add((info, potentialHeal));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var prop in toRemove) props.Remove(prop);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute potential heal from a station given the distance.
|
||||
/// Mirrors your original logic: ceil(HealthIncrements * healthScale).
|
||||
/// </summary>
|
||||
private int ComputePotentialHeal(double dist) {
|
||||
var healthScale = 1.0 - dist / _Config.MaxRange;
|
||||
return (int)Math.Ceiling(_Config.HealthIncrements * healthScale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply heals to each player once per tick.
|
||||
/// Distributes the actual heal proportionally across contributing stations,
|
||||
/// updates station.Info.HealthGiven and emits a single pickup sound if needed.
|
||||
/// </summary>
|
||||
private void applyAccumulatedHeals(
|
||||
Dictionary<CCSPlayerController, List<(StationInfo info, int potential)>>
|
||||
perPlayerContrib) {
|
||||
foreach (var kv in perPlayerContrib) {
|
||||
var player = kv.Key;
|
||||
var contribs = kv.Value;
|
||||
|
||||
var maxHp = player.Pawn.Value?.MaxHealth ?? 100;
|
||||
var currentHp = player.GetHealth();
|
||||
if (currentHp >= maxHp) continue;
|
||||
|
||||
var totalPotential = contribs.Sum(c => c.potential);
|
||||
if (totalPotential <= 0) continue;
|
||||
|
||||
var totalHealAvailable = Math.Min(totalPotential, maxHp - currentHp);
|
||||
if (totalHealAvailable <= 0) continue;
|
||||
|
||||
var potentials = contribs.Select(c => c.potential).ToList();
|
||||
var allocations =
|
||||
allocateProportionalInteger(totalHealAvailable, potentials);
|
||||
|
||||
// safety clamp: never allocate more than a station's potential
|
||||
for (var i = 0; i < allocations.Count; i++)
|
||||
if (allocations[i] > potentials[i])
|
||||
allocations[i] = potentials[i];
|
||||
|
||||
// if clamping reduced the total, try to redistribute remaining amount
|
||||
var allocatedSum = allocations.Sum();
|
||||
if (allocatedSum < totalHealAvailable) {
|
||||
var remaining = totalHealAvailable - allocatedSum;
|
||||
for (var i = 0; i < allocations.Count && remaining > 0; i++) {
|
||||
var headroom = potentials[i] - allocations[i];
|
||||
if (headroom <= 0) continue;
|
||||
var give = Math.Min(headroom, remaining);
|
||||
allocations[i] += give;
|
||||
remaining -= give;
|
||||
}
|
||||
|
||||
allocatedSum = allocations.Sum();
|
||||
}
|
||||
|
||||
// apply heal to player
|
||||
var actualAllocated = Math.Min(allocatedSum, maxHp - currentHp);
|
||||
if (actualAllocated <= 0) continue;
|
||||
|
||||
var newHealth = Math.Min(currentHp + actualAllocated, maxHp);
|
||||
player.SetHealth(newHealth);
|
||||
|
||||
// update station HealthGiven
|
||||
for (var i = 0; i < allocations.Count; i++) {
|
||||
var info = contribs[i].info;
|
||||
var allocated = allocations[i];
|
||||
if (allocated > 0) info.HealthGiven += allocated;
|
||||
}
|
||||
|
||||
// emit a single pickup sound if any heal applied
|
||||
player.EmitSound("HealthShot.Pickup", null, 0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Proportionally distribute an integer total across integer potentials.
|
||||
/// Uses floor of shares and assigns leftover to largest fractional remainders.
|
||||
/// Returns a list of allocations with same length as potentials.
|
||||
/// </summary>
|
||||
private List<int>
|
||||
allocateProportionalInteger(int total, List<int> potentials) {
|
||||
var allocations = new List<int>(new int[potentials.Count]);
|
||||
var remainders = new List<(int idx, double rem)>();
|
||||
var sumBase = 0;
|
||||
var totalPotential = potentials.Sum();
|
||||
|
||||
if (totalPotential <= 0) return allocations;
|
||||
|
||||
for (var i = 0; i < potentials.Count; i++) {
|
||||
var potential = potentials[i];
|
||||
var share = (double)potential / totalPotential * total;
|
||||
var baseAlloc = (int)Math.Floor(share);
|
||||
var rem = share - baseAlloc;
|
||||
allocations[i] = baseAlloc;
|
||||
sumBase += baseAlloc;
|
||||
remainders.Add((i, rem));
|
||||
}
|
||||
|
||||
var leftover = total - sumBase;
|
||||
if (leftover <= 0) return allocations;
|
||||
|
||||
remainders = remainders.OrderByDescending(r => r.rem).ToList();
|
||||
var idx = 0;
|
||||
while (leftover > 0 && idx < remainders.Count) {
|
||||
allocations[remainders[idx].idx] += 1;
|
||||
leftover--;
|
||||
idx++;
|
||||
}
|
||||
|
||||
return allocations;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public abstract class StationItem<T>(IServiceProvider provider,
|
||||
|
||||
private readonly long PROP_SIZE_SQUARED = 700;
|
||||
|
||||
protected readonly Dictionary<CPhysicsPropMultiplayer, StationInfo> props =
|
||||
protected readonly Dictionary<CPhysicsPropMultiplayer, StationInfo> Props =
|
||||
new();
|
||||
|
||||
private readonly IScheduler scheduler =
|
||||
@@ -60,7 +60,7 @@ public abstract class StationItem<T>(IServiceProvider provider,
|
||||
public HookResult OnBulletImpact(EventBulletImpact ev, GameEventInfo info) {
|
||||
var hitVec = new Vector(ev.X, ev.Y, ev.Z);
|
||||
|
||||
var nearest = props
|
||||
var nearest = Props
|
||||
.Select(kv => (kv.Key, kv.Value,
|
||||
Distance: kv.Key.AbsOrigin!.DistanceSquared(hitVec)))
|
||||
.Where(t => t.Key is { IsValid: true, AbsOrigin: not null })
|
||||
@@ -76,7 +76,7 @@ public abstract class StationItem<T>(IServiceProvider provider,
|
||||
|
||||
if (nearest.Value.Health <= 0) {
|
||||
nearest.Key.AcceptInput("Kill");
|
||||
props.Remove(nearest.Key);
|
||||
Props.Remove(nearest.Key);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ public abstract class StationItem<T>(IServiceProvider provider,
|
||||
|
||||
if (prop == null) return;
|
||||
|
||||
props[prop] = new StationInfo(prop, _Config.StationHealth, player);
|
||||
Props[prop] = new StationInfo(prop, _Config.StationHealth, player);
|
||||
|
||||
prop.SetModel("models/props/cs_office/microwave.vmdl");
|
||||
prop.DispatchSpawn();
|
||||
|
||||
47
TTT/CS2/Items/Tripwire/TripwireDamageListener.cs
Normal file
47
TTT/CS2/Items/Tripwire/TripwireDamageListener.cs
Normal 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 {
|
||||
private readonly ITripwireActivator? tripwireActivator =
|
||||
provider.GetRequiredService<ITripwireActivator>();
|
||||
|
||||
private readonly ITripwireTracker? tripwires =
|
||||
provider.GetService<ITripwireTracker>();
|
||||
|
||||
private TripwireConfig config
|
||||
=> provider.GetService<IStorage<TripwireConfig>>()
|
||||
?.Load()
|
||||
.GetAwaiter()
|
||||
.GetResult() ?? new TripwireConfig();
|
||||
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
[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;
|
||||
}
|
||||
}
|
||||
129
TTT/CS2/Items/Tripwire/TripwireDefuserListener.cs
Normal file
129
TTT/CS2/Items/Tripwire/TripwireDefuserListener.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ShopAPI;
|
||||
using ShopAPI.Configs.Traitor;
|
||||
using TTT.API;
|
||||
using TTT.API.Messages;
|
||||
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.Locale;
|
||||
|
||||
namespace TTT.CS2.Items.Tripwire;
|
||||
|
||||
public class TripwireDefuserListener(IServiceProvider provider)
|
||||
: IPluginModule {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
private readonly IMsgLocalizer locale =
|
||||
provider.GetRequiredService<IMsgLocalizer>();
|
||||
|
||||
private readonly IMessenger messenger =
|
||||
provider.GetRequiredService<IMessenger>();
|
||||
|
||||
private readonly ITripwireTracker? tripwireTracker =
|
||||
provider.GetService<ITripwireTracker>();
|
||||
|
||||
private TripwireConfig config
|
||||
=> provider.GetService<IStorage<TripwireConfig>>()
|
||||
?.Load()
|
||||
.GetAwaiter()
|
||||
.GetResult() ?? new TripwireConfig();
|
||||
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
public void Start(BasePlugin? plugin) {
|
||||
if (tripwireTracker == null) return;
|
||||
plugin
|
||||
?.RegisterListener<
|
||||
CounterStrikeSharp.API.Core.Listeners.OnPlayerButtonsChanged>(
|
||||
onButtonsChanged);
|
||||
}
|
||||
|
||||
private void onButtonsChanged(CCSPlayerController player,
|
||||
PlayerButtons pressed, PlayerButtons released) {
|
||||
if (tripwireTracker == null) return;
|
||||
if ((pressed & PlayerButtons.Use) != PlayerButtons.Use) return;
|
||||
|
||||
var instance = getTargetTripwire(player);
|
||||
if (instance == null) return;
|
||||
|
||||
startDefuseTimer(player, instance);
|
||||
}
|
||||
|
||||
private TripwireInstance? getTargetTripwire(CCSPlayerController player) {
|
||||
if (tripwireTracker == null) return null;
|
||||
var raytrace =
|
||||
player.GetGameTraceByEyePosition(TraceMask.MaskSolid, Contents.NoDraw,
|
||||
player);
|
||||
|
||||
if (raytrace == null || tripwireTracker.ActiveTripwires.Count == 0)
|
||||
return null;
|
||||
var raytracePos = raytrace.Value.EndPos.toVector();
|
||||
|
||||
var closest =
|
||||
tripwireTracker?.ActiveTripwires.MinBy(i
|
||||
=> i.StartPos.DistanceSquared(raytracePos));
|
||||
|
||||
if (closest == null || closest.StartPos.DistanceSquared(raytracePos)
|
||||
> config.TripwireSizeSquared)
|
||||
return null;
|
||||
|
||||
if (player.Pawn.Value?.AbsOrigin == null
|
||||
|| closest.StartPos.DistanceSquared(player.Pawn.Value.AbsOrigin)
|
||||
> config.MaxPlacementDistanceSquared)
|
||||
return null;
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
private void startDefuseTimer(CCSPlayerController player,
|
||||
TripwireInstance instance) {
|
||||
tickDefuse(player, instance, DateTime.Now);
|
||||
}
|
||||
|
||||
private void tickDefuse(CCSPlayerController player, TripwireInstance instance,
|
||||
DateTime startTime) {
|
||||
if (!player.IsValid) return;
|
||||
var apiPlayer = converter.GetPlayer(player);
|
||||
|
||||
if ((player.Buttons & PlayerButtons.Use) != PlayerButtons.Use) {
|
||||
messenger.Message(apiPlayer,
|
||||
locale[TripwireMsgs.SHOP_ITEM_TRIPWIRE_DEFUSING_CANCELED]);
|
||||
return;
|
||||
}
|
||||
|
||||
var progress = (DateTime.Now - startTime) / config.DefuseTime;
|
||||
var timeLeft = config.DefuseTime - config.DefuseTime * progress;
|
||||
|
||||
if (progress >= 1) {
|
||||
instance.TripwireProp.EmitSound("c4.disarmfinish", null, 0.2f, 1.5f);
|
||||
tripwireTracker?.RemoveTripwire(instance);
|
||||
if (apiPlayer is IOnlinePlayer online)
|
||||
provider.GetService<IShop>()
|
||||
?.AddBalance(online, config.DefuseReward, "Tripwire Defusal");
|
||||
return;
|
||||
}
|
||||
|
||||
var target = getTargetTripwire(player);
|
||||
if (target != instance) {
|
||||
messenger.Message(apiPlayer,
|
||||
locale[TripwireMsgs.SHOP_ITEM_TRIPWIRE_DEFUSING_CANCELED]);
|
||||
return;
|
||||
}
|
||||
|
||||
player.PrintToCenter(
|
||||
locale[TripwireMsgs.SHOP_ITEM_TRIPWIRE_DEFUSING(progress, timeLeft)]);
|
||||
var pitch = 1.5f - (float)progress;
|
||||
player.EmitSound("c4.keypressquiet", pitch: pitch);
|
||||
var ticksDelay = (int)Math.Round(64 * config.DefuseRate.TotalSeconds);
|
||||
Server.RunOnTick(Server.TickCount + ticksDelay,
|
||||
() => tickDefuse(player, instance, startTime));
|
||||
}
|
||||
}
|
||||
165
TTT/CS2/Items/Tripwire/TripwireItem.cs
Normal file
165
TTT/CS2/Items/Tripwire/TripwireItem.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
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>();
|
||||
services.AddModBehavior<TripwireDefuserListener>();
|
||||
}
|
||||
}
|
||||
|
||||
public class TripwireItem(IServiceProvider provider)
|
||||
: RoleRestrictedItem<TraitorRole>(provider), IPluginModule, ITripwireTracker {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
private readonly IScheduler scheduler =
|
||||
provider.GetRequiredService<IScheduler>();
|
||||
|
||||
private TripwireConfig config
|
||||
=> Provider.GetService<IStorage<TripwireConfig>>()
|
||||
?.Load()
|
||||
.GetAwaiter()
|
||||
.GetResult() ?? new TripwireConfig();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public List<TripwireInstance> ActiveTripwires { get; } = [];
|
||||
|
||||
public void RemoveTripwire(TripwireInstance instance) {
|
||||
instance.Beam.Remove();
|
||||
instance.TripwireProp.Remove();
|
||||
ActiveTripwires.Remove(instance);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
137
TTT/CS2/Items/Tripwire/TripwireMovementListener.cs
Normal file
137
TTT/CS2/Items/Tripwire/TripwireMovementListener.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
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 Dictionary<string, TripwireInstance> killedWithTripwire =
|
||||
new();
|
||||
|
||||
private readonly ITripwireTracker? tripwireTracker =
|
||||
provider.GetService<ITripwireTracker>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public void ActivateTripwire(TripwireInstance instance) {
|
||||
tripwireTracker?.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 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 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);
|
||||
if (Roles.GetRoles(player).Any(r => r is TraitorRole))
|
||||
damage *= config.FriendlyFireMultiplier;
|
||||
|
||||
return (int)Math.Floor(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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
23
TTT/CS2/Items/Tripwire/TripwireMsgs.cs
Normal file
23
TTT/CS2/Items/Tripwire/TripwireMsgs.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
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));
|
||||
|
||||
public static IMsg SHOP_ITEM_TRIPWIRE_DEFUSING_CANCELED
|
||||
=> MsgFactory.Create(nameof(SHOP_ITEM_TRIPWIRE_DEFUSING_CANCELED));
|
||||
|
||||
public static IMsg
|
||||
SHOP_ITEM_TRIPWIRE_DEFUSING(double progress, TimeSpan time) {
|
||||
return MsgFactory.Create(nameof(SHOP_ITEM_TRIPWIRE_DEFUSING),
|
||||
progress.ToString("P0"), time.TotalSeconds.ToString("F1"));
|
||||
}
|
||||
}
|
||||
@@ -19,24 +19,20 @@ public class MapHookListener(IServiceProvider provider)
|
||||
[UsedImplicitly]
|
||||
[EventHandler(Priority = Priority.MONITOR, IgnoreCanceled = true)]
|
||||
public void OnRoleAssign(PlayerRoleAssignEvent ev) {
|
||||
Server.NextWorldUpdate(() => {
|
||||
var player = converter.GetPlayer(ev.Player);
|
||||
if (player == null) return;
|
||||
var player = converter.GetPlayer(ev.Player);
|
||||
if (player == null) return;
|
||||
|
||||
switch (ev.Role) {
|
||||
case TraitorRole:
|
||||
player.Pawn.Value?.AcceptInput("AddContext", null, null, "TRAITOR:1");
|
||||
break;
|
||||
case DetectiveRole:
|
||||
player.Pawn.Value?.AcceptInput("AddContext", null, null,
|
||||
"DETECTIVE:1");
|
||||
break;
|
||||
case InnocentRole:
|
||||
player.Pawn.Value?.AcceptInput("AddContext", null, null,
|
||||
"INNOCENT:1");
|
||||
break;
|
||||
}
|
||||
});
|
||||
switch (ev.Role) {
|
||||
case TraitorRole:
|
||||
player.Pawn.Value?.AcceptInput("AddContext", null, null, "TRAITOR:1");
|
||||
break;
|
||||
case DetectiveRole:
|
||||
player.Pawn.Value?.AcceptInput("AddContext", null, null, "DETECTIVE:1");
|
||||
break;
|
||||
case InnocentRole:
|
||||
player.Pawn.Value?.AcceptInput("AddContext", null, null, "INNOCENT:1");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
|
||||
66
TTT/CS2/Listeners/WardenTagAssigner.cs
Normal file
66
TTT/CS2/Listeners/WardenTagAssigner.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.ThirdParties.eGO;
|
||||
using TTT.Game.Events.Game;
|
||||
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;
|
||||
if (ev.Role is not DetectiveRole) return;
|
||||
var gamePlayer = converter.GetPlayer(ev.Player);
|
||||
if (gamePlayer == null) return;
|
||||
|
||||
Task.Run(async () => {
|
||||
var oldTag = await maul.getTagService().GetTag(gamePlayer.SteamID);
|
||||
var oldTagColor =
|
||||
await maul.getTagService().GetTagColor(gamePlayer.SteamID);
|
||||
if (oldTag != "[DETECTIVE]")
|
||||
oldTags[ev.Player.Id] = (oldTag, oldTagColor);
|
||||
|
||||
await Server.NextWorldUpdateAsync(() => {
|
||||
maul.getTagService().SetTag(gamePlayer, "[DETECTIVE]", false);
|
||||
maul.getTagService()
|
||||
.SetTagColor(gamePlayer, ChatColors.DarkBlue, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[EventHandler]
|
||||
public void OnGameEnd(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState != State.FINISHED) return;
|
||||
var maul = EgoApi.MAUL.Get();
|
||||
if (maul == null) return;
|
||||
|
||||
foreach (var (playerId, (oldTag, oldTagColor)) in oldTags) {
|
||||
var apiPlayer = Finder.GetPlayerById(playerId);
|
||||
if (apiPlayer == null) continue;
|
||||
var csPlayer = converter.GetPlayer(apiPlayer);
|
||||
if (csPlayer == null || !csPlayer.IsValid) continue;
|
||||
maul.getTagService().SetTag(csPlayer, oldTag, false);
|
||||
maul.getTagService().SetTagColor(csPlayer, oldTagColor, false);
|
||||
}
|
||||
|
||||
oldTags.Clear();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.Extensions;
|
||||
|
||||
namespace TTT.CS2.Player;
|
||||
|
||||
@@ -87,18 +88,17 @@ public class CS2Player : IOnlinePlayer, IEquatable<CS2Player> {
|
||||
}
|
||||
|
||||
public int Armor {
|
||||
get => Player?.PawnArmor ?? 0;
|
||||
get => Player != null && Player.IsValid ? Player.GetArmor().Item1 : 0;
|
||||
|
||||
set {
|
||||
if (Player == null) return;
|
||||
Player.PawnArmor = value;
|
||||
Utilities.SetStateChanged(Player, "CCSPlayerController", "m_iPawnArmor");
|
||||
if (Player == null || !Player.IsValid) return;
|
||||
Player.SetArmor(value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAlive {
|
||||
get
|
||||
=> Player != null && Player is {
|
||||
=> Player != null && Player.IsValid && Player is {
|
||||
Team : CsTeam.CounterTerrorist or CsTeam.Terrorist,
|
||||
Pawn.Value.Health: > 0
|
||||
};
|
||||
|
||||
@@ -8,48 +8,54 @@ AFK_MOVED: "%PREFIX%You were moved to spectators for being AFK."
|
||||
DEAD_MUTE_REMINDER: "%PREFIX%You are dead and cannot be heard."
|
||||
|
||||
SHOP_ITEM_DNA: "DNA Scanner"
|
||||
SHOP_ITEM_DNA_DESC: "Scan bodies to reveal the person who killed them."
|
||||
SHOP_ITEM_DNA_DESC: "Scan bodies to reveal the person who killed them"
|
||||
SHOP_ITEM_DNA_SCANNED: "%DNA_PREFIX%You scanned {0}{1}'%s% {grey}body, their killer was {red}{2}{grey}."
|
||||
SHOP_ITEM_DNA_SCANNED_OTHER: "%DNA_PREFIX%You scanned {0}{1}'%s% {grey}body, {2}."
|
||||
SHOP_ITEM_DNA_EXPIRED: "%DNA_PREFIX%You scanned {0}{1}'%s% {grey}body, but the DNA has expired."
|
||||
|
||||
SHOP_ITEM_STATION_HEALTH: "Health Station"
|
||||
SHOP_ITEM_STATION_HEALTH_DESC: "A health station that heals players around it."
|
||||
SHOP_ITEM_STATION_HEALTH_DESC: "The health station will heall all players around it"
|
||||
SHOP_ITEM_STATION_HURT: "Hurt Station"
|
||||
SHOP_ITEM_STATION_HURT_DESC: "A station that hurts non-Traitors around it."
|
||||
SHOP_ITEM_STATION_HURT_DESC: "The hurt station will damage all non-Traitors around it"
|
||||
|
||||
SHOP_ITEM_CAMO: "Camouflage"
|
||||
SHOP_ITEM_CAMO_DESC: "Disguise yourself and make yourself harder to see."
|
||||
SHOP_ITEM_CAMO_DESC: "You are now harder to see"
|
||||
|
||||
SHOP_ITEM_BODY_PAINT: "Body Paint"
|
||||
SHOP_ITEM_BODY_PAINT_DESC: "Paint bodies to make them appear identified."
|
||||
SHOP_ITEM_BODY_PAINT_DESC: "Interacting with bodies will now paint them, making them appear identified"
|
||||
SHOP_ITEM_BODY_PAINT_OUT: "%PREFIX% You ran out of body paint."
|
||||
|
||||
SHOP_ITEM_POISON_SHOTS: "Poison Shots"
|
||||
SHOP_ITEM_POISON_SHOTS_DESC: "Your bullets are coated in a mildly poisonous substance."
|
||||
SHOP_ITEM_POISON_SHOTS_DESC: "The next 5 shots from your {red}pistols{grey} are coated with poison"
|
||||
SHOP_ITEM_POISON_HIT: "%PREFIX% You hit {green}{0}{grey} with a {lightpurple}poison shot{grey}."
|
||||
SHOP_ITEM_POISON_OUT: "%PREFIX% You are out of poison shots."
|
||||
|
||||
SHOP_ITEM_POISON_SMOKE: "Poison Smoke"
|
||||
SHOP_ITEM_POISON_SMOKE_DESC: "Throw a grenade that releases poisonous gas."
|
||||
SHOP_ITEM_POISON_SMOKE_DESC: "The smoke grenade will damage all non-Traitors inside it over time"
|
||||
|
||||
SHOP_ITEM_ARMOR: "Armor with Helmet"
|
||||
SHOP_ITEM_ARMOR_DESC: "Wear armor that reduces incoming damage."
|
||||
SHOP_ITEM_ARMOR_DESC: ""
|
||||
|
||||
SHOP_ITEM_ONE_HIT_KNIFE: "One-Hit Knife"
|
||||
SHOP_ITEM_ONE_HIT_KNIFE_DESC: "Your next knife hit will be a guaranteed kill."
|
||||
SHOP_ITEM_ONE_HIT_KNIFE_DESC: "Your {red}next knife{grey} attack will instantly kill your target"
|
||||
|
||||
SHOP_ITEM_COMPASS_PLAYER: "Player Compass"
|
||||
SHOP_ITEM_COMPASS_PLAYER_DESC: "Reveals the direction that the nearest non-Traitor is in."
|
||||
SHOP_ITEM_COMPASS_PLAYER_DESC: ""
|
||||
|
||||
SHOP_ITEM_COMPASS_BODY: "Body Compass"
|
||||
SHOP_ITEM_COMPASS_BODY_DESC: "Reveals the direction that the nearest unidentified body is in."
|
||||
SHOP_ITEM_COMPASS_BODY_DESC: ""
|
||||
|
||||
SHOP_ITEM_SILENT_AWP: "Silent AWP"
|
||||
SHOP_ITEM_SILENT_AWP_DESC: "Receive a silenced AWP with limited ammo."
|
||||
SHOP_ITEM_SILENT_AWP_DESC: ""
|
||||
|
||||
SHOP_ITEM_CLUSTER_GRENADE: "Cluster Grenade"
|
||||
SHOP_ITEM_CLUSTER_GRENADE_DESC: "A grenade that splits into multiple smaller grenades."
|
||||
SHOP_ITEM_CLUSTER_GRENADE_DESC: ""
|
||||
|
||||
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: "The decoy will teleport you to its location {default}once it explodes{grey}"
|
||||
|
||||
SHOP_ITEM_TRIPWIRE: "Tripwire"
|
||||
SHOP_ITEM_TRIPWIRE_DESC: "The tripwire will activate once anyone crosses it"
|
||||
SHOP_ITEM_TRIPWIRE_TOOFAR: "%PREFIX%You are too far away to place the tripwire."
|
||||
SHOP_ITEM_TRIPWIRE_DEFUSING: "Defusing... ({0}, {1} second%s% left)."
|
||||
SHOP_ITEM_TRIPWIRE_DEFUSING_CANCELED: "%PREFIX%You stopped defusing the tripwire."
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.342"/>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346"/>
|
||||
<PackageReference Include="System.Reactive" Version="6.0.1"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -21,9 +21,9 @@ public record TTTConfig {
|
||||
public int TraitorHealth { get; init; } = 100;
|
||||
public int DetectiveHealth { get; init; } = 100;
|
||||
public int InnocentHealth { get; init; } = 100;
|
||||
public int TraitorArmor { get; init; } = 100;
|
||||
public int DetectiveArmor { get; init; } = 100;
|
||||
public int InnocentArmor { get; init; } = 100;
|
||||
public int TraitorArmor { get; init; }
|
||||
public int DetectiveArmor { get; init; }
|
||||
public int InnocentArmor { get; init; }
|
||||
|
||||
public string[]? TraitorWeapons { get; init; } = ["knife", "pistol"];
|
||||
|
||||
|
||||
@@ -51,9 +51,14 @@ public class BuyCommand(IServiceProvider provider) : ICommand {
|
||||
}
|
||||
|
||||
var result = shop.TryPurchase(executor, item);
|
||||
return Task.FromResult(result == PurchaseResult.SUCCESS ?
|
||||
CommandResult.SUCCESS :
|
||||
CommandResult.ERROR);
|
||||
if (result == PurchaseResult.SUCCESS) {
|
||||
info.ReplySync(locale[ShopMsgs.SHOP_PURCHASED(item)]);
|
||||
if (!string.IsNullOrWhiteSpace(item.Description))
|
||||
info.ReplySync(locale[ShopMsgs.SHOP_PURCHASED_DETAIL(item)]);
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
|
||||
return Task.FromResult(CommandResult.ERROR);
|
||||
}
|
||||
|
||||
private IShopItem? searchItem(IOnlinePlayer? player, string query) {
|
||||
|
||||
@@ -77,6 +77,7 @@ public class ListCommand(IServiceProvider provider) : ICommand, IItemSorter {
|
||||
public void InvalidateOrder(IOnlinePlayer? player) {
|
||||
if (player == null) return;
|
||||
cache.Remove(player.Id);
|
||||
lastUpdate.Remove(player.Id);
|
||||
}
|
||||
|
||||
private List<IShopItem> calculateSortedItems(IOnlinePlayer? player) {
|
||||
|
||||
@@ -54,5 +54,6 @@ public class DeagleDamageListener(IServiceProvider provider)
|
||||
}
|
||||
|
||||
ev.HpLeft = -100;
|
||||
Messenger.Message(victim, Locale[DeagleMsgs.SHOP_ITEM_DEAGLE_VICTIM]);
|
||||
}
|
||||
}
|
||||
@@ -11,4 +11,7 @@ public class DeagleMsgs {
|
||||
|
||||
public static IMsg SHOP_ITEM_DEAGLE_HIT_FF
|
||||
=> MsgFactory.Create(nameof(SHOP_ITEM_DEAGLE_HIT_FF));
|
||||
|
||||
public static IMsg SHOP_ITEM_DEAGLE_VICTIM
|
||||
=> MsgFactory.Create(nameof(SHOP_ITEM_DEAGLE_VICTIM));
|
||||
}
|
||||
@@ -38,6 +38,7 @@ public class C4ShopItem(IServiceProvider provider)
|
||||
public override ShopItemConfig Config => config;
|
||||
|
||||
public override void OnPurchase(IOnlinePlayer player) {
|
||||
c4sBought++;
|
||||
Inventory.GiveWeapon(player, new BaseWeapon(config.Weapon));
|
||||
}
|
||||
|
||||
@@ -46,13 +47,10 @@ public class C4ShopItem(IServiceProvider provider)
|
||||
if (c4sBought > config.MaxC4PerRound)
|
||||
return PurchaseResult.ITEM_NOT_PURCHASABLE;
|
||||
|
||||
if (config.MaxC4AtOnce > 0) {
|
||||
var count = 0;
|
||||
if (finder.GetOnline()
|
||||
.Where(p => Shop.HasItem<C4ShopItem>(p))
|
||||
.Any(_ => count++ >= config.MaxC4AtOnce))
|
||||
if (config.MaxC4AtOnce > 0)
|
||||
if (finder.GetOnline().Count(p => Shop.HasItem<C4ShopItem>(p))
|
||||
> config.MaxC4AtOnce)
|
||||
return PurchaseResult.ITEM_NOT_PURCHASABLE;
|
||||
}
|
||||
|
||||
return base.CanPurchase(player);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Storage;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.Locale;
|
||||
|
||||
namespace TTT.Shop;
|
||||
|
||||
@@ -23,6 +24,9 @@ public class PeriodicRewarder(IServiceProvider provider) : ITerrorModule {
|
||||
private readonly IGameManager games =
|
||||
provider.GetRequiredService<IGameManager>();
|
||||
|
||||
private readonly IMsgLocalizer localizer =
|
||||
provider.GetRequiredService<IMsgLocalizer>();
|
||||
|
||||
private readonly Dictionary<string, List<Vector>> playerPositions = new();
|
||||
|
||||
private readonly IScheduler scheduler =
|
||||
@@ -67,7 +71,8 @@ public class PeriodicRewarder(IServiceProvider provider) : ITerrorModule {
|
||||
var position = count == 1 ? 1f : (float)(count - i - 1) / (count - 1);
|
||||
var rewardAmount = scaleRewardAmount(position, config.MinRewardAmount,
|
||||
config.MaxRewardAmount);
|
||||
shop.AddBalance(player, rewardAmount, "Exploration");
|
||||
shop.AddBalance(player, rewardAmount,
|
||||
localizer[ShopMsgs.SHOP_EXPLORATION]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,11 +58,8 @@ public class Shop(IServiceProvider provider) : ITerrorModule, IShop {
|
||||
bus.Dispatch(purchaseEvent);
|
||||
if (purchaseEvent.IsCanceled) return PurchaseResult.PURCHASE_CANCELED;
|
||||
|
||||
AddBalance(player, -cost, item.Name);
|
||||
AddBalance(player, -cost, item.Name, printReason);
|
||||
GiveItem(player, item);
|
||||
|
||||
if (printReason)
|
||||
messenger?.Message(player, localizer[ShopMsgs.SHOP_PURCHASED(item)]);
|
||||
return PurchaseResult.SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,6 @@
|
||||
<RootNamespace>TTT.Shop</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions">
|
||||
<HintPath>..\..\..\..\..\..\.nuget\packages\microsoft.extensions.dependencyinjection.abstractions\8.0.0\lib\net8.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\API\API.csproj"/>
|
||||
<ProjectReference Include="..\Game\Game.csproj"/>
|
||||
|
||||
@@ -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>
|
||||
@@ -13,6 +13,7 @@ using TTT.CS2.Items.PoisonSmoke;
|
||||
using TTT.CS2.Items.SilentAWP;
|
||||
using TTT.CS2.Items.Station;
|
||||
using TTT.CS2.Items.TeleportDecoy;
|
||||
using TTT.CS2.Items.Tripwire;
|
||||
using TTT.Shop.Commands;
|
||||
using TTT.Shop.Items;
|
||||
using TTT.Shop.Items.Detective.Stickers;
|
||||
@@ -61,5 +62,6 @@ public static class ShopServiceCollection {
|
||||
collection.AddStickerServices();
|
||||
collection.AddTaserServices();
|
||||
collection.AddTeleportDecoyServices();
|
||||
collection.AddTripwireServices();
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,9 @@ public static class ShopMsgs {
|
||||
|
||||
public static IMsg CREDITS_NAME => MsgFactory.Create(nameof(CREDITS_NAME));
|
||||
|
||||
public static IMsg SHOP_EXPLORATION
|
||||
=> MsgFactory.Create(nameof(SHOP_EXPLORATION));
|
||||
|
||||
public static IMsg SHOP_CANNOT_PURCHASE
|
||||
=> MsgFactory.Create(nameof(SHOP_CANNOT_PURCHASE));
|
||||
|
||||
@@ -19,6 +22,10 @@ public static class ShopMsgs {
|
||||
return MsgFactory.Create(nameof(SHOP_PURCHASED), item.Name);
|
||||
}
|
||||
|
||||
public static IMsg SHOP_PURCHASED_DETAIL(IShopItem item) {
|
||||
return MsgFactory.Create(nameof(SHOP_PURCHASED_DETAIL), item.Description);
|
||||
}
|
||||
|
||||
public static IMsg SHOP_ITEM_NOT_FOUND(string query) {
|
||||
return MsgFactory.Create(nameof(SHOP_ITEM_NOT_FOUND), query);
|
||||
}
|
||||
|
||||
@@ -2,36 +2,40 @@ SHOP_PREFIX: "{green}SHOP {grey}| "
|
||||
SHOP_INACTIVE: "%SHOP_PREFIX%The shop is currently closed."
|
||||
SHOP_ITEM_NOT_FOUND: "%SHOP_PREFIX%Could not find an item named \"{default}{0}{grey}\"."
|
||||
|
||||
SHOP_EXPLORATION: "Exploration"
|
||||
|
||||
SHOP_ITEM_DEAGLE: "One-Hit Revolver"
|
||||
SHOP_ITEM_DEAGLE_DESC: "A one-hit kill revolver with a single bullet. Aim carefully!"
|
||||
SHOP_ITEM_DEAGLE_DESC: "If you hit an enemy, they will die instantly. Hitting a teammate will kill you instead"
|
||||
SHOP_ITEM_DEAGLE_HIT_FF: "%PREFIX%You hit a teammate!"
|
||||
SHOP_ITEM_DEAGLE_VICTIM: "%PREFIX%You were hit by a {yellow}One-Hit Revolver{grey}."
|
||||
|
||||
SHOP_ITEM_STICKERS: "Stickers"
|
||||
SHOP_ITEM_STICKERS_DESC: "Reveal the roles of all players you taser to others."
|
||||
SHOP_ITEM_STICKERS_HIT: "%SHOP_PREFIX%You got stickered, your role is now visible to everyone."
|
||||
SHOP_ITEM_STICKERS_DESC: "When you tase a player, their role will be shown to everyone"
|
||||
SHOP_ITEM_STICKERS_HIT: "%SHOP_PREFIX%You got {green}stickered{grey}, your role is now visible to everyone."
|
||||
|
||||
SHOP_ITEM_C4: "C4 Explosive"
|
||||
SHOP_ITEM_C4_DESC: "A powerful explosive that blows up after a delay."
|
||||
SHOP_ITEM_C4_DESC: "The bomb will deal damage to everyone including you and fellow {red}Traitors{grey}"
|
||||
|
||||
SHOP_ITEM_M4A1: "M4A1 Rifle and USP-S"
|
||||
SHOP_ITEM_M4A1_DESC: "A fully automatic rifle with a silencer accompanied by a silenced pistol."
|
||||
SHOP_ITEM_M4A1_DESC: ""
|
||||
|
||||
SHOP_ITEM_GLOVES: "Gloves"
|
||||
SHOP_ITEM_GLOVES_DESC: "Lets you kill without DNA being left behind, or move bodies without identifying the body."
|
||||
SHOP_ITEM_GLOVES_DESC: "You can now kill without leaving DNA behind, or move bodies without IDing them"
|
||||
SHOP_ITEM_GLOVES_USED_BODY: "%SHOP_PREFIX%You used your gloves to move a body without leaving DNA. ({yellow}{0}{grey}/{yellow}{1}{grey} use%s% left)."
|
||||
SHOP_ITEM_GLOVES_USED_KILL: "%SHOP_PREFIX%You used your gloves to kill without leaving DNA evidence. ({yellow}{0}{grey}/{yellow}{1}{grey} use%s% left)."
|
||||
SHOP_ITEM_GLOVES_WORN_OUT: "%SHOP_PREFIX%Your gloves worn out."
|
||||
SHOP_ITEM_GLOVES_WORN_OUT: "%SHOP_PREFIX%Your gloves wore out."
|
||||
|
||||
SHOP_ITEM_TASER: "Taser"
|
||||
SHOP_ITEM_TASER_DESC: "A taser that allows you to identify the roles of players you hit."
|
||||
SHOP_ITEM_TASER_DESC: "Tasing a player will tell you their role"
|
||||
|
||||
SHOP_ITEM_HEALTHSHOT: "Healthshot"
|
||||
SHOP_ITEM_HEALTHSHOT_DESC: "A healthshot that heals you gradually for 50 health."
|
||||
SHOP_ITEM_HEALTHSHOT_DESC: ""
|
||||
|
||||
SHOP_INSUFFICIENT_BALANCE: "%SHOP_PREFIX%You cannot afford {white}{0}{grey}, it costs {yellow}{1}{grey} %CREDITS_NAME%%s%, and you have {yellow}{2}{grey}."
|
||||
SHOP_CANNOT_PURCHASE: "%SHOP_PREFIX%You cannot purchase this item."
|
||||
SHOP_CANNOT_PURCHASE_WITH_REASON: "%SHOP_PREFIX%You cannot purchase this item: {red}{0}{grey}."
|
||||
SHOP_PURCHASED: "%SHOP_PREFIX%You purchased {white}{0}{grey}."
|
||||
SHOP_PURCHASED_DETAIL: "%SHOP_PREFIX%{white}{0}{grey}."
|
||||
SHOP_LIST_FOOTER: "%SHOP_PREFIX%You are %an% {0}{grey}, you have {yellow}{1}{grey} %CREDITS_NAME%%s%."
|
||||
|
||||
CREDITS_NAME: "point"
|
||||
|
||||
24
TTT/ShopAPI/Configs/Traitor/TripwireConfig.cs
Normal file
24
TTT/ShopAPI/Configs/Traitor/TripwireConfig.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace ShopAPI.Configs.Traitor;
|
||||
|
||||
public record TripwireConfig : ShopItemConfig {
|
||||
public override int Price { get; init; } = 60;
|
||||
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);
|
||||
|
||||
public float TripwireSizeSquared { get; init; } = 500f;
|
||||
public Color TripwireColor { get; init; } = Color.FromArgb(64, Color.Red);
|
||||
public float TripwireThickness { get; init; } = 0.5f;
|
||||
|
||||
public TimeSpan DefuseTime { get; init; } = TimeSpan.FromSeconds(5);
|
||||
public TimeSpan DefuseRate { get; init; } = TimeSpan.FromMilliseconds(500);
|
||||
|
||||
public int DefuseReward { get; init; } = 20;
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public static class PurchaseResultExtensions {
|
||||
PurchaseResult.WRONG_ROLE =>
|
||||
"You do not have the required role to purchase this item",
|
||||
PurchaseResult.ALREADY_OWNED =>
|
||||
"You already own this item and cannot purchase it again",
|
||||
"You have purchased the maximum amount of this item",
|
||||
_ => "An unexpected error occurred"
|
||||
};
|
||||
}
|
||||
|
||||
11
TTT/SpecialRound/Events/SpecialRoundEnableEvent.cs
Normal file
11
TTT/SpecialRound/Events/SpecialRoundEnableEvent.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using SpecialRoundAPI;
|
||||
|
||||
namespace SpecialRound.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Called when a special round is enabled.
|
||||
/// Note that multiple special rounds may be enabled per round.
|
||||
/// </summary>
|
||||
/// <param name="round"></param>
|
||||
public class SpecialRoundEnableEvent(AbstractSpecialRound round)
|
||||
: SpecialRoundEvent(round);
|
||||
8
TTT/SpecialRound/Events/SpecialRoundEvent.cs
Normal file
8
TTT/SpecialRound/Events/SpecialRoundEvent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using SpecialRoundAPI;
|
||||
using TTT.API.Events;
|
||||
|
||||
namespace SpecialRound.Events;
|
||||
|
||||
public abstract class SpecialRoundEvent(AbstractSpecialRound round) : Event {
|
||||
public AbstractSpecialRound Round { get; } = round;
|
||||
}
|
||||
43
TTT/SpecialRound/Rounds/LowGravRound.cs
Normal file
43
TTT/SpecialRound/Rounds/LowGravRound.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Modules.Cvars;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SpecialRound.lang;
|
||||
using SpecialRoundAPI;
|
||||
using SpecialRoundAPI.Configs;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Storage;
|
||||
using TTT.Game.Events.Game;
|
||||
using TTT.Locale;
|
||||
|
||||
namespace SpecialRound.Rounds;
|
||||
|
||||
public class LowGravRound(IServiceProvider provider)
|
||||
: AbstractSpecialRound(provider) {
|
||||
private int originalGravity = 800;
|
||||
public override string Name => "Low Grav";
|
||||
public override IMsg Description => RoundMsgs.SPECIAL_ROUND_LOWGRAV;
|
||||
public override SpecialRoundConfig Config => config;
|
||||
|
||||
private LowGravRoundConfig config
|
||||
=> Provider.GetService<IStorage<LowGravRoundConfig>>()
|
||||
?.Load()
|
||||
.GetAwaiter()
|
||||
.GetResult() ?? new LowGravRoundConfig();
|
||||
|
||||
public override void ApplyRoundEffects() {
|
||||
var cvar = ConVar.Find("sv_gravity");
|
||||
if (cvar == null) return;
|
||||
|
||||
originalGravity = (int) Math.Round(cvar.GetPrimitiveValue<float>());
|
||||
var newGravity = (int)(originalGravity * config.GravityMultiplier);
|
||||
Server.NextWorldUpdate(()
|
||||
=> Server.ExecuteCommand($"sv_gravity {newGravity}"));
|
||||
}
|
||||
|
||||
public override void OnGameState(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState != State.FINISHED) return;
|
||||
|
||||
Server.NextWorldUpdate(()
|
||||
=> Server.ExecuteCommand($"sv_gravity {originalGravity}"));
|
||||
}
|
||||
}
|
||||
66
TTT/SpecialRound/Rounds/PistolRound.cs
Normal file
66
TTT/SpecialRound/Rounds/PistolRound.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SpecialRound.lang;
|
||||
using SpecialRoundAPI;
|
||||
using SpecialRoundAPI.Configs;
|
||||
using TTT.API;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Storage;
|
||||
using TTT.Game.Events.Game;
|
||||
using TTT.Locale;
|
||||
|
||||
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 PistolRoundConfig config
|
||||
=> Provider.GetService<IStorage<PistolRoundConfig>>()
|
||||
?.Load()
|
||||
.GetAwaiter()
|
||||
.GetResult() ?? new PistolRoundConfig();
|
||||
|
||||
public override SpecialRoundConfig Config => config;
|
||||
|
||||
public void Start(BasePlugin? newPluing) { plugin = newPluing; }
|
||||
|
||||
public override void ApplyRoundEffects() {
|
||||
VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Hook(canAcquire,
|
||||
HookMode.Pre);
|
||||
foreach (var player in Finder.GetOnline())
|
||||
inventory.RemoveWeaponInSlot(player, 0);
|
||||
}
|
||||
|
||||
private HookResult canAcquire(DynamicHook hook) {
|
||||
var player = hook.GetParam<CCSPlayer_ItemServices>(0)
|
||||
.Pawn.Value.Controller.Value?.As<CCSPlayerController>();
|
||||
var data = VirtualFunctions.GetCSWeaponDataFromKey.Invoke(-1,
|
||||
hook.GetParam<CEconItemView>(1).ItemDefinitionIndex.ToString());
|
||||
|
||||
if (player == null || !player.IsValid) return HookResult.Continue;
|
||||
|
||||
if (Tag.RIFLES.Contains(data.Name)) {
|
||||
hook.SetReturn(AcquireResult.NotAllowedByMode);
|
||||
return HookResult.Handled;
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
public override void OnGameState(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState != State.FINISHED) return;
|
||||
|
||||
VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Unhook(canAcquire,
|
||||
HookMode.Pre);
|
||||
}
|
||||
}
|
||||
50
TTT/SpecialRound/Rounds/RichRound.cs
Normal file
50
TTT/SpecialRound/Rounds/RichRound.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ShopAPI.Events;
|
||||
using SpecialRound.lang;
|
||||
using SpecialRoundAPI;
|
||||
using SpecialRoundAPI.Configs;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Storage;
|
||||
using TTT.Game.Events.Game;
|
||||
using TTT.Locale;
|
||||
|
||||
namespace SpecialRound.Rounds;
|
||||
|
||||
public class RichRound(IServiceProvider provider)
|
||||
: AbstractSpecialRound(provider) {
|
||||
public override string Name => "Rich";
|
||||
public override IMsg Description => RoundMsgs.SPECIAL_ROUND_RICH;
|
||||
public override SpecialRoundConfig Config => config;
|
||||
|
||||
private RichRoundConfig config
|
||||
=> Provider.GetService<IStorage<RichRoundConfig>>()
|
||||
?.Load()
|
||||
.GetAwaiter()
|
||||
.GetResult() ?? new RichRoundConfig();
|
||||
|
||||
public override void ApplyRoundEffects() { }
|
||||
|
||||
[UsedImplicitly]
|
||||
[EventHandler]
|
||||
public void OnBalanceChange(PlayerBalanceEvent ev) {
|
||||
if (!Tracker.ActiveRounds.Contains(this)) return;
|
||||
if (ev.Reason == "Round Start") {
|
||||
var newBal = (int)(ev.NewBalance * config.BonusCreditsMultiplier);
|
||||
ev.NewBalance = newBal;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.NewBalance <= ev.OldBalance) return;
|
||||
|
||||
var gain = ev.NewBalance - ev.OldBalance;
|
||||
gain = (int)(gain * config.AdditiveCreditsMultiplier);
|
||||
ev.NewBalance = ev.OldBalance + gain;
|
||||
}
|
||||
|
||||
public override bool ConflictsWith(AbstractSpecialRound other) {
|
||||
return other is VanillaRound;
|
||||
}
|
||||
|
||||
public override void OnGameState(GameStateUpdateEvent ev) { }
|
||||
}
|
||||
@@ -82,7 +82,7 @@ public class SpeedRound(IServiceProvider provider)
|
||||
public void OnDeath(PlayerDeathEvent ev) {
|
||||
var game = games.ActiveGame;
|
||||
if (game == null) return;
|
||||
if (Tracker.CurrentRound != this) return;
|
||||
if (Tracker.ActiveRounds.Contains(this)) return;
|
||||
|
||||
var victimRoles = roles.GetRoles(ev.Victim);
|
||||
if (!victimRoles.Any(r => r is InnocentRole)) return;
|
||||
|
||||
@@ -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,10 +14,10 @@ namespace SpecialRound.Rounds;
|
||||
|
||||
public class SuppressedRound(IServiceProvider provider)
|
||||
: AbstractSpecialRound(provider), IPluginModule {
|
||||
private BasePlugin? plugin;
|
||||
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>>()
|
||||
@@ -32,23 +31,11 @@ public class SuppressedRound(IServiceProvider provider)
|
||||
plugin?.HookUserMessage(452, onWeaponSound);
|
||||
}
|
||||
|
||||
private static readonly HashSet<uint> silencedWeapons = new() {
|
||||
1, // deagle
|
||||
2, // dual berettas
|
||||
3, // five seven
|
||||
30, // tec9
|
||||
32, // p2000
|
||||
36, // p250
|
||||
4, // glock
|
||||
61, // usp-s
|
||||
63, // cz75 auto
|
||||
64 // r8 revolver
|
||||
};
|
||||
|
||||
private HookResult onWeaponSound(UserMessage msg) {
|
||||
var defIndex = msg.ReadUInt("item_def_index");
|
||||
|
||||
if (!silencedWeapons.Contains(defIndex)) return HookResult.Continue;
|
||||
if (!WeaponSoundIndex.PISTOLS.Contains(defIndex))
|
||||
return HookResult.Continue;
|
||||
|
||||
msg.Recipients.Clear();
|
||||
return HookResult.Handled;
|
||||
|
||||
@@ -35,10 +35,14 @@ public class VanillaRound(IServiceProvider provider)
|
||||
|
||||
public override void OnGameState(GameStateUpdateEvent ev) { }
|
||||
|
||||
public override bool ConflictsWith(AbstractSpecialRound other) {
|
||||
return other is RichRound;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[EventHandler(Priority = Priority.HIGH)]
|
||||
public void OnPurchase(PlayerPurchaseItemEvent ev) {
|
||||
if (Tracker.CurrentRound != this) return;
|
||||
if (!Tracker.ActiveRounds.Contains(this)) return;
|
||||
ev.IsCanceled = true;
|
||||
|
||||
messenger.Message(ev.Player, locale[RoundMsgs.VANILLA_ROUND_REMINDER]);
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SpecialRoundAPI\SpecialRoundAPI\SpecialRoundAPI.csproj"/>
|
||||
<ProjectReference Include="..\API\API.csproj"/>
|
||||
<ProjectReference Include="..\CS2\CS2.csproj"/>
|
||||
<ProjectReference Include="..\Game\Game.csproj"/>
|
||||
<ProjectReference Include="..\SpecialRoundAPI\SpecialRoundAPI.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -9,11 +9,15 @@ public static class SpecialRoundCollection {
|
||||
public static void AddSpecialRounds(this IServiceCollection services) {
|
||||
services.AddModBehavior<ISpecialRoundStarter, SpecialRoundStarter>();
|
||||
services.AddModBehavior<ISpecialRoundTracker, SpecialRoundTracker>();
|
||||
services.AddModBehavior<SpecialRoundSoundNotifier>();
|
||||
|
||||
services.AddModBehavior<SpeedRound>();
|
||||
services.AddModBehavior<BhopRound>();
|
||||
services.AddModBehavior<VanillaRound>();
|
||||
services.AddModBehavior<SuppressedRound>();
|
||||
services.AddModBehavior<LowGravRound>();
|
||||
services.AddModBehavior<PistolRound>();
|
||||
services.AddModBehavior<RichRound>();
|
||||
services.AddModBehavior<SilentRound>();
|
||||
services.AddModBehavior<SpeedRound>();
|
||||
services.AddModBehavior<SuppressedRound>();
|
||||
services.AddModBehavior<VanillaRound>();
|
||||
}
|
||||
}
|
||||
17
TTT/SpecialRound/SpecialRoundSoundNotifier.cs
Normal file
17
TTT/SpecialRound/SpecialRoundSoundNotifier.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using JetBrains.Annotations;
|
||||
using SpecialRound.Events;
|
||||
using TTT.API.Events;
|
||||
using TTT.Game.Listeners;
|
||||
|
||||
namespace SpecialRound;
|
||||
|
||||
public class SpecialRoundSoundNotifier(IServiceProvider provider)
|
||||
: BaseListener(provider) {
|
||||
[UsedImplicitly]
|
||||
[EventHandler]
|
||||
public void OnSpecialRoundStart(SpecialRoundEnableEvent ev) {
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
player.EmitSound("UI.XP.Star.Spend", null, 0.2f);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SpecialRound.Events;
|
||||
using SpecialRound.lang;
|
||||
using SpecialRoundAPI;
|
||||
using TTT.API;
|
||||
@@ -29,16 +30,22 @@ public class SpecialRoundStarter(IServiceProvider provider)
|
||||
plugin?.RegisterListener<Listeners.OnMapStart>(onMapChange);
|
||||
}
|
||||
|
||||
public AbstractSpecialRound?
|
||||
TryStartSpecialRound(AbstractSpecialRound? round) {
|
||||
round ??= getSpecialRound();
|
||||
Messenger.MessageAll(Locale[RoundMsgs.SPECIAL_ROUND_STARTED(round)]);
|
||||
Messenger.MessageAll(Locale[round.Description]);
|
||||
public List<AbstractSpecialRound> TryStartSpecialRound(
|
||||
List<AbstractSpecialRound>? rounds = null) {
|
||||
rounds ??= getSpecialRounds();
|
||||
|
||||
round?.ApplyRoundEffects();
|
||||
tracker.CurrentRound = round;
|
||||
Messenger.MessageAll(Locale[RoundMsgs.SPECIAL_ROUND_STARTED(rounds)]);
|
||||
|
||||
foreach (var round in rounds) {
|
||||
var roundStart = new SpecialRoundEnableEvent(round);
|
||||
Bus.Dispatch(roundStart);
|
||||
Messenger.MessageAll(Locale[round.Description]);
|
||||
round.ApplyRoundEffects();
|
||||
}
|
||||
|
||||
tracker.ActiveRounds.AddRange(rounds);
|
||||
tracker.RoundsSinceLastSpecial = 0;
|
||||
return round;
|
||||
return rounds;
|
||||
}
|
||||
|
||||
private void onMapChange(string mapName) { roundsSinceMapChange = 0; }
|
||||
@@ -57,16 +64,31 @@ public class SpecialRoundStarter(IServiceProvider provider)
|
||||
if (roundsSinceMapChange < config.MinRoundsAfterMapChange) return;
|
||||
if (Random.Shared.NextSingle() > config.SpecialRoundChance) return;
|
||||
|
||||
var specialRound = getSpecialRound();
|
||||
var specialRound = getSpecialRounds();
|
||||
|
||||
TryStartSpecialRound(specialRound);
|
||||
}
|
||||
|
||||
private AbstractSpecialRound getSpecialRound() {
|
||||
private List<AbstractSpecialRound> getSpecialRounds() {
|
||||
var selectedRounds = new List<AbstractSpecialRound>();
|
||||
|
||||
do {
|
||||
var round = pickWeightedRound(selectedRounds);
|
||||
if (round == null) break;
|
||||
selectedRounds.Add(round);
|
||||
} while (config.MultiRoundChance > Random.Shared.NextSingle());
|
||||
|
||||
return selectedRounds;
|
||||
}
|
||||
|
||||
private AbstractSpecialRound? pickWeightedRound(
|
||||
List<AbstractSpecialRound> exclude) {
|
||||
var rounds = Provider.GetServices<ITerrorModule>()
|
||||
.OfType<AbstractSpecialRound>()
|
||||
.Where(r => r.Config.Weight > 0)
|
||||
.Where(r => r.Config.Weight > 0 && !exclude.Contains(r))
|
||||
.Where(r => !exclude.Any(er => er.ConflictsWith(r) || r.ConflictsWith(er)))
|
||||
.ToList();
|
||||
if (rounds.Count == 0) return null;
|
||||
var totalWeight = rounds.Sum(r => r.Config.Weight);
|
||||
var roll = Random.Shared.NextDouble() * totalWeight;
|
||||
foreach (var round in rounds) {
|
||||
@@ -74,7 +96,6 @@ public class SpecialRoundStarter(IServiceProvider provider)
|
||||
if (roll <= 0) return round;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
"Failed to select a special round. This should never happen.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace SpecialRound;
|
||||
|
||||
public class SpecialRoundTracker : ISpecialRoundTracker, ITerrorModule,
|
||||
IListener {
|
||||
public AbstractSpecialRound? CurrentRound { get; set; }
|
||||
public List<AbstractSpecialRound> ActiveRounds { get; } = new();
|
||||
public int RoundsSinceLastSpecial { get; set; }
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
@@ -18,6 +18,6 @@ public class SpecialRoundTracker : ISpecialRoundTracker, ITerrorModule,
|
||||
[EventHandler]
|
||||
public void OnRoundEnd(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState != State.FINISHED) return;
|
||||
CurrentRound = null;
|
||||
ActiveRounds.Clear();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,14 @@
|
||||
|
||||
public record SpecialRoundsConfig {
|
||||
public int MinRoundsBetweenSpecial { get; init; } = 3;
|
||||
public int MinPlayersForSpecial { get; init; } = 8;
|
||||
public int MinPlayersForSpecial { get; init; } = 5;
|
||||
public int MinRoundsAfterMapChange { get; init; } = 2;
|
||||
public float SpecialRoundChance { get; init; } = 0.2f;
|
||||
|
||||
/// <summary>
|
||||
/// If a special round is started, the chance that another special round
|
||||
/// will start in conjunction with it. This check is run until it fails,
|
||||
/// or we run out of special rounds to start.
|
||||
/// </summary>
|
||||
public float MultiRoundChance { get; init; } = 0.33f;
|
||||
}
|
||||
@@ -22,7 +22,19 @@ public class RoundMsgs {
|
||||
public static IMsg SPECIAL_ROUND_SILENT
|
||||
=> MsgFactory.Create(nameof(SPECIAL_ROUND_SILENT));
|
||||
|
||||
public static IMsg SPECIAL_ROUND_STARTED(AbstractSpecialRound round) {
|
||||
return MsgFactory.Create(nameof(SPECIAL_ROUND_STARTED), round.Name);
|
||||
public static IMsg SPECIAL_ROUND_PISTOL
|
||||
=> MsgFactory.Create(nameof(SPECIAL_ROUND_PISTOL));
|
||||
|
||||
public static IMsg SPECIAL_ROUND_RICH
|
||||
=> MsgFactory.Create(nameof(SPECIAL_ROUND_RICH));
|
||||
|
||||
public static IMsg SPECIAL_ROUND_LOWGRAV
|
||||
=> MsgFactory.Create(nameof(SPECIAL_ROUND_LOWGRAV));
|
||||
|
||||
public static IMsg SPECIAL_ROUND_STARTED(List<AbstractSpecialRound> round) {
|
||||
var roundNames = round.Count == 1 ?
|
||||
round[0].Name :
|
||||
string.Join(", ", round.Select(r => r.Name));
|
||||
return MsgFactory.Create(nameof(SPECIAL_ROUND_STARTED), roundNames);
|
||||
}
|
||||
}
|
||||
@@ -4,4 +4,7 @@ SPECIAL_ROUND_BHOP: " {Yellow}BHOP{grey}: Bunny hopping is enabled! Hold jump to
|
||||
SPECIAL_ROUND_VANILLA: " {green}VANILLA{grey}: The shop has been disabled!"
|
||||
SPECIAL_ROUND_SUPPRESSED: " {grey}SUPPRESSED{grey}: All pistols are silent!"
|
||||
SPECIAL_ROUND_SILENT: " {grey}SILENT{grey}: All players are muted!"
|
||||
SPECIAL_ROUND_PISTOL: " {blue}PISTOL{grey}: You can only use pistols this round!"
|
||||
SPECIAL_ROUND_RICH: " {gold}RICH{grey}: All players start with extra credits!"
|
||||
SPECIAL_ROUND_LOWGRAV: " {lightblue}LOW GRAVITY{grey}: Players can jump higher and fall slower!"
|
||||
VANILLA_ROUND_REMINDER: "%SHOP_PREFIX%This is a {purple}Vanilla{grey} round. The shop is disabled."
|
||||
@@ -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;
|
||||
@@ -20,6 +19,8 @@ public abstract class AbstractSpecialRound(IServiceProvider provider)
|
||||
|
||||
public abstract void ApplyRoundEffects();
|
||||
|
||||
public virtual bool ConflictsWith(AbstractSpecialRound _) { return false; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[EventHandler]
|
||||
public abstract void OnGameState(GameStateUpdateEvent ev);
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace SpecialRoundAPI.Configs;
|
||||
|
||||
public record BhopRoundConfig : SpecialRoundConfig {
|
||||
public override float Weight { get; init; } = 0.2f;
|
||||
public override float Weight { get; init; } = 0.25f;
|
||||
}
|
||||
6
TTT/SpecialRoundAPI/Configs/LowGravRoundConfig.cs
Normal file
6
TTT/SpecialRoundAPI/Configs/LowGravRoundConfig.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace SpecialRoundAPI.Configs;
|
||||
|
||||
public record LowGravRoundConfig : SpecialRoundConfig {
|
||||
public override float Weight { get; init; } = 0.6f;
|
||||
public float GravityMultiplier { get; init; } = 0.5f;
|
||||
}
|
||||
5
TTT/SpecialRoundAPI/Configs/PistolRoundConfig.cs
Normal file
5
TTT/SpecialRoundAPI/Configs/PistolRoundConfig.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace SpecialRoundAPI.Configs;
|
||||
|
||||
public record PistolRoundConfig : SpecialRoundConfig {
|
||||
public override float Weight { get; init; } = 0.75f;
|
||||
}
|
||||
7
TTT/SpecialRoundAPI/Configs/RichRoundConfig.cs
Normal file
7
TTT/SpecialRoundAPI/Configs/RichRoundConfig.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace SpecialRoundAPI.Configs;
|
||||
|
||||
public record RichRoundConfig : SpecialRoundConfig {
|
||||
public override float Weight { get; init; } = 0.75f;
|
||||
public float BonusCreditsMultiplier { get; init; } = 2.0f;
|
||||
public float AdditiveCreditsMultiplier { get; init; } = 3.0f;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace SpecialRoundAPI.Configs;
|
||||
|
||||
public record SilentRoundConfig : SpecialRoundConfig {
|
||||
public override float Weight { get; init; } = 0.1f;
|
||||
public override float Weight { get; init; } = 0.5f;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
namespace SpecialRoundAPI.Configs;
|
||||
|
||||
public record SpeedRoundConfig : SpecialRoundConfig {
|
||||
public override float Weight { get; init; } = 0.6f;
|
||||
public override float Weight { get; init; } = 1;
|
||||
|
||||
public TimeSpan InitialSeconds { get; init; } = TimeSpan.FromSeconds(40);
|
||||
public TimeSpan SecondsPerKill { get; init; } = TimeSpan.FromSeconds(10);
|
||||
public TimeSpan SecondsPerKill { get; init; } = TimeSpan.FromSeconds(8);
|
||||
public TimeSpan MaxTimeEver { get; init; } = TimeSpan.FromSeconds(90);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace SpecialRoundAPI.Configs;
|
||||
|
||||
public record SuppressedRoundConfig : SpecialRoundConfig {
|
||||
public override float Weight { get; init; } = 0.3f;
|
||||
public override float Weight { get; init; } = 0.75f;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace SpecialRoundAPI.Configs;
|
||||
|
||||
public record VanillaRoundConfig : SpecialRoundConfig {
|
||||
public override float Weight { get; init; } = 0.2f;
|
||||
public override float Weight { get; init; } = 0.5f;
|
||||
}
|
||||
@@ -8,6 +8,6 @@ public interface ISpecialRoundStarter {
|
||||
/// </summary>
|
||||
/// <param name="round"></param>
|
||||
/// <returns></returns>
|
||||
public AbstractSpecialRound?
|
||||
TryStartSpecialRound(AbstractSpecialRound? round);
|
||||
public List<AbstractSpecialRound>? TryStartSpecialRound(
|
||||
List<AbstractSpecialRound>? round = null);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
namespace SpecialRoundAPI;
|
||||
|
||||
public interface ISpecialRoundTracker {
|
||||
public AbstractSpecialRound? CurrentRound { get; set; }
|
||||
public List<AbstractSpecialRound> ActiveRounds { get; }
|
||||
|
||||
public int RoundsSinceLastSpecial { get; set; }
|
||||
}
|
||||
16
TTT/SpecialRoundAPI/WeaponSoundIndex.cs
Normal file
16
TTT/SpecialRoundAPI/WeaponSoundIndex.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace SpecialRoundAPI;
|
||||
|
||||
public class WeaponSoundIndex {
|
||||
public static readonly HashSet<uint> PISTOLS = [
|
||||
1, // deagle
|
||||
2, // dual berettas
|
||||
3, // five seven
|
||||
30, // tec9
|
||||
32, // p2000
|
||||
36, // p250
|
||||
4, // glock
|
||||
61, // usp-s
|
||||
63, // cz75 auto
|
||||
64
|
||||
];
|
||||
}
|
||||
44
TTT/Stats/SpecialRoundListener.cs
Normal file
44
TTT/Stats/SpecialRoundListener.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SpecialRound.Events;
|
||||
using SpecialRoundAPI;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.Game.Events.Game;
|
||||
using TTT.Game.Listeners;
|
||||
|
||||
namespace Stats;
|
||||
|
||||
public class SpecialRoundListener(IServiceProvider provider)
|
||||
: BaseListener(provider) {
|
||||
private readonly HttpClient client =
|
||||
provider.GetRequiredService<HttpClient>();
|
||||
|
||||
private readonly IRoundTracker tracker =
|
||||
provider.GetRequiredService<IRoundTracker>();
|
||||
|
||||
private AbstractSpecialRound? round;
|
||||
|
||||
[UsedImplicitly]
|
||||
[EventHandler]
|
||||
public void OnRoundStart(SpecialRoundEnableEvent ev) { round = ev.Round; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[EventHandler]
|
||||
public void OnRoundEnd(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState != State.FINISHED) return;
|
||||
if (round == null) return;
|
||||
|
||||
var data = new { special_round = round.Name };
|
||||
var payload = new StringContent(JsonSerializer.Serialize(data),
|
||||
Encoding.UTF8, "application/json");
|
||||
|
||||
Task.Run(async () => {
|
||||
var id = tracker.CurrentRoundId;
|
||||
if (id == null) return;
|
||||
await client.PatchAsync("round/" + id.Value, payload);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Game\Game.csproj"/>
|
||||
<ProjectReference Include="..\ShopAPI\ShopAPI.csproj"/>
|
||||
<ProjectReference Include="..\SpecialRound\SpecialRound.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,343 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet" href="/Content/Site.css" />
|
||||
<title>'Apache-2.0' reference</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main-content">
|
||||
|
||||
|
||||
|
||||
<h1>Apache License 2.0</h1>
|
||||
|
||||
<h2>SPDX identifier</h2>
|
||||
<div id="license-expression">Apache-2.0</div>
|
||||
|
||||
<h2>License text</h2>
|
||||
|
||||
<div class="optional-license-text">
|
||||
<p>Apache License
|
||||
<br />
|
||||
|
||||
Version 2.0, January 2004
|
||||
<br />
|
||||
|
||||
http://www.apache.org/licenses/
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="optional-license-text">
|
||||
<p>TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION</p>
|
||||
|
||||
</div>
|
||||
|
||||
<ul style="list-style:none">
|
||||
|
||||
<li>
|
||||
<var class="replaceable-license-text"> 1.</var>
|
||||
Definitions.
|
||||
|
||||
<ul style="list-style:none">
|
||||
|
||||
<li>
|
||||
<p>"License" shall mean the terms and conditions for use, reproduction, and distribution
|
||||
as defined by Sections 1 through 9 of this document.</p>
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>"Licensor" shall mean the copyright owner or entity authorized by the copyright owner
|
||||
that is granting the License.</p>
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>"Legal Entity" shall mean the union of the acting entity and all other entities that
|
||||
control, are controlled by, or are under common control with that entity. For the purposes of
|
||||
this definition, "control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or otherwise, or (ii) ownership of
|
||||
fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such
|
||||
entity.</p>
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.</p>
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>"Source" form shall mean the preferred form for making modifications, including but not
|
||||
limited to software source code, documentation source, and configuration files.</p>
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code, generated
|
||||
documentation, and conversions to other media types.</p>
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included in or
|
||||
attached to the work (an example is provided in the Appendix below).</p>
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>"Derivative Works" shall mean any work, whether in Source or Object form, that is based
|
||||
on (or derived from) the Work and for which the editorial revisions, annotations,
|
||||
elaborations, or other modifications represent, as a whole, an original work of authorship.
|
||||
For the purposes of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative
|
||||
Works thereof.</p>
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>"Contribution" shall mean any work of authorship, including the original version of the
|
||||
Work and any modifications or additions to that Work or Derivative Works thereof, that is
|
||||
intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an
|
||||
individual or Legal Entity authorized to submit on behalf of the copyright owner. For the
|
||||
purposes of this definition, "submitted" means any form of electronic, verbal, or
|
||||
written communication sent to the Licensor or its representatives, including but not limited
|
||||
to communication on electronic mailing lists, source code control systems, and issue tracking
|
||||
systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and
|
||||
improving the Work, but excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."</p>
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom
|
||||
a Contribution has been received by Licensor and subsequently incorporated within the
|
||||
Work.</p>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<var class="replaceable-license-text"> 2.</var>
|
||||
Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor
|
||||
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display,
|
||||
publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or
|
||||
Object form.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<var class="replaceable-license-text"> 3.</var>
|
||||
Grant of Patent License. Subject to the terms and conditions of this License, each Contributor
|
||||
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have made, use, offer
|
||||
to sell, sell, import, and otherwise transfer the Work, where such license applies only to
|
||||
those patent claims licensable by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s) with the Work to which such
|
||||
Contribution(s) was submitted. If You institute patent litigation against any entity
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory patent
|
||||
infringement, then any patent licenses granted to You under this License for that Work shall
|
||||
terminate as of the date such litigation is filed.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<var class="replaceable-license-text"> 4.</var>
|
||||
Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
<ul style="list-style:none">
|
||||
|
||||
<li>
|
||||
<var class="replaceable-license-text"> (a)</var>
|
||||
You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<var class="replaceable-license-text"> (b)</var>
|
||||
You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<var class="replaceable-license-text"> (c)</var>
|
||||
You must retain, in the Source form of any Derivative Works that You distribute, all
|
||||
copyright, patent, trademark, and attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<var class="replaceable-license-text"> (d)</var>
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the attribution
|
||||
notices contained within such NOTICE file, excluding those notices that do not pertain to
|
||||
any part of the Derivative Works, in at least one of the following places: within a NOTICE
|
||||
text file distributed as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or, within a display generated
|
||||
by the Derivative Works, if and wherever such third-party notices normally appear. The
|
||||
contents of the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that You
|
||||
distribute, alongside or as an addendum to the NOTICE text from the Work, provided that
|
||||
such additional attribution notices cannot be construed as modifying the License.
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<p>You may add Your own copyright statement to Your modifications and may provide additional or
|
||||
different license terms and conditions for use, reproduction, or distribution of Your
|
||||
modifications, or for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with the conditions stated
|
||||
in this License.</p>
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<var class="replaceable-license-text"> 5.</var>
|
||||
Submission of Contributions. Unless You explicitly state otherwise, any Contribution
|
||||
intentionally submitted for inclusion in the Work by You to the Licensor shall be under the
|
||||
terms and conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate
|
||||
license agreement you may have executed with Licensor regarding such Contributions.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<var class="replaceable-license-text"> 6.</var>
|
||||
Trademarks. This License does not grant permission to use the trade names, trademarks, service
|
||||
marks, or product names of the Licensor, except as required for reasonable and customary use
|
||||
in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<var class="replaceable-license-text"> 7.</var>
|
||||
Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor
|
||||
provides the Work (and each Contributor provides its Contributions) on an "AS IS"
|
||||
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including,
|
||||
without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY,
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any risks associated with Your
|
||||
exercise of permissions under this License.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<var class="replaceable-license-text"> 8.</var>
|
||||
Limitation of Liability. In no event and under no legal theory, whether in tort (including
|
||||
negligence), contract, or otherwise, unless required by applicable law (such as deliberate and
|
||||
grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for
|
||||
damages, including any direct, indirect, special, incidental, or consequential damages of any
|
||||
character arising as a result of this License or out of the use or inability to use the Work
|
||||
(including but not limited to damages for loss of goodwill, work stoppage, computer failure or
|
||||
malfunction, or any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<var class="replaceable-license-text"> 9.</var>
|
||||
Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works
|
||||
thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty,
|
||||
indemnity, or other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your sole
|
||||
responsibility, not on behalf of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability incurred by, or claims asserted
|
||||
against, such Contributor by reason of your accepting any such warranty or additional
|
||||
liability.
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<div class="optional-license-text">
|
||||
<p>END OF TERMS AND CONDITIONS</p>
|
||||
|
||||
</div>
|
||||
<div class="optional-license-text">
|
||||
<p>APPENDIX: How to apply the Apache License to your work.</p>
|
||||
|
||||
<p>To apply the Apache License to your work, attach the following boilerplate notice, with the fields
|
||||
enclosed by brackets "[]" replaced with your own identifying information. (Don't
|
||||
include the brackets!) The text should be enclosed in the appropriate comment syntax for the file
|
||||
format. We also recommend that a file or class name and description of purpose be included on the same
|
||||
"printed page" as the copyright notice for easier identification within third-party
|
||||
archives.</p>
|
||||
|
||||
<p>Copyright <var class="replaceable-license-text"> [yyyy] [name of copyright owner]</var></p>
|
||||
|
||||
<p>Licensed under the Apache License, Version 2.0 (the "License");
|
||||
<br />
|
||||
|
||||
you may not use this file except in compliance with the License.
|
||||
<br />
|
||||
|
||||
You may obtain a copy of the License at
|
||||
</p>
|
||||
|
||||
<p>http://www.apache.org/licenses/LICENSE-2.0</p>
|
||||
|
||||
<p>Unless required by applicable law or agreed to in writing, software
|
||||
<br />
|
||||
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
<br />
|
||||
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
<br />
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
<br />
|
||||
|
||||
limitations under the License.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Standard License Header</h2>
|
||||
|
||||
<p>Copyright <var class="replaceable-license-text"> [yyyy] [name of copyright owner]</var></p>
|
||||
|
||||
<p>Licensed under the Apache License, Version 2.0 (the "License");
|
||||
<br />
|
||||
|
||||
you may not use this file except in compliance with the License.
|
||||
<br />
|
||||
|
||||
You may obtain a copy of the License at
|
||||
</p>
|
||||
|
||||
<p>http://www.apache.org/licenses/LICENSE-2.0</p>
|
||||
|
||||
<p>Unless required by applicable law or agreed to in writing, software
|
||||
<br />
|
||||
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
<br />
|
||||
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
<br />
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
<br />
|
||||
|
||||
limitations under the License.
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<h2>Notes</h2>
|
||||
<p>This license was released January 2004</p>
|
||||
|
||||
<h2>SPDX web page</h2>
|
||||
<ul>
|
||||
<li><a href="https://spdx.org/licenses/Apache-2.0.html">https://spdx.org/licenses/Apache-2.0.html</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Notice</h2>
|
||||
<p>This license content is provided by the <a href="https://spdx.dev/">SPDX project</a>. For more information about <b>licenses.nuget.org</b>, see <a href="https://aka.ms/licenses.nuget.org">our documentation</a>.
|
||||
|
||||
<p><i>Data pulled from <a href="https://github.com/spdx/license-list-data">spdx/license-list-data</a> on November 6, 2024.</i></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,63 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet" href="/Content/Site.css" />
|
||||
<title>'MIT' reference</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main-content">
|
||||
|
||||
|
||||
|
||||
<h1>MIT License</h1>
|
||||
|
||||
<h2>SPDX identifier</h2>
|
||||
<div id="license-expression">MIT</div>
|
||||
|
||||
<h2>License text</h2>
|
||||
|
||||
<div class="optional-license-text">
|
||||
<p>MIT License</p>
|
||||
|
||||
</div>
|
||||
<div class="replaceable-license-text">
|
||||
<p>Copyright (c) <year> <copyright holders>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<p>Permission is hereby granted, free of charge, to any person obtaining a copy of <var class="replaceable-license-text"> this software and
|
||||
associated documentation files</var> (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:</p>
|
||||
|
||||
<p>The above copyright notice and this permission notice
|
||||
<var class="optional-license-text"> (including the next paragraph)</var>
|
||||
shall be included in all copies or substantial
|
||||
portions of the Software.</p>
|
||||
|
||||
<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL <var class="replaceable-license-text"> THE AUTHORS OR COPYRIGHT HOLDERS</var> BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h2>SPDX web page</h2>
|
||||
<ul>
|
||||
<li><a href="https://spdx.org/licenses/MIT.html">https://spdx.org/licenses/MIT.html</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Notice</h2>
|
||||
<p>This license content is provided by the <a href="https://spdx.dev/">SPDX project</a>. For more information about <b>licenses.nuget.org</b>, see <a href="https://aka.ms/licenses.nuget.org">our documentation</a>.
|
||||
|
||||
<p><i>Data pulled from <a href="https://github.com/spdx/license-list-data">spdx/license-list-data</a> on November 6, 2024.</i></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user