mirror of
https://github.com/MSWS/TTT.git
synced 2025-12-06 22:36:35 -08:00
Compare commits
11 Commits
0.13.0-dev
...
0.13.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e9ec76975 | ||
|
|
63cd3e782f | ||
|
|
a1acb91151 | ||
|
|
355f39a12a | ||
|
|
87815d099e | ||
|
|
6f904df564 | ||
|
|
42a2fc2caf | ||
|
|
306be07e8f | ||
|
|
233396fbd1 | ||
|
|
a1194008ad | ||
|
|
5bc52acf3c |
@@ -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
|
||||
|
||||
35
TTT/CS2/Items/BodyPaint/BodyPaintItem.cs
Normal file
35
TTT/CS2/Items/BodyPaint/BodyPaintItem.cs
Normal 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) { }
|
||||
}
|
||||
60
TTT/CS2/Items/BodyPaint/BodyPaintListener.cs
Normal file
60
TTT/CS2/Items/BodyPaint/BodyPaintListener.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
14
TTT/CS2/Items/BodyPaint/BodyPaintMsgs.cs
Normal file
14
TTT/CS2/Items/BodyPaint/BodyPaintMsgs.cs
Normal 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));
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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."
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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[
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>>()
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
9
TTT/ShopAPI/Configs/BodyPaintConfig.cs
Normal file
9
TTT/ShopAPI/Configs/BodyPaintConfig.cs
Normal 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;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace ShopAPI.Configs;
|
||||
namespace ShopAPI.Configs.Detective;
|
||||
|
||||
public record DnaScannerConfig : ShopItemConfig {
|
||||
public override int Price { get; init; } = 100;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace ShopAPI.Configs;
|
||||
namespace ShopAPI.Configs.Detective;
|
||||
|
||||
public record StickerConfig : ShopItemConfig {
|
||||
public override int Price { get; init; } = 70;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
@@ -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) {
|
||||
@@ -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 {
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user