Compare commits

...

11 Commits

Author SHA1 Message Date
MSWS
8e9ec76975 Populate dictionary prior to adding 2025-10-02 23:44:45 -07:00
MSWS
63cd3e782f Apply default config to body paint 2025-10-02 23:44:00 -07:00
MSWS
a1acb91151 Register body paint service 2025-10-02 23:42:59 -07:00
MSWS
355f39a12a refactor: Add Body Paint, refactor namespaces +semver:minor (resolves #82)
```
- Add descriptions and labels for "Body Paint" in English language file
- Update Detective's DNA Scanner and Sticker configurations for improved namespace specificity
- Implement BodyPaintMsgs class for handling body paint item messages
- Introduce BodyPaintListener for handling body paint purchase and usage events
- Restructure PlayerEvent files to align with new ShopAPI directory and namespace
- Add BodyPaintConfig for shop item configuration with default settings
```
2025-10-02 23:29:11 -07:00
MSWS
87815d099e Append Item to 1ShotDeagle Class, start work on BodyPaintItem 2025-10-02 23:08:00 -07:00
MSWS
6f904df564 Fix role assignment logging 2025-10-02 21:18:39 -07:00
MSWS
42a2fc2caf Fix gloves item (ref #81) 2025-10-02 18:49:43 -07:00
MSWS
306be07e8f Reformat and Cleanup 2025-10-02 18:30:21 -07:00
MSWS
233396fbd1 refactor: Refactor round timer and remove C4 listener logic
- Adjust handling of round win conditions in `RoundTimerListener.cs` by adding commands to ignore and re-enable conditions during round end process; improve round end subscription logic and add `Dispose` method to clean up resources.
- Remove addition of `C4Listener` from the service collection in `C4ShopItem.cs`.
- Delete `C4Listener.cs`, removing event handlers for bomb-related events.
2025-10-02 15:50:08 -07:00
MSWS
a1194008ad feat: Refactor name formatting and add C4 event handling +semver:minor
```
- Refactor `CS2Player.cs` to improve name formatting with `CreatePaddedName`, eliminating redundant methods and enhancing alignment.
- Integrate `C4Listener` into `C4ShopItem.cs` for improved C4 item handling in the shop.
- Comment out the `RoundEnd_GameEndHandler` registration in `CS2ServiceCollection.cs` to potentially defer its functional roles.
- Refactor `EndRound` method in `RoundUtil.cs` to utilize `VirtualFunctions.TerminateRoundFunc`, comment out obsolete implementations.
- Update `RoundTimerListener.cs` to incorporate `System.Reactive.Concurrency`, optimize round timing logic, and introduce resource disposal to prevent leaks.
- Implement `C4Listener.cs` to handle bomb events and announce activities, enhancing player communication.
- Simplify `StationItem.cs` by removing unused imports and redundant debug announcements while maintaining core functionality.
- Adjust `RoundBasedGame.cs` to ensure the game state is accurately set to `IN_PROGRESS` post player preparation, without changing game flow logic.
```
2025-10-02 15:21:43 -07:00
MSWS
5bc52acf3c Fix shop events not being called 2025-10-02 13:35:42 -07:00
34 changed files with 240 additions and 76 deletions

View File

@@ -52,7 +52,7 @@ public static class CS2ServiceCollection {
collection.AddModBehavior<DamageCanceler>();
collection.AddModBehavior<PlayerConnectionsHandler>();
collection.AddModBehavior<PropMover>();
collection.AddModBehavior<RoundEnd_GameEndHandler>();
// collection.AddModBehavior<RoundEnd_GameEndHandler>();
collection.AddModBehavior<RoundStart_GameStartHandler>();
// Damage Cancelers

View File

@@ -0,0 +1,35 @@
using Microsoft.Extensions.DependencyInjection;
using ShopAPI;
using ShopAPI.Configs;
using TTT.API.Extensions;
using TTT.API.Player;
using TTT.API.Storage;
using TTT.Game.Roles;
using TTT.Shop.Items.Traitor.BodyPaint;
namespace TTT.CS2.Items.BodyPaint;
public static class BodyPaintServicesCollection {
public static void AddBodyPaintServices(this IServiceCollection collection) {
collection.AddModBehavior<BodyPaintItem>();
collection.AddModBehavior<BodyPaintListener>();
}
}
public class BodyPaintItem(IServiceProvider provider)
: RoleRestrictedItem<TraitorRole>(provider) {
private readonly BodyPaintConfig config = provider
.GetService<IStorage<BodyPaintConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new BodyPaintConfig();
public override string Name => Locale[BodyPaintMsgs.SHOP_ITEM_BODY_PAINT];
public override string Description
=> Locale[BodyPaintMsgs.SHOP_ITEM_BODY_PAINT_DESC];
public override ShopItemConfig Config => config;
public override void OnPurchase(IOnlinePlayer player) { }
}

View File

@@ -0,0 +1,60 @@
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using ShopAPI;
using ShopAPI.Configs;
using ShopAPI.Events;
using TTT.API.Events;
using TTT.API.Player;
using TTT.API.Storage;
using TTT.CS2.API;
using TTT.CS2.Extensions;
using TTT.Game.Events.Body;
using TTT.Game.Listeners;
using TTT.Shop.Items.Traitor.BodyPaint;
namespace TTT.CS2.Items.BodyPaint;
public class BodyPaintListener(IServiceProvider provider)
: BaseListener(provider) {
private readonly BodyPaintConfig config =
provider.GetService<IStorage<BodyPaintConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new BodyPaintConfig();
private readonly Dictionary<IPlayer, int> uses = new();
private readonly IShop shop = provider.GetRequiredService<IShop>();
private readonly IBodyTracker bodies =
provider.GetRequiredService<IBodyTracker>();
[UsedImplicitly]
[EventHandler]
public void OnPurchase(PlayerPurchaseItemEvent ev) {
if (ev.Item is not BodyPaintItem) return;
if (ev.Player is not IOnlinePlayer online) return;
uses.TryAdd(online, 0);
uses[online] += config.MaxUses;
}
[UsedImplicitly]
[EventHandler]
public void BodyIdentify(BodyIdentifyEvent ev) {
if (!bodies.Bodies.TryGetValue(ev.Body, out var body)) return;
if (ev.Identifier == null || !usePaint(ev.Identifier)) return;
ev.IsCanceled = true;
body.SetColor(config.ColorToApply);
}
private bool usePaint(IPlayer player) {
if (player is not IOnlinePlayer online) return false;
if (!uses.TryGetValue(player, out var useCount)) return false;
if (useCount <= 0) return false;
uses[player] = useCount - 1;
if (uses[player] > 0) return true;
shop.RemoveItem<BodyPaintItem>(online);
Messenger.Message(online, Locale[BodyPaintMsgs.SHOP_ITEM_BODY_PAINT_OUT]);
return true;
}
}

View File

@@ -0,0 +1,14 @@
using TTT.Locale;
namespace TTT.Shop.Items.Traitor.BodyPaint;
public class BodyPaintMsgs {
public static IMsg SHOP_ITEM_BODY_PAINT
=> MsgFactory.Create(nameof(SHOP_ITEM_BODY_PAINT));
public static IMsg SHOP_ITEM_BODY_PAINT_DESC
=> MsgFactory.Create(nameof(SHOP_ITEM_BODY_PAINT_DESC));
public static IMsg SHOP_ITEM_BODY_PAINT_OUT
=> MsgFactory.Create(nameof(SHOP_ITEM_BODY_PAINT_OUT));
}

View File

@@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection;
using ShopAPI;
using ShopAPI.Configs;
using ShopAPI.Configs.Detective;
using TTT.API.Events;
using TTT.API.Game;
using TTT.API.Player;

View File

@@ -20,7 +20,7 @@ public class DnaMsgs {
public static IMsg SHOP_ITEM_DNA_SCANNED_OTHER(IRole victimRole,
IPlayer player, string explanation) {
return MsgFactory.Create(nameof(SHOP_ITEM_DNA_SCANNED_OTHER),
GameMsgs.GetRolePrefix(victimRole), player.Name);
GameMsgs.GetRolePrefix(victimRole), player.Name, explanation);
}
public static IMsg

View File

@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using ShopAPI;
using ShopAPI.Configs;
using ShopAPI.Configs.Detective;
using TTT.API.Extensions;
using TTT.API.Player;
using TTT.API.Storage;

View File

@@ -1,6 +1,7 @@
using CounterStrikeSharp.API.Core;
using Microsoft.Extensions.DependencyInjection;
using ShopAPI.Configs;
using ShopAPI.Configs.Traitor;
using TTT.API.Extensions;
using TTT.API.Player;
using TTT.API.Role;

View File

@@ -1,6 +1,7 @@
using CounterStrikeSharp.API;
using Microsoft.Extensions.DependencyInjection;
using ShopAPI.Configs;
using ShopAPI.Configs.Detective;
using TTT.API.Extensions;
using TTT.API.Storage;
using TTT.CS2.Extensions;

View File

@@ -8,7 +8,6 @@ using Microsoft.Extensions.DependencyInjection;
using ShopAPI;
using ShopAPI.Configs;
using TTT.API;
using TTT.API.Messages;
using TTT.API.Player;
using TTT.CS2.Extensions;
using TTT.Game.Roles;
@@ -24,9 +23,6 @@ public abstract class StationItem(IServiceProvider provider,
protected readonly IPlayerConverter<CCSPlayerController> Converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly IMessenger messenger =
provider.GetRequiredService<IMessenger>();
protected readonly Dictionary<CPhysicsPropMultiplayer, StationInfo> props =
new();
@@ -45,7 +41,6 @@ public abstract class StationItem(IServiceProvider provider,
public void Start(BasePlugin? plugin) {
Start();
messenger.DebugAnnounce("Starting StationItem2 ");
plugin
?.RegisterListener<
CounterStrikeSharp.API.Core.Listeners.OnServerPrecacheResources>(m => {

View File

@@ -1,6 +1,7 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using TTT.API.Events;
using TTT.API.Player;
@@ -25,6 +26,7 @@ public class BodyPickupListener(IServiceProvider provider)
private readonly IAliveSpoofer? spoofer =
provider.GetService<IAliveSpoofer>();
[UsedImplicitly]
[EventHandler]
public void OnPropPickup(PropPickupEvent ev) {
if (!bodies.TryLookup(ev.Prop.Index.ToString(), out var body)) return;
@@ -35,10 +37,10 @@ public class BodyPickupListener(IServiceProvider provider)
var identifyEvent = new BodyIdentifyEvent(body, online);
Bus.Dispatch(identifyEvent);
if (identifyEvent.IsCanceled) return;
}
[EventHandler]
[UsedImplicitly]
[EventHandler(IgnoreCanceled = true)]
public void OnIdentify(BodyIdentifyEvent ev) {
ev.Body.IsIdentified = true;

View File

@@ -1,4 +1,5 @@
using System.Drawing;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
@@ -30,6 +31,11 @@ public class RoundTimerListener(IServiceProvider provider)
private readonly IPlayerConverter<CCSPlayerController> converter =
provider.GetRequiredService<IPlayerConverter<CCSPlayerController>>();
private readonly IScheduler scheduler = provider
.GetRequiredService<IScheduler>();
private IDisposable? endTimer;
[UsedImplicitly]
[EventHandler(IgnoreCanceled = true)]
public void OnRoundStart(GameStateUpdateEvent ev) {
@@ -49,12 +55,21 @@ public class RoundTimerListener(IServiceProvider provider)
return;
}
if (ev.NewState == State.FINISHED) endTimer?.Dispose();
if (ev.NewState != State.IN_PROGRESS) return;
var duration = config.RoundCfg.RoundDuration(ev.Game.Players.Count);
Messenger.DebugAnnounce("Total duration: {0} for {1} player", duration,
ev.Game.Players.Count);
Server.NextWorldUpdate(() => {
RoundUtil.SetTimeRemaining((int)config.RoundCfg
.RoundDuration(ev.Game.Players.Count)
.TotalSeconds);
Server.ExecuteCommand("mp_ignore_round_win_conditions 0");
RoundUtil.SetTimeRemaining((int)duration.TotalSeconds);
});
endTimer?.Dispose();
endTimer = scheduler.Schedule(duration, () => {
Server.NextWorldUpdate(() => {
Messenger.DebugAnnounce("Time is up!");
ev.Game.EndGame(EndReason.TIMEOUT(new InnocentRole(provider)));
});
});
}
@@ -65,11 +80,6 @@ public class RoundTimerListener(IServiceProvider provider)
revealRoles(ev.Game);
// If CS caused the round to end, we will have 0 time left
// in this case, CS automatically handles the end of round stuff
// so we don't need to do anything
if (RoundUtil.GetTimeRemaining() <= 1) return;
Server.NextWorldUpdate(() => {
var endReason = endRound(ev);
@@ -81,8 +91,11 @@ public class RoundTimerListener(IServiceProvider provider)
var timer = Observable.Timer(
config.RoundCfg.TimeBetweenRounds, Scheduler);
timer.Subscribe(_
=> Server.NextWorldUpdate(() => RoundUtil.EndRound(endReason)));
timer.Subscribe(_ => Server.NextWorldUpdate(() => {
Server.ExecuteCommand("mp_ignore_round_win_conditions 1");
RoundUtil.EndRound(endReason);
Server.ExecuteCommand("mp_ignore_round_win_conditions 0");
}));
});
}
@@ -115,4 +128,10 @@ public class RoundTimerListener(IServiceProvider provider)
new EventNextlevelChanged(true).FireEvent(false);
}
public override void Dispose() {
base.Dispose();
endTimer?.Dispose();
}
}

View File

@@ -112,15 +112,29 @@ public class CS2Player : IOnlinePlayer {
// Goal: Pad the name to a fixed width for better alignment in logs
// Left-align ID, right-align name
private string createPaddedName() {
var idPart = $"({getSuffix(Id, 5)})";
var effectivePadding = namePadding - idPart.Length;
var namePart = Name.Length >= effectivePadding ?
getSuffix(Name, effectivePadding) :
Name.PadLeft(effectivePadding);
return $"{idPart} {namePart}";
return CreatePaddedName(Id, Name, namePadding + 8);
}
private string getSuffix(string s, int len) {
return s.Length <= len ? s : s[^len..];
public static string CreatePaddedName(string id, string name, int len) {
var suffix = id.Length > 5 ? id[^5..] : id.PadLeft(5, '0');
var prefix = $"({suffix})";
var baseStr = $"{prefix} {name}";
if (baseStr.Length == len) return baseStr;
if (baseStr.Length < len) {
// Pad spaces so the name ends up right-aligned
var padding = len - (prefix.Length + name.Length);
return prefix + new string(' ', padding + 1) + name;
}
// Too long, cut off from the end of the name
var availableForName = len - (prefix.Length + 1);
if (availableForName < 0) availableForName = 0;
var trimmedName = name.Length > availableForName ?
name[..availableForName] :
name;
return $"{prefix} {trimmedName}";
}
}

View File

@@ -1,16 +1,16 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
namespace TTT.CS2.Utils;
public static class RoundUtil {
private static readonly
MemoryFunctionVoid<nint, float, RoundEndReason, nint, nint>
TerminateRoundFunc =
new(GameData.GetSignature("CCSGameRules_TerminateRound"));
// private static readonly
// MemoryFunctionVoid<nint, float, RoundEndReason, nint, nint>
// TerminateRoundFunc =
// new(GameData.GetSignature("CCSGameRules_TerminateRound"));
private static IEnumerable<CCSTeam>? _teamManager;
@@ -52,7 +52,9 @@ public static class RoundUtil {
var gameRules = ServerUtil.GameRulesProxy;
if (gameRules == null || gameRules.GameRules == null) return;
// TODO: Figure out what these params do
TerminateRoundFunc.Invoke(gameRules.GameRules.Handle, 5f, reason, 0, 0);
// TerminateRoundFunc.Invoke(gameRules.GameRules.Handle, 5f, reason, 0, 0);
VirtualFunctions.TerminateRoundFunc.Invoke(gameRules.GameRules.Handle,
reason, 5f, 0, 0);
}
public static void SetTeamScore(CsTeam team, int score) {

View File

@@ -14,4 +14,8 @@ SHOP_ITEM_STATION_HURT: "Hurt Station"
SHOP_ITEM_STATION_HURT_DESC: "A station that hurts non-Traitors around it."
SHOP_ITEM_CAMO: "Camouflage"
SHOP_ITEM_CAMO_DESC: "Disguise yourself and make yourself harder to see."
SHOP_ITEM_CAMO_DESC: "Disguise yourself and make yourself 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_OUT: "%PREFIX% You ran out of body paint."

View File

@@ -9,23 +9,25 @@ namespace TTT.Game.Listeners.Loggers;
public class PlayerActionsLogger(IServiceProvider provider)
: BaseListener(provider) {
// Needs to be higher so we detect the kill before the game ends
[EventHandler(Priority = Priority.HIGH)]
[UsedImplicitly]
[EventHandler(Priority = Priority.HIGH)]
public void OnPlayerKill(PlayerDeathEvent ev) {
if (Games.ActiveGame is not { State: State.IN_PROGRESS }) return;
Games.ActiveGame.Logger.LogAction(new DeathAction(Provider, ev));
}
[UsedImplicitly]
[EventHandler]
public void OnPlayerDamage(PlayerDamagedEvent ev) {
if (Games.ActiveGame is not { State: State.IN_PROGRESS }) return;
Games.ActiveGame.Logger.LogAction(new DamagedAction(Provider, ev));
}
[UsedImplicitly]
[EventHandler]
public void OnPlayerAssignedRole(PlayerRoleAssignEvent ev) {
if (Games.ActiveGame is not { State: State.IN_PROGRESS }) return;
Games.ActiveGame.Logger.LogAction(
Games.ActiveGame?.Logger.LogAction(
new RoleAssignedAction(ev.Player, ev.Role));
}
}

View File

@@ -172,7 +172,6 @@ public class RoundBasedGame(IServiceProvider provider) : IGame {
return;
}
State = State.IN_PROGRESS;
foreach (var player in online) inventory.RemoveAllWeapons(player);
@@ -180,6 +179,8 @@ public class RoundBasedGame(IServiceProvider provider) : IGame {
RoleAssigner.AssignRoles(online, Roles);
players.AddRange(online);
State = State.IN_PROGRESS;
var traitors = ((IGame)this).GetAlive(typeof(TraitorRole)).Count;
var nonTraitors = online.Count - traitors;
Messenger?.MessageAll(Locale[

View File

@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using ShopAPI;
using ShopAPI.Configs;
using ShopAPI.Configs.Detective;
using TTT.API.Extensions;
using TTT.API.Player;
using TTT.API.Storage;

View File

@@ -31,7 +31,7 @@ public class DeagleDamageListener(IServiceProvider provider)
if (attacker == null) return;
var deagleItem = shop.GetOwnedItems(attacker)
.FirstOrDefault(s => s is OneShotDeagle);
.FirstOrDefault(s => s is OneShotDeagleItem);
if (deagleItem == null) return;
if (ev.Weapon != config.Weapon)

View File

@@ -10,12 +10,12 @@ namespace TTT.Shop.Items;
public static class DeagleServiceCollection {
public static void AddDeagleServices(this IServiceCollection collection) {
collection.AddModBehavior<OneShotDeagle>();
collection.AddModBehavior<OneShotDeagleItem>();
collection.AddModBehavior<DeagleDamageListener>();
}
}
public class OneShotDeagle(IServiceProvider provider)
public class OneShotDeagleItem(IServiceProvider provider)
: BaseItem(provider), IWeapon {
private readonly OneShotDeagleConfig deagleConfigStorage = provider
.GetService<IStorage<OneShotDeagleConfig>>()

View File

@@ -9,29 +9,20 @@ using TTT.API.Storage;
using TTT.Game.Events.Body;
using TTT.Game.Events.Game;
using TTT.Game.Listeners;
using TTT.Shop.Events;
namespace TTT.Shop.Items.Traitor.Gloves;
public class GlovesListener(IServiceProvider provider)
: BaseListener(provider) {
private readonly GlovesConfig item =
private readonly GlovesConfig config =
provider.GetService<IStorage<GlovesConfig>>()
?.Load()
.GetAwaiter()
.GetResult() ?? new GlovesConfig();
private readonly IShop shop = provider.GetRequiredService<IShop>();
private readonly Dictionary<IPlayer, int> uses = new();
[UsedImplicitly]
[EventHandler]
public void OnPurchase(PlayerPurchaseItemEvent ev) {
if (ev.Item is not GlovesItem) return;
uses[ev.Player] = item.MaxUses;
}
[UsedImplicitly]
[EventHandler]
public void BodyCreate(BodyCreateEvent ev) {
@@ -40,11 +31,12 @@ public class GlovesListener(IServiceProvider provider)
ev.Body.Killer = null;
Messenger.Message(online,
Locale[
GlovesMsgs.SHOP_ITEM_GLOVES_USED_KILL(uses[online], item.MaxUses)]);
GlovesMsgs.SHOP_ITEM_GLOVES_USED_KILL(uses[online], config.MaxUses)]);
}
[UsedImplicitly]
[EventHandler]
[EventHandler(Priority =
Priority.HIGH)] // High priority to cancel before other handlers
public void BodyIdentify(BodyIdentifyEvent ev) {
if (ev.Identifier == null || !useGloves(ev.Identifier)) return;
ev.IsCanceled = true;
@@ -52,7 +44,7 @@ public class GlovesListener(IServiceProvider provider)
Messenger.Message(ev.Identifier,
Locale[
GlovesMsgs.SHOP_ITEM_GLOVES_USED_BODY(uses[ev.Identifier],
item.MaxUses)]);
config.MaxUses)]);
}
[UsedImplicitly]
@@ -63,11 +55,16 @@ public class GlovesListener(IServiceProvider provider)
}
private bool useGloves(IPlayer player) {
uses.TryGetValue(player, out var useCount);
if (player is not IOnlinePlayer online) return false;
if (!uses.TryGetValue(player, out var useCount)) {
if (!shop.HasItem<GlovesItem>(online)) return false;
uses[player] = config.MaxUses - 1;
return true;
}
if (useCount <= 0) return false;
uses[player] = useCount - 1;
if (useCount - 1 > 0) return true;
if (player is not IOnlinePlayer online) return true;
shop.RemoveItem<GlovesItem>(online);
Messenger.Message(online, Locale[GlovesMsgs.SHOP_ITEM_GLOVES_WORN_OUT]);
return true;

View File

@@ -1,11 +1,11 @@
using Microsoft.Extensions.DependencyInjection;
using ShopAPI;
using ShopAPI.Events;
using TTT.API;
using TTT.API.Events;
using TTT.API.Messages;
using TTT.API.Player;
using TTT.Locale;
using TTT.Shop.Events;
namespace TTT.Shop;
@@ -54,6 +54,10 @@ public class Shop(IServiceProvider provider) : ITerrorModule, IShop {
return canPurchase;
}
var purchaseEvent = new PlayerPurchaseItemEvent(player, item);
bus.Dispatch(purchaseEvent);
if (purchaseEvent.IsCanceled) return PurchaseResult.PURCHASE_CANCELED;
AddBalance(player, -cost, item.Name);
GiveItem(player, item);

View File

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

View File

@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using ShopAPI;
using TTT.API.Extensions;
using TTT.CS2.Items.BodyPaint;
using TTT.CS2.Items.Camouflage;
using TTT.CS2.Items.DNA;
using TTT.CS2.Items.Station;
@@ -25,6 +26,7 @@ public static class ShopServiceCollection {
collection.AddModBehavior<BuyCommand>();
collection.AddModBehavior<BalanceCommand>();
collection.AddBodyPaintServices();
collection.AddC4Services();
collection.AddCamoServices();
collection.AddDamageStation();

View File

@@ -11,8 +11,9 @@ public static class ShopMsgs {
public static IMsg SHOP_CANNOT_PURCHASE
=> MsgFactory.Create(nameof(SHOP_CANNOT_PURCHASE));
public static IMsg SHOP_PURCHASED(IShopItem item)
=> MsgFactory.Create(nameof(SHOP_PURCHASED), item.Name);
public static IMsg SHOP_PURCHASED(IShopItem item) {
return MsgFactory.Create(nameof(SHOP_PURCHASED), item.Name);
}
public static IMsg SHOP_ITEM_NOT_FOUND(string query) {
return MsgFactory.Create(nameof(SHOP_ITEM_NOT_FOUND), query);

View File

@@ -0,0 +1,9 @@
using System.Drawing;
namespace ShopAPI.Configs;
public record BodyPaintConfig : ShopItemConfig {
public override int Price { get; init; } = 60;
public int MaxUses { get; init; } = 1;
public Color ColorToApply { get; init; } = Color.GreenYellow;
}

View File

@@ -1,4 +1,4 @@
namespace ShopAPI.Configs;
namespace ShopAPI.Configs.Detective;
public record DnaScannerConfig : ShopItemConfig {
public override int Price { get; init; } = 100;

View File

@@ -1,6 +1,6 @@
using System.Drawing;
namespace ShopAPI.Configs;
namespace ShopAPI.Configs.Detective;
public record HealthStationConfig : StationConfig {
public override string UseSound { get; init; } = "sounds/buttons/blip1";

View File

@@ -1,4 +1,4 @@
namespace ShopAPI.Configs;
namespace ShopAPI.Configs.Detective;
public record StickerConfig : ShopItemConfig {
public override int Price { get; init; } = 70;

View File

@@ -1,6 +1,6 @@
using System.Drawing;
namespace ShopAPI.Configs;
namespace ShopAPI.Configs.Traitor;
public record DamageStationConfig : StationConfig {
public override int HealthIncrements { get; init; } = -15;

View File

@@ -2,7 +2,7 @@
using TTT.API.Player;
using TTT.Game.Events.Player;
namespace TTT.Shop.Events;
namespace ShopAPI.Events;
public class PlayerBalanceEvent(IPlayer player, int oldBalance, int newBalance,
string? reason) : PlayerEvent(player), ICancelableEvent {

View File

@@ -1,8 +1,7 @@
using ShopAPI;
using TTT.API.Player;
using TTT.API.Player;
using TTT.Game.Events.Player;
namespace TTT.Shop.Events;
namespace ShopAPI.Events;
public abstract class PlayerItemEvent(IPlayer player, IShopItem item)
: PlayerEvent(player) {

View File

@@ -1,8 +1,7 @@
using ShopAPI;
using TTT.API.Events;
using TTT.API.Events;
using TTT.API.Player;
namespace TTT.Shop.Events;
namespace ShopAPI.Events;
public class PlayerPurchaseItemEvent(IPlayer player, IShopItem item)
: PlayerItemEvent(player, item), ICancelableEvent {

View File

@@ -11,7 +11,7 @@ namespace TTT.Test.Shop.Items;
public class DeagleTests {
private readonly IEventBus bus;
private readonly OneShotDeagle item;
private readonly OneShotDeagleItem item;
private readonly IServiceProvider provider;
private readonly IShop shop;
private readonly TestPlayer testPlayer;
@@ -23,7 +23,7 @@ public class DeagleTests {
var finder = provider.GetRequiredService<IPlayerFinder>();
shop = provider.GetRequiredService<IShop>();
bus = provider.GetRequiredService<IEventBus>();
item = new OneShotDeagle(provider);
item = new OneShotDeagleItem(provider);
testPlayer = (finder.AddPlayer(TestPlayer.Random()) as TestPlayer)!;
victim = finder.AddPlayer(TestPlayer.Random());