mirror of
https://github.com/MSWS/TTT.git
synced 2025-12-06 06:22:44 -08:00
Compare commits
12 Commits
1.3.0-dev.
...
1.4.0-dev.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24cd1295b6 | ||
|
|
125daa515e | ||
|
|
722c29bde7 | ||
|
|
3c0fd74c2a | ||
|
|
60de8b54db | ||
|
|
e7dfbca35d | ||
|
|
3a463b29c6 | ||
|
|
e11a8e20e5 | ||
|
|
b0a7ec60e0 | ||
|
|
0aa28b1076 | ||
|
|
b53920282b | ||
|
|
5167291ab4 |
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346" />
|
||||
<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"/>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using TTT.CS2.Items.Tripwire;
|
||||
|
||||
namespace TTT.CS2.API.Items;
|
||||
|
||||
public interface ITripwireTracker {
|
||||
|
||||
@@ -9,12 +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" />
|
||||
<ProjectReference Include="..\SpecialRoundAPI\SpecialRoundAPI.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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,24 +31,14 @@ 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);
|
||||
foreach (var player in targets) shop.GiveItem(player, item);
|
||||
|
||||
target = result;
|
||||
}
|
||||
|
||||
var purchaseEv = new PlayerPurchaseItemEvent(target, item);
|
||||
provider.GetRequiredService<IEventBus>().Dispatch(purchaseEv);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class SpecialRoundCommand(IServiceProvider provider) : ICommand {
|
||||
}
|
||||
|
||||
if (info.ArgCount == 1) {
|
||||
tracker.TryStartSpecialRound(null);
|
||||
tracker.TryStartSpecialRound();
|
||||
info.ReplySync("Started a random special round.");
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
@@ -41,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,21 +55,21 @@ 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(
|
||||
|
||||
@@ -78,6 +78,11 @@ public class CS2TripwireConfig : IStorage<TripwireConfig>, IPluginModule {
|
||||
"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() { }
|
||||
@@ -102,7 +107,8 @@ public class CS2TripwireConfig : IStorage<TripwireConfig>, IPluginModule {
|
||||
CV_COLOR_B.Value),
|
||||
TripwireThickness = CV_THICKNESS.Value,
|
||||
DefuseTime = TimeSpan.FromSeconds(CV_DEFUSE_TIME.Value),
|
||||
DefuseRate = TimeSpan.FromSeconds(CV_DEFUSE_RATE.Value)
|
||||
DefuseRate = TimeSpan.FromSeconds(CV_DEFUSE_RATE.Value),
|
||||
DefuseReward = CV_DEFUSE_REWARD.Value
|
||||
};
|
||||
|
||||
return Task.FromResult<TripwireConfig?>(cfg);
|
||||
|
||||
@@ -97,7 +97,7 @@ 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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -42,8 +42,8 @@ public class HealthStation(IServiceProvider provider)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scan all props and build a map: Player -> list of (StationInfo, potentialHeal).
|
||||
/// Also fills toRemove for invalid/expired props.
|
||||
/// 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)>>
|
||||
@@ -90,8 +90,8 @@ public class HealthStation(IServiceProvider provider)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute potential heal from a station given the distance.
|
||||
/// Mirrors your original logic: ceil(HealthIncrements * healthScale).
|
||||
/// 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;
|
||||
@@ -99,9 +99,9 @@ public class HealthStation(IServiceProvider provider)
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// 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)>>
|
||||
@@ -125,7 +125,7 @@ public class HealthStation(IServiceProvider provider)
|
||||
allocateProportionalInteger(totalHealAvailable, potentials);
|
||||
|
||||
// safety clamp: never allocate more than a station's potential
|
||||
for (int i = 0; i < allocations.Count; i++)
|
||||
for (var i = 0; i < allocations.Count; i++)
|
||||
if (allocations[i] > potentials[i])
|
||||
allocations[i] = potentials[i];
|
||||
|
||||
@@ -164,9 +164,9 @@ public class HealthStation(IServiceProvider provider)
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// 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) {
|
||||
|
||||
@@ -12,21 +12,21 @@ using TTT.CS2.Extensions;
|
||||
namespace TTT.CS2.Items.Tripwire;
|
||||
|
||||
public class TripwireDamageListener(IServiceProvider provider) : IPluginModule {
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
private readonly ITripwireActivator? tripwireActivator =
|
||||
provider.GetRequiredService<ITripwireActivator>();
|
||||
|
||||
private readonly ITripwireTracker? tripwires =
|
||||
provider.GetService<ITripwireTracker>();
|
||||
|
||||
private readonly ITripwireActivator? tripwireActivator =
|
||||
provider.GetRequiredService<ITripwireActivator>();
|
||||
|
||||
private TripwireConfig config
|
||||
=> provider.GetService<IStorage<TripwireConfig>>()
|
||||
?.Load()
|
||||
.GetAwaiter()
|
||||
.GetResult() ?? new TripwireConfig();
|
||||
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
[UsedImplicitly]
|
||||
[GameEventHandler]
|
||||
public HookResult OnBulletImpact(EventBulletImpact ev, GameEventInfo info) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ShopAPI;
|
||||
using ShopAPI.Configs.Traitor;
|
||||
using TTT.API;
|
||||
using TTT.API.Messages;
|
||||
@@ -16,17 +17,17 @@ namespace TTT.CS2.Items.Tripwire;
|
||||
|
||||
public class TripwireDefuserListener(IServiceProvider provider)
|
||||
: IPluginModule {
|
||||
private readonly ITripwireTracker? tripwireTracker =
|
||||
provider.GetService<ITripwireTracker>();
|
||||
|
||||
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 IMsgLocalizer locale =
|
||||
provider.GetRequiredService<IMsgLocalizer>();
|
||||
private readonly ITripwireTracker? tripwireTracker =
|
||||
provider.GetService<ITripwireTracker>();
|
||||
|
||||
private TripwireConfig config
|
||||
=> provider.GetService<IStorage<TripwireConfig>>()
|
||||
@@ -99,11 +100,14 @@ public class TripwireDefuserListener(IServiceProvider provider)
|
||||
}
|
||||
|
||||
var progress = (DateTime.Now - startTime) / config.DefuseTime;
|
||||
var timeLeft = config.DefuseTime - (config.DefuseTime * progress);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Reactive.Concurrency;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
@@ -36,25 +35,17 @@ public static class TripwireServiceCollection {
|
||||
|
||||
public class TripwireItem(IServiceProvider provider)
|
||||
: RoleRestrictedItem<TraitorRole>(provider), IPluginModule, ITripwireTracker {
|
||||
private TripwireConfig config
|
||||
=> Provider.GetService<IStorage<TripwireConfig>>()
|
||||
?.Load()
|
||||
.GetAwaiter()
|
||||
.GetResult() ?? new TripwireConfig();
|
||||
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
private readonly IScheduler scheduler =
|
||||
provider.GetRequiredService<IScheduler>();
|
||||
|
||||
public List<TripwireInstance> ActiveTripwires { get; } = [];
|
||||
|
||||
public void RemoveTripwire(TripwireInstance instance) {
|
||||
instance.Beam.Remove();
|
||||
instance.TripwireProp.Remove();
|
||||
ActiveTripwires.Remove(instance);
|
||||
}
|
||||
private TripwireConfig config
|
||||
=> Provider.GetService<IStorage<TripwireConfig>>()
|
||||
?.Load()
|
||||
.GetAwaiter()
|
||||
.GetResult() ?? new TripwireConfig();
|
||||
|
||||
public override string Name => Locale[TripwireMsgs.SHOP_ITEM_TRIPWIRE];
|
||||
|
||||
@@ -71,6 +62,14 @@ public class TripwireItem(IServiceProvider provider)
|
||||
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");
|
||||
|
||||
@@ -27,12 +27,12 @@ public class TripwireMovementListener(IServiceProvider provider)
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
private readonly ITripwireTracker? tripwireTracker =
|
||||
provider.GetService<ITripwireTracker>();
|
||||
|
||||
private readonly Dictionary<string, TripwireInstance> killedWithTripwire =
|
||||
new();
|
||||
|
||||
private readonly ITripwireTracker? tripwireTracker =
|
||||
provider.GetService<ITripwireTracker>();
|
||||
|
||||
private TripwireConfig config
|
||||
=> Provider.GetService<IStorage<TripwireConfig>>()
|
||||
?.Load()
|
||||
@@ -44,6 +44,16 @@ public class TripwireMovementListener(IServiceProvider provider)
|
||||
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
|
||||
@@ -96,16 +106,6 @@ public class TripwireMovementListener(IServiceProvider provider)
|
||||
ev.Body.Killer = info.owner;
|
||||
}
|
||||
|
||||
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 bool dealTripwireDamage(TripwireInstance instance,
|
||||
IOnlinePlayer player,
|
||||
[NotNullWhen(true)] out CCSPlayerController? gamePlayer) {
|
||||
|
||||
@@ -12,12 +12,12 @@ public class TripwireMsgs {
|
||||
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"));
|
||||
}
|
||||
|
||||
public static IMsg SHOP_ITEM_TRIPWIRE_DEFUSING_CANCELED
|
||||
=> MsgFactory.Create(nameof(SHOP_ITEM_TRIPWIRE_DEFUSING_CANCELED));
|
||||
}
|
||||
@@ -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,54 +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: "A tripwire that explodes when triggered."
|
||||
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."
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,37 +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 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"
|
||||
|
||||
@@ -19,4 +19,6 @@ public record TripwireConfig : ShopItemConfig {
|
||||
|
||||
public TimeSpan DefuseTime { get; init; } = TimeSpan.FromSeconds(5);
|
||||
public TimeSpan DefuseRate { get; init; } = TimeSpan.FromMilliseconds(500);
|
||||
|
||||
public int DefuseReward { get; init; } = 20;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public enum PurchaseResult {
|
||||
/// <summary>
|
||||
/// The item cannot be purchased multiple times, and the player already owns it.
|
||||
/// </summary>
|
||||
ALREADY_OWNED,
|
||||
ALREADY_OWNED
|
||||
}
|
||||
|
||||
public static class PurchaseResultExtensions {
|
||||
|
||||
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);
|
||||
@@ -1,6 +0,0 @@
|
||||
using SpecialRoundAPI;
|
||||
|
||||
namespace SpecialRound.Events;
|
||||
|
||||
public class SpecialRoundStartEvent(AbstractSpecialRound round)
|
||||
: SpecialRoundEvent(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 = cvar.GetPrimitiveValue<int>();
|
||||
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}"));
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -14,19 +14,6 @@ namespace SpecialRound.Rounds;
|
||||
|
||||
public class SuppressedRound(IServiceProvider provider)
|
||||
: AbstractSpecialRound(provider), IPluginModule {
|
||||
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 BasePlugin? plugin;
|
||||
public override string Name => "Suppressed";
|
||||
public override IMsg Description => RoundMsgs.SPECIAL_ROUND_SUPPRESSED;
|
||||
@@ -47,7 +34,8 @@ public class SuppressedRound(IServiceProvider provider)
|
||||
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]);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<ProjectReference Include="..\API\API.csproj"/>
|
||||
<ProjectReference Include="..\CS2\CS2.csproj"/>
|
||||
<ProjectReference Include="..\Game\Game.csproj"/>
|
||||
<ProjectReference Include="..\SpecialRoundAPI\SpecialRoundAPI.csproj" />
|
||||
<ProjectReference Include="..\SpecialRoundAPI\SpecialRoundAPI.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -11,11 +11,13 @@ public static class SpecialRoundCollection {
|
||||
services.AddModBehavior<ISpecialRoundTracker, SpecialRoundTracker>();
|
||||
services.AddModBehavior<SpecialRoundSoundNotifier>();
|
||||
|
||||
services.AddModBehavior<SpeedRound>();
|
||||
services.AddModBehavior<BhopRound>();
|
||||
services.AddModBehavior<VanillaRound>();
|
||||
services.AddModBehavior<SuppressedRound>();
|
||||
services.AddModBehavior<SilentRound>();
|
||||
services.AddModBehavior<LowGravRound>();
|
||||
services.AddModBehavior<PistolRound>();
|
||||
services.AddModBehavior<RichRound>();
|
||||
services.AddModBehavior<SilentRound>();
|
||||
services.AddModBehavior<SpeedRound>();
|
||||
services.AddModBehavior<SuppressedRound>();
|
||||
services.AddModBehavior<VanillaRound>();
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ public class SpecialRoundSoundNotifier(IServiceProvider provider)
|
||||
: BaseListener(provider) {
|
||||
[UsedImplicitly]
|
||||
[EventHandler]
|
||||
public void OnSpecialRoundStart(SpecialRoundStartEvent ev) {
|
||||
public void OnSpecialRoundStart(SpecialRoundEnableEvent ev) {
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
player.EmitSound("UI.XP.Star.Spend", null, 0.8f);
|
||||
player.EmitSound("UI.XP.Star.Spend", null, 0.2f);
|
||||
}
|
||||
}
|
||||
@@ -30,20 +30,21 @@ public class SpecialRoundStarter(IServiceProvider provider)
|
||||
plugin?.RegisterListener<Listeners.OnMapStart>(onMapChange);
|
||||
}
|
||||
|
||||
public AbstractSpecialRound?
|
||||
TryStartSpecialRound(AbstractSpecialRound? round) {
|
||||
round ??= getSpecialRound();
|
||||
public List<AbstractSpecialRound> TryStartSpecialRound(
|
||||
List<AbstractSpecialRound>? rounds = null) {
|
||||
rounds ??= getSpecialRounds();
|
||||
|
||||
var roundStart = new SpecialRoundStartEvent(round);
|
||||
Bus.Dispatch(roundStart);
|
||||
Messenger.MessageAll(Locale[RoundMsgs.SPECIAL_ROUND_STARTED(rounds)]);
|
||||
|
||||
Messenger.MessageAll(Locale[RoundMsgs.SPECIAL_ROUND_STARTED(round)]);
|
||||
Messenger.MessageAll(Locale[round.Description]);
|
||||
foreach (var round in rounds) {
|
||||
var roundStart = new SpecialRoundEnableEvent(round);
|
||||
Bus.Dispatch(roundStart);
|
||||
Messenger.MessageAll(Locale[round.Description]);
|
||||
round.ApplyRoundEffects();
|
||||
}
|
||||
|
||||
round.ApplyRoundEffects();
|
||||
tracker.CurrentRound = round;
|
||||
tracker.RoundsSinceLastSpecial = 0;
|
||||
return round;
|
||||
return rounds;
|
||||
}
|
||||
|
||||
private void onMapChange(string mapName) { roundsSinceMapChange = 0; }
|
||||
@@ -62,16 +63,32 @@ 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) {
|
||||
@@ -79,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;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,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() { }
|
||||
@@ -19,5 +20,6 @@ public class SpecialRoundTracker : ISpecialRoundTracker, ITerrorModule,
|
||||
public void OnRoundEnd(GameStateUpdateEvent ev) {
|
||||
if (ev.NewState != State.FINISHED) return;
|
||||
CurrentRound = null;
|
||||
ActiveRounds.Clear();
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,11 @@ public record SpecialRoundsConfig {
|
||||
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;
|
||||
}
|
||||
@@ -25,7 +25,16 @@ public class RoundMsgs {
|
||||
public static IMsg SPECIAL_ROUND_PISTOL
|
||||
=> MsgFactory.Create(nameof(SPECIAL_ROUND_PISTOL));
|
||||
|
||||
public static IMsg SPECIAL_ROUND_STARTED(AbstractSpecialRound round) {
|
||||
return MsgFactory.Create(nameof(SPECIAL_ROUND_STARTED), round.Name);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,6 @@ 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."
|
||||
@@ -19,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);
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user