mirror of
https://github.com/MSWS/TTT.git
synced 2025-12-07 23:06:33 -08:00
Compare commits
10 Commits
0.10.0
...
0.10.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5736588484 | ||
|
|
8a894c65e8 | ||
|
|
0634af8ad8 | ||
|
|
9f6c3f7be4 | ||
|
|
9eb313e9f1 | ||
|
|
9b5563aa8e | ||
|
|
2058d0c780 | ||
|
|
ee7f34b435 | ||
|
|
783f6da6dd | ||
|
|
fca81c0577 |
@@ -11,6 +11,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.332"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.3"/>
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
<PackageReference Include="YamlDotNet" Version="16.3.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -5,4 +5,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>TTT.API</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -4,7 +4,7 @@ public interface IWeapon {
|
||||
/// <summary>
|
||||
/// The internal ID of the weapon, should match the ID of the weapon in the underlying game.
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
public string WeaponId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of ammo that is in reserve for this weapon.
|
||||
|
||||
@@ -16,9 +16,10 @@ public interface IInventoryManager {
|
||||
void RemoveWeapon(IOnlinePlayer player, string weaponId);
|
||||
|
||||
void RemoveWeapon(IOnlinePlayer player, IWeapon weapon) {
|
||||
RemoveWeapon(player, weapon.Id);
|
||||
RemoveWeapon(player, weapon.WeaponId);
|
||||
}
|
||||
|
||||
void RemoveWeaponInSlot(IOnlinePlayer player, int slot);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all weapons from the player.
|
||||
|
||||
@@ -37,10 +37,6 @@ public class CS2Body(IServiceProvider provider, CRagdollProp ragdoll,
|
||||
return this;
|
||||
}
|
||||
|
||||
public CS2Body WithWeapon(string weapon) {
|
||||
return WithWeapon(new BaseWeapon(weapon));
|
||||
}
|
||||
|
||||
public CS2Body WithKiller(IPlayer? killer) {
|
||||
Killer = killer;
|
||||
return this;
|
||||
|
||||
@@ -63,9 +63,12 @@ public static class CS2ServiceCollection {
|
||||
collection.AddModBehavior<LateSpawnListener>();
|
||||
collection.AddModBehavior<PlayerStatsTracker>();
|
||||
collection.AddModBehavior<RoundTimerListener>();
|
||||
collection.AddModBehavior<ScreenColorApplier>();
|
||||
|
||||
// Commands
|
||||
#if DEBUG
|
||||
collection.AddModBehavior<TestCommand>();
|
||||
#endif
|
||||
|
||||
collection.AddScoped<IGameManager, CS2GameManager>();
|
||||
collection.AddScoped<IInventoryManager, CS2InventoryManager>();
|
||||
|
||||
35
TTT/CS2/Command/Test/ScreenColorCommand.cs
Normal file
35
TTT/CS2/Command/Test/ScreenColorCommand.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Drawing;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.Extensions;
|
||||
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class ScreenColorCommand(IServiceProvider provider) : ICommand {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
public string Name => "screencolor";
|
||||
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
if (executor == null) return Task.FromResult(CommandResult.PLAYER_ONLY);
|
||||
Server.NextWorldUpdate(() => {
|
||||
var player = converter.GetPlayer(executor);
|
||||
float hold = 0.5f, fade = 0.5f;
|
||||
if (info.ArgCount >= 2) float.TryParse(info.Args[1], out hold);
|
||||
if (info.ArgCount >= 3) float.TryParse(info.Args[2], out fade);
|
||||
|
||||
player?.ColorScreen(Color.Red, hold, fade);
|
||||
|
||||
info.ReplySync("Colored your screen red.");
|
||||
});
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
}
|
||||
26
TTT/CS2/Command/Test/StateCommand.cs
Normal file
26
TTT/CS2/Command/Test/StateCommand.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Command;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
|
||||
namespace TTT.CS2.Command.Test;
|
||||
|
||||
public class StateCommand(IServiceProvider provider) : ICommand {
|
||||
private readonly IGameManager games =
|
||||
provider.GetRequiredService<IGameManager>();
|
||||
|
||||
public string Name => "state";
|
||||
public void Dispose() { }
|
||||
public void Start() { }
|
||||
|
||||
public Task<CommandResult>
|
||||
Execute(IOnlinePlayer? executor, ICommandInfo info) {
|
||||
if (games.ActiveGame == null) {
|
||||
info.ReplySync("ActiveGame is null.");
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
|
||||
info.ReplySync($"Current game state: {games.ActiveGame?.State}");
|
||||
return Task.FromResult(CommandResult.SUCCESS);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ public class TestCommand(IServiceProvider provider) : ICommand, IPluginModule {
|
||||
subCommands.Add("stop", new StopCommand(provider));
|
||||
subCommands.Add("forcealive", new ForceAliveCommand(provider));
|
||||
subCommands.Add("identifyall", new IdentifyAllCommand(provider));
|
||||
subCommands.Add("state", new StateCommand(provider));
|
||||
subCommands.Add("screencolor", new ScreenColorCommand(provider));
|
||||
}
|
||||
|
||||
public Task<CommandResult>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Drawing;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.UserMessages;
|
||||
|
||||
namespace TTT.CS2.Extensions;
|
||||
|
||||
@@ -31,7 +32,35 @@ public static class PlayerExtensions {
|
||||
if (!player.IsValid || pawn == null || !pawn.IsValid) return;
|
||||
|
||||
if (color.A == 255)
|
||||
color = Color.FromArgb(pawn.Render.A, color.R, color.G, color.B);
|
||||
color = Color.FromArgb(pawn.Render.A == 255 ? 255 : 254, color.R, color.G,
|
||||
color.B);
|
||||
pawn.SetColor(color);
|
||||
}
|
||||
|
||||
public enum FadeFlags {
|
||||
FADE_IN, FADE_OUT, FADE_STAYOUT
|
||||
}
|
||||
|
||||
public static void ColorScreen(this CCSPlayerController player, Color color,
|
||||
float hold = 0.1f, float fade = 0.2f, FadeFlags flags = FadeFlags.FADE_IN,
|
||||
bool withPurge = true) {
|
||||
var fadeMsg = UserMessage.FromId(106);
|
||||
|
||||
fadeMsg.SetInt("duration", Convert.ToInt32(fade * 512));
|
||||
fadeMsg.SetInt("hold_time", Convert.ToInt32(hold * 512));
|
||||
|
||||
var flag = flags switch {
|
||||
FadeFlags.FADE_IN => 0x0001,
|
||||
FadeFlags.FADE_OUT => 0x0002,
|
||||
FadeFlags.FADE_STAYOUT => 0x0008,
|
||||
_ => 0x0001
|
||||
};
|
||||
|
||||
if (withPurge) flag |= 0x0010;
|
||||
|
||||
fadeMsg.SetInt("flags", flag);
|
||||
fadeMsg.SetInt("color",
|
||||
color.R | color.G << 8 | color.B << 16 | color.A << 24);
|
||||
fadeMsg.Send(player);
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,40 @@ public static class VectorExtensions {
|
||||
(float)(Math.Sin(yaw) * cosPitch), (float)-Math.Sin(pitch));
|
||||
}
|
||||
|
||||
public static Vector ToRight(this QAngle angle) {
|
||||
var pitch = angle.X * (Math.PI / 180.0);
|
||||
var yaw = angle.Y * (Math.PI / 180.0);
|
||||
var roll = angle.Z * (Math.PI / 180.0);
|
||||
|
||||
var sinPitch = Math.Sin(pitch);
|
||||
var cosPitch = Math.Cos(pitch);
|
||||
var sinYaw = Math.Sin(yaw);
|
||||
var cosYaw = Math.Cos(yaw);
|
||||
var sinRoll = Math.Sin(roll);
|
||||
var cosRoll = Math.Cos(roll);
|
||||
|
||||
return new Vector((float)(sinYaw * sinPitch * cosRoll - cosYaw * sinRoll),
|
||||
(float)(-cosYaw * sinPitch * cosRoll - sinYaw * sinRoll),
|
||||
(float)(cosPitch * -sinRoll));
|
||||
}
|
||||
|
||||
public static Vector ToUp(this QAngle angle) {
|
||||
var pitch = angle.X * (Math.PI / 180.0);
|
||||
var yaw = angle.Y * (Math.PI / 180.0);
|
||||
var roll = angle.Z * (Math.PI / 180.0);
|
||||
|
||||
var sinPitch = Math.Sin(pitch);
|
||||
var cosPitch = Math.Cos(pitch);
|
||||
var sinYaw = Math.Sin(yaw);
|
||||
var cosYaw = Math.Cos(yaw);
|
||||
var sinRoll = Math.Sin(roll);
|
||||
var cosRoll = Math.Cos(roll);
|
||||
|
||||
return new Vector((float)(-cosYaw * sinPitch * cosRoll - sinYaw * sinRoll),
|
||||
(float)(-sinYaw * sinPitch * cosRoll + cosYaw * sinRoll),
|
||||
(float)(cosPitch * cosRoll));
|
||||
}
|
||||
|
||||
public static Vector Lerp(this Vector from, Vector to, float t) {
|
||||
return new Vector(from.X + (to.X - from.X) * t,
|
||||
from.Y + (to.Y - from.Y) * t, from.Z + (to.Z - from.Z) * t);
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
using TTT.API.Game;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Messages;
|
||||
using TTT.Game;
|
||||
using TTT.Game.Events.Game;
|
||||
|
||||
namespace TTT.CS2.Game;
|
||||
|
||||
public class CS2GameManager(IServiceProvider provider) : GameManager(provider) {
|
||||
protected readonly IMessenger messenger =
|
||||
provider.GetRequiredService<IMessenger>();
|
||||
|
||||
public override IGame CreateGame() {
|
||||
messenger.Debug($"Attempting to create a new CS2 game...");
|
||||
switch (ActiveGame) {
|
||||
case { State: State.IN_PROGRESS or State.COUNTDOWN }:
|
||||
throw new InvalidOperationException(
|
||||
@@ -14,6 +20,7 @@ public class CS2GameManager(IServiceProvider provider) : GameManager(provider) {
|
||||
return ActiveGame;
|
||||
}
|
||||
|
||||
messenger.Debug("Creating a new CS2 game instance...");
|
||||
ActiveGame = new CS2Game(Provider);
|
||||
|
||||
var ev = new GameInitEvent(ActiveGame);
|
||||
|
||||
@@ -11,6 +11,7 @@ using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.Game.Events.Body;
|
||||
using TTT.Game.Roles;
|
||||
|
||||
namespace TTT.CS2.GameHandlers;
|
||||
|
||||
@@ -32,7 +33,7 @@ public class BodySpawner(IServiceProvider provider) : IPluginModule {
|
||||
return HookResult.Continue;
|
||||
var player = ev.Userid;
|
||||
if (player == null || !player.IsValid) return HookResult.Continue;
|
||||
player.SetColor(Color.FromArgb(0, 0, 0, 0));
|
||||
player.SetColor(Color.FromArgb(0, 255, 255, 255));
|
||||
|
||||
var ragdollBody = makeGameRagdoll(player);
|
||||
var body = new CS2Body(provider, ragdollBody, converter.GetPlayer(player));
|
||||
@@ -40,7 +41,7 @@ public class BodySpawner(IServiceProvider provider) : IPluginModule {
|
||||
if (ev.Attacker != null && ev.Attacker.IsValid)
|
||||
body.WithKiller(converter.GetPlayer(ev.Attacker));
|
||||
|
||||
body.WithWeapon(ev.Weapon);
|
||||
body.WithWeapon(new BaseWeapon(ev.Weapon));
|
||||
|
||||
var bodyCreatedEvent = new BodyCreateEvent(body);
|
||||
bus.Dispatch(bodyCreatedEvent);
|
||||
@@ -53,7 +54,7 @@ public class BodySpawner(IServiceProvider provider) : IPluginModule {
|
||||
public HookResult OnStart(EventRoundStart ev, GameEventInfo _) {
|
||||
Server.NextWorldUpdate(() => {
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
player.SetColor(Color.FromArgb(254, 255, 255, 255));
|
||||
player.SetColor(Color.White);
|
||||
});
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.API.Storage;
|
||||
using TTT.Game;
|
||||
|
||||
@@ -15,6 +16,9 @@ public class RoundStart_GameStartHandler(IServiceProvider provider)
|
||||
provider.GetService<IStorage<TTTConfig>>()?.Load().GetAwaiter().GetResult()
|
||||
?? new TTTConfig();
|
||||
|
||||
private readonly IPlayerFinder finder =
|
||||
provider.GetRequiredService<IPlayerFinder>();
|
||||
|
||||
private readonly IGameManager games =
|
||||
provider.GetRequiredService<IGameManager>();
|
||||
|
||||
@@ -28,6 +32,9 @@ public class RoundStart_GameStartHandler(IServiceProvider provider)
|
||||
if (games.ActiveGame is { State: State.IN_PROGRESS or State.COUNTDOWN })
|
||||
return HookResult.Continue;
|
||||
|
||||
var count = finder.GetOnline().Count;
|
||||
if (count < config.RoundCfg.MinimumPlayers) return HookResult.Continue;
|
||||
|
||||
var game = games.CreateGame();
|
||||
game?.Start(config.RoundCfg.CountDownDuration);
|
||||
return HookResult.Continue;
|
||||
|
||||
@@ -19,4 +19,7 @@ public interface ITextSpawner {
|
||||
|
||||
IEnumerable<CPointWorldText> CreateTextHat(TextSetting setting,
|
||||
CCSPlayerController player);
|
||||
|
||||
IEnumerable<CPointWorldText> CreateTextScreen(TextSetting setting,
|
||||
CCSPlayerController player);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.CS2.RayTrace.Class;
|
||||
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
|
||||
|
||||
namespace TTT.CS2.Hats;
|
||||
@@ -36,6 +37,31 @@ public class TextSpawner : ITextSpawner {
|
||||
return [one, two];
|
||||
}
|
||||
|
||||
public IEnumerable<CPointWorldText> CreateTextScreen(TextSetting setting,
|
||||
CCSPlayerController player) {
|
||||
var screen = spawnScreen(setting, player);
|
||||
return [screen];
|
||||
}
|
||||
|
||||
public CPointWorldText spawnScreen(TextSetting setting,
|
||||
CCSPlayerController player, float xOff = 0, float yOff = 0,
|
||||
float zDist = 50) {
|
||||
if (player.Pawn.Value == null || player.Pawn.Value.AbsRotation == null)
|
||||
throw new Exception("Failed to get player rotation");
|
||||
var eyes = player.GetEyePosition().Clone()!;
|
||||
var localAngle = player.Pawn.Value.AbsRotation.Clone()!;
|
||||
var forward = localAngle.Clone()!.ToForward();
|
||||
var right = localAngle.ToRight();
|
||||
var up = localAngle.ToUp();
|
||||
var inFront = eyes + forward * zDist;
|
||||
var centered = inFront + right * xOff + up * yOff;
|
||||
|
||||
var ent = CreateText(setting, centered,
|
||||
new QAngle(localAngle.X + 180, localAngle.Y + 90, localAngle.Z + 270));
|
||||
ent.AcceptInput("SetParent", player.Pawn.Value, null, "!activator");
|
||||
return ent;
|
||||
}
|
||||
|
||||
private CPointWorldText spawnHatPart(TextSetting setting,
|
||||
CCSPlayerController player, float yRot) {
|
||||
var position = player.Pawn.Value?.AbsOrigin;
|
||||
@@ -46,19 +72,10 @@ public class TextSpawner : ITextSpawner {
|
||||
position.Add(new Vector(0, 0, 72));
|
||||
rotation = new QAngle(rotation.X, rotation.Y + yRot, rotation.Z + 90);
|
||||
|
||||
position.Add(GetRightVector(rotation) * -10);
|
||||
position.Add(rotation.ToRight() * -10);
|
||||
|
||||
var ent = CreateText(setting, position, rotation);
|
||||
ent.AcceptInput("SetParent", player.Pawn.Value, null, "!activator");
|
||||
return ent;
|
||||
}
|
||||
|
||||
public static Vector GetRightVector(QAngle rotation) {
|
||||
var forward = new Vector {
|
||||
X = (float)Math.Cos(rotation.Y * Math.PI / 180),
|
||||
Y = (float)Math.Sin(rotation.Y * Math.PI / 180),
|
||||
Z = 0
|
||||
};
|
||||
return forward;
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ public class PlayerStatsTracker(IServiceProvider provider) : IListener {
|
||||
revealedDeaths.Add(gamePlayer.Slot);
|
||||
}
|
||||
|
||||
// Needs to be higher so we detect the kill the game ends
|
||||
// Needs to be higher so we detect the kill before the game ends
|
||||
// in the case that this is the last player
|
||||
[EventHandler(Priority = Priority.HIGHER)]
|
||||
public void OnKill(PlayerDeathEvent ev) {
|
||||
|
||||
@@ -43,7 +43,7 @@ public class RoundTimerListener(IServiceProvider provider)
|
||||
player.Respawn();
|
||||
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
player.SetColor(Color.FromArgb(254, 255, 255, 255));
|
||||
player.SetColor(Color.White);
|
||||
});
|
||||
|
||||
return;
|
||||
|
||||
30
TTT/CS2/Listeners/ScreenColorApplier.cs
Normal file
30
TTT/CS2/Listeners/ScreenColorApplier.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Drawing;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.Extensions;
|
||||
using TTT.CS2.Roles;
|
||||
using TTT.Game.Events.Player;
|
||||
using TTT.Game.Listeners;
|
||||
|
||||
namespace TTT.CS2.Listeners;
|
||||
|
||||
public class ScreenColorApplier(IServiceProvider provider)
|
||||
: BaseListener(provider) {
|
||||
private readonly IPlayerConverter<CCSPlayerController> converter =
|
||||
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
|
||||
|
||||
[EventHandler]
|
||||
public void OnRoleAssign(PlayerRoleAssignEvent ev) {
|
||||
if (ev.Role is SpectatorRole) return;
|
||||
|
||||
var player = converter.GetPlayer(ev.Player);
|
||||
var alphaColor = Color.FromArgb(16, ev.Role.Color);
|
||||
if (player != null)
|
||||
player.ColorScreen(alphaColor, 5f, 5f,
|
||||
flags: PlayerExtensions.FadeFlags.FADE_OUT);
|
||||
|
||||
player?.PrintToCenterHtml("You are a " + ev.Role.Name, 20);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using TTT.API;
|
||||
using TTT.API.Player;
|
||||
using TTT.CS2.Extensions;
|
||||
@@ -13,17 +14,67 @@ public class CS2InventoryManager(
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
|
||||
gamePlayer.GiveNamedItem(weapon.Id);
|
||||
if (weapon.ReserveAmmo == null && weapon.CurrentAmmo == null) return;
|
||||
var weaponBase = gamePlayer.GetWeaponBase(weapon.Id);
|
||||
if (weaponBase == null) return;
|
||||
if (weapon.CurrentAmmo != null)
|
||||
weaponBase.Clip1 = weapon.CurrentAmmo.Value;
|
||||
if (weapon.ReserveAmmo != null)
|
||||
weaponBase.Clip2 = weapon.ReserveAmmo.Value;
|
||||
giveWeapon(gamePlayer, weapon);
|
||||
});
|
||||
}
|
||||
|
||||
private void giveWeapon(CCSPlayerController player, IWeapon weapon) {
|
||||
if (player.Team is CsTeam.None or CsTeam.Spectator) return;
|
||||
|
||||
// Give the weapon
|
||||
player.GiveNamedItem(weapon.WeaponId);
|
||||
|
||||
// Set ammo if applicable
|
||||
var weaponBase = player.GetWeaponBase(weapon.WeaponId);
|
||||
if (weaponBase == null) return;
|
||||
if (weapon.CurrentAmmo != null) weaponBase.Clip1 = weapon.CurrentAmmo.Value;
|
||||
if (weapon.ReserveAmmo != null) weaponBase.Clip2 = weapon.ReserveAmmo.Value;
|
||||
}
|
||||
|
||||
public static gear_slot_t IntToSlot(int slot)
|
||||
=> slot switch {
|
||||
0 => gear_slot_t.GEAR_SLOT_RIFLE,
|
||||
1 => gear_slot_t.GEAR_SLOT_PISTOL,
|
||||
2 => gear_slot_t.GEAR_SLOT_KNIFE,
|
||||
3 => gear_slot_t.GEAR_SLOT_UTILITY,
|
||||
4 => gear_slot_t.GEAR_SLOT_C4,
|
||||
_ => gear_slot_t.GEAR_SLOT_FIRST
|
||||
};
|
||||
|
||||
public static int SlotToInt(gear_slot_t slot)
|
||||
=> slot switch {
|
||||
gear_slot_t.GEAR_SLOT_RIFLE => 0,
|
||||
gear_slot_t.GEAR_SLOT_PISTOL => 1,
|
||||
gear_slot_t.GEAR_SLOT_KNIFE => 2,
|
||||
gear_slot_t.GEAR_SLOT_UTILITY => 3,
|
||||
gear_slot_t.GEAR_SLOT_C4 => 4,
|
||||
_ => -1
|
||||
};
|
||||
|
||||
private void clearSlot(CCSPlayerController player,
|
||||
params gear_slot_t[] slots) {
|
||||
if (player.Team is CsTeam.None or CsTeam.Spectator) return;
|
||||
var weapons = player.Pawn.Value?.WeaponServices?.MyWeapons;
|
||||
if (weapons == null || weapons.Count == 0) return;
|
||||
|
||||
foreach (var weapon in weapons) {
|
||||
if (!weapon.IsValid || weapon.Value == null) continue;
|
||||
if (!weapon.Value.IsValid
|
||||
|| !weapon.Value.DesignerName.StartsWith("weapon_"))
|
||||
continue;
|
||||
if (weapon.Value.Entity == null) continue;
|
||||
if (!weapon.Value.OwnerEntity.IsValid) continue;
|
||||
var weaponBase = weapon.Value.As<CBaseEntity>();
|
||||
if (!weaponBase.IsValid || (weaponBase.Entity == null)) continue;
|
||||
|
||||
var weaponData = (weaponBase as CCSWeaponBase)?.VData;
|
||||
if (weaponData == null) continue;
|
||||
if (!slots.Contains(weaponData.GearSlot)) continue;
|
||||
|
||||
weapon.Value.AddEntityIOEvent("Kill", weapon.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveWeapon(IOnlinePlayer player, string weaponId) {
|
||||
Server.NextWorldUpdate(() => {
|
||||
if (!player.IsAlive) return;
|
||||
@@ -51,6 +102,17 @@ public class CS2InventoryManager(
|
||||
});
|
||||
}
|
||||
|
||||
public void RemoveWeaponInSlot(IOnlinePlayer player, int slot) {
|
||||
Server.NextWorldUpdate(() => {
|
||||
if (!player.IsAlive) return;
|
||||
|
||||
var gamePlayer = converter.GetPlayer(player);
|
||||
if (gamePlayer == null) return;
|
||||
|
||||
clearSlot(gamePlayer, IntToSlot(slot));
|
||||
});
|
||||
}
|
||||
|
||||
public void RemoveAllWeapons(IOnlinePlayer player) {
|
||||
Server.NextWorldUpdate(() => {
|
||||
if (!player.IsAlive) return;
|
||||
|
||||
@@ -80,6 +80,6 @@ public class PlayerDamagedEvent(IOnlinePlayer player, IOnlinePlayer? attacker,
|
||||
}
|
||||
}
|
||||
|
||||
public string? Weapon { get; private set; }
|
||||
public string? Weapon { get; init; }
|
||||
public bool IsCanceled { get; set; }
|
||||
}
|
||||
@@ -8,7 +8,8 @@ namespace TTT.Game.Listeners.Loggers;
|
||||
|
||||
public class PlayerActionsLogger(IServiceProvider provider)
|
||||
: BaseListener(provider) {
|
||||
[EventHandler]
|
||||
// Needs to be higher so we detect the kill before the game ends
|
||||
[EventHandler(Priority = Priority.HIGHER)]
|
||||
[UsedImplicitly]
|
||||
public void OnPlayerKill(PlayerDeathEvent ev) {
|
||||
if (Games.ActiveGame is not { State: State.IN_PROGRESS }) return;
|
||||
|
||||
@@ -25,6 +25,9 @@ public class PlayerJoinStarting(IServiceProvider provider)
|
||||
$"There are {playerCount} Players online, starting the game...");
|
||||
|
||||
var game = Games.CreateGame();
|
||||
|
||||
if (game == null)
|
||||
Messenger.DebugAnnounce("Failed to create a new game instance.");
|
||||
game?.Start(config.RoundCfg.CountDownDuration);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace TTT.Game.Roles;
|
||||
|
||||
public class BaseWeapon(string id, int? reserve = null, int? current = null)
|
||||
: IWeapon {
|
||||
public string Id { get; } = id;
|
||||
public class BaseWeapon(string id, int? reserve = null,
|
||||
int? current = null) : IWeapon {
|
||||
public string WeaponId { get; } = id;
|
||||
public int? ReserveAmmo { get; } = reserve;
|
||||
public int? CurrentAmmo { get; } = current;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
@@ -18,6 +19,7 @@ public class DeagleDamageListener(IServiceProvider provider)
|
||||
|
||||
private readonly IShop shop = provider.GetRequiredService<IShop>();
|
||||
|
||||
[UsedImplicitly]
|
||||
[EventHandler]
|
||||
public void OnDamage(PlayerDamagedEvent ev) {
|
||||
Messenger.Debug("DeagleDamageListener: OnDamage");
|
||||
@@ -42,10 +44,16 @@ public class DeagleDamageListener(IServiceProvider provider)
|
||||
var victimRole = Roles.GetRoles(victim);
|
||||
|
||||
shop.RemoveItem(attacker, deagleItem);
|
||||
if (!config.DoesFriendlyFire && attackerRole.Intersect(victimRole).Any())
|
||||
if (!config.DoesFriendlyFire && attackerRole.Intersect(victimRole).Any()) {
|
||||
Messenger.DebugAnnounce(
|
||||
"DeagleDamageListener: Friendly fire is off, roles intersect");
|
||||
return;
|
||||
}
|
||||
|
||||
Messenger.DebugAnnounce(
|
||||
"DeagleDamageListener: One-shot kill conditions met");
|
||||
if (victim is not IOnlinePlayer onlineVictim) return;
|
||||
Messenger.DebugAnnounce("DeagleDamageListener: One-shot kill applied");
|
||||
onlineVictim.Health = 0;
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class OneShotDeagle(IServiceProvider provider) : IWeapon, IShopItem {
|
||||
|
||||
string IShopItem.Id => ID;
|
||||
|
||||
public string Id => deagleConfigStorage.Weapon;
|
||||
public string WeaponId => deagleConfigStorage.Weapon;
|
||||
|
||||
public int? ReserveAmmo { get; init; } = 0;
|
||||
public int? CurrentAmmo { get; init; } = 1;
|
||||
|
||||
@@ -6,6 +6,6 @@ namespace TTT.Test.Fakes;
|
||||
public class FakeInventoryManager : IInventoryManager {
|
||||
public void GiveWeapon(IOnlinePlayer player, IWeapon weapon) { }
|
||||
public void RemoveWeapon(IOnlinePlayer player, string weaponId) { }
|
||||
|
||||
public void RemoveWeaponInSlot(IOnlinePlayer player, int slot) { }
|
||||
public void RemoveAllWeapons(IOnlinePlayer player) { }
|
||||
}
|
||||
69
TTT/Test/Shop/Items/DeagleTests.cs
Normal file
69
TTT/Test/Shop/Items/DeagleTests.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TTT.API.Events;
|
||||
using TTT.API.Game;
|
||||
using TTT.API.Player;
|
||||
using TTT.Game.Events.Player;
|
||||
using TTT.Shop;
|
||||
using TTT.Shop.Items;
|
||||
using Xunit;
|
||||
|
||||
namespace TTT.Test.Shop.Items;
|
||||
|
||||
public class DeagleTests {
|
||||
private readonly IServiceProvider provider;
|
||||
private readonly IEventBus bus;
|
||||
private readonly TestPlayer testPlayer;
|
||||
private readonly IOnlinePlayer victim, survivor;
|
||||
private readonly IShop shop;
|
||||
private readonly OneShotDeagle item;
|
||||
|
||||
public DeagleTests(IServiceProvider provider) {
|
||||
this.provider = provider;
|
||||
var games = provider.GetRequiredService<IGameManager>();
|
||||
var finder = provider.GetRequiredService<IPlayerFinder>();
|
||||
shop = provider.GetRequiredService<IShop>();
|
||||
bus = provider.GetRequiredService<IEventBus>();
|
||||
item = new OneShotDeagle(provider);
|
||||
|
||||
testPlayer = (finder.AddPlayer(TestPlayer.Random()) as TestPlayer)!;
|
||||
victim = finder.AddPlayer(TestPlayer.Random());
|
||||
survivor = finder.AddPlayer(TestPlayer.Random());
|
||||
|
||||
games.CreateGame()?.Start();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deagle_Kills_OnDamage() {
|
||||
bus.RegisterListener(new DeagleDamageListener(provider));
|
||||
shop.GiveItem(testPlayer, item);
|
||||
|
||||
var playerDmgEvent =
|
||||
new PlayerDamagedEvent(victim, testPlayer, 1, 99) {
|
||||
Weapon = item.WeaponId
|
||||
};
|
||||
bus.Dispatch(playerDmgEvent);
|
||||
|
||||
Assert.Equal(0, victim.Health);
|
||||
Assert.False(victim.IsAlive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deagle_DoesNotKill_AfterFirstKill() {
|
||||
bus.RegisterListener(new DeagleDamageListener(provider));
|
||||
shop.GiveItem(testPlayer, item);
|
||||
|
||||
var playerDmgEvent =
|
||||
new PlayerDamagedEvent(victim, testPlayer, 1, 99) {
|
||||
Weapon = item.WeaponId
|
||||
};
|
||||
bus.Dispatch(playerDmgEvent);
|
||||
|
||||
var secondDmgEvent =
|
||||
new PlayerDamagedEvent(survivor, testPlayer, 1, 99) {
|
||||
Weapon = item.WeaponId
|
||||
};
|
||||
bus.Dispatch(secondDmgEvent);
|
||||
|
||||
Assert.NotEqual(0, survivor.Health);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,15 @@ public class TestPlayer(string id, string name) : IOnlinePlayer {
|
||||
public int Health { get; set; } = 100;
|
||||
public int MaxHealth { get; set; } = 100;
|
||||
public int Armor { get; set; } = 100;
|
||||
public bool IsAlive { get; set; } = true;
|
||||
|
||||
public bool IsAlive {
|
||||
get => Health > 0;
|
||||
set {
|
||||
if (!value)
|
||||
Health = 0;
|
||||
else if (Health <= 0) { Health = 1; }
|
||||
}
|
||||
}
|
||||
|
||||
public static TestPlayer Random() {
|
||||
return new TestPlayer(new Random().NextInt64().ToString(),
|
||||
|
||||
Reference in New Issue
Block a user