diff --git a/Jailbreak.sln b/Jailbreak.sln index f4de6fa2..a15be712 100644 --- a/Jailbreak.sln +++ b/Jailbreak.sln @@ -52,6 +52,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gangs.Boostrap", "mod\Gangs EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gangs", "Gangs", "{069A4F54-AE2A-4FDF-A5A8-F550B254C2A9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gangs.SpecialDayColorPerk", "mod\Gangs.SpecialDayColorPerk\Gangs.SpecialDayColorPerk.csproj", "{17AD9AA2-6014-47E3-B39D-4C9942C619DD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -142,6 +144,10 @@ Global {214E54DB-DD70-4B61-BFFC-596C2717E333}.Debug|Any CPU.Build.0 = Debug|Any CPU {214E54DB-DD70-4B61-BFFC-596C2717E333}.Release|Any CPU.ActiveCfg = Release|Any CPU {214E54DB-DD70-4B61-BFFC-596C2717E333}.Release|Any CPU.Build.0 = Release|Any CPU + {17AD9AA2-6014-47E3-B39D-4C9942C619DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17AD9AA2-6014-47E3-B39D-4C9942C619DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17AD9AA2-6014-47E3-B39D-4C9942C619DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17AD9AA2-6014-47E3-B39D-4C9942C619DD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {9135CCC9-66C5-4A9C-AE3C-91475B5F0437} = {177DA48D-8306-4102-918D-992569878581} @@ -166,5 +172,6 @@ Global {4035945A-80FA-49E7-A9BF-069FEA3213E5} = {069A4F54-AE2A-4FDF-A5A8-F550B254C2A9} {80F8E8F7-6976-414D-AFC0-17E1E3ABD746} = {069A4F54-AE2A-4FDF-A5A8-F550B254C2A9} {214E54DB-DD70-4B61-BFFC-596C2717E333} = {069A4F54-AE2A-4FDF-A5A8-F550B254C2A9} + {17AD9AA2-6014-47E3-B39D-4C9942C619DD} = {069A4F54-AE2A-4FDF-A5A8-F550B254C2A9} EndGlobalSection EndGlobal diff --git a/mod/Gangs.BombIconPerk/BombIconBootstrap.cs b/mod/Gangs.BombIconPerk/BombIconBootstrap.cs index 0987cb4f..e972fba3 100644 --- a/mod/Gangs.BombIconPerk/BombIconBootstrap.cs +++ b/mod/Gangs.BombIconPerk/BombIconBootstrap.cs @@ -1,11 +1,10 @@ -using CounterStrikeSharp.API.Core; -using GangsAPI.Services; +using GangsAPI.Services; using Microsoft.Extensions.DependencyInjection; namespace Gangs.BombIconPerk; public class BombIconBootstrap { - public BombIconBootstrap(IServiceProvider collection, BasePlugin plugin) { + public BombIconBootstrap(IServiceProvider collection) { new BombIconCommand(collection).Start(); collection.GetRequiredService() .Perks.Add(new BombPerk(collection)); diff --git a/mod/Gangs.BombIconPerk/BombIconCommand.cs b/mod/Gangs.BombIconPerk/BombIconCommand.cs index eef0b1d2..da1350e8 100644 --- a/mod/Gangs.BombIconPerk/BombIconCommand.cs +++ b/mod/Gangs.BombIconPerk/BombIconCommand.cs @@ -96,6 +96,7 @@ public async Task Execute(PlayerWrapper? executor, return CommandResult.SUCCESS; data.Unlocked |= icon; + data.Equipped = icon; await gangStats.SetForGang(gang, BombPerk.STAT_ID, data); diff --git a/mod/Gangs.Boostrap/GangsServiceExtension.cs b/mod/Gangs.Boostrap/GangsServiceExtension.cs index 0a2323f0..b1333ae6 100644 --- a/mod/Gangs.Boostrap/GangsServiceExtension.cs +++ b/mod/Gangs.Boostrap/GangsServiceExtension.cs @@ -18,6 +18,6 @@ public void Start(BasePlugin basePlugin) { var services = API.Gangs?.Services; if (services == null) return; - _ = new BombIconBootstrap(services, basePlugin); + _ = new BombIconBootstrap(services); } } \ No newline at end of file diff --git a/mod/Gangs.SpecialDayColorPerk/Gangs.SpecialDayColorPerk.csproj b/mod/Gangs.SpecialDayColorPerk/Gangs.SpecialDayColorPerk.csproj new file mode 100644 index 00000000..00f19816 --- /dev/null +++ b/mod/Gangs.SpecialDayColorPerk/Gangs.SpecialDayColorPerk.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + + + + + + + + ..\..\public\Jailbreak.Public\Mixin\GangsAPI.dll + + + + + + + + + diff --git a/mod/Gangs.SpecialDayColorPerk/SDColor.cs b/mod/Gangs.SpecialDayColorPerk/SDColor.cs new file mode 100644 index 00000000..0382de9e --- /dev/null +++ b/mod/Gangs.SpecialDayColorPerk/SDColor.cs @@ -0,0 +1,76 @@ +using System.Drawing; +using CounterStrikeSharp.API.Modules.Utils; + +namespace Gangs.SpecialDayColorPerk; + +[Flags] +public enum SDColor { + RED = 1 << 0, + ORANGE = 1 << 1, + YELLOW = 1 << 2, + GREEN = 1 << 3, + CYAN = 1 << 4, + BLUE = 1 << 5, + PURPLE = 1 << 6, + DEFAULT = 1 << 7, + RANDOM = 1 << 8 +} + +public static class SmokeColorExtensions { + public static int GetCost(this SDColor color) { + return color switch { + SDColor.RED => 2000, + SDColor.ORANGE => 1000, + SDColor.YELLOW => 1000, + SDColor.GREEN => 2000, + SDColor.CYAN => 5000, + SDColor.BLUE => 4000, + SDColor.PURPLE => 2000, + SDColor.DEFAULT => 1000, + SDColor.RANDOM => 10000, + _ => 0 + }; + } + + public static Color? GetColor(this SDColor color) { + return color switch { + SDColor.RED => Color.Red, + SDColor.ORANGE => Color.Orange, + SDColor.YELLOW => Color.Yellow, + SDColor.GREEN => Color.Green, + SDColor.CYAN => Color.Cyan, + SDColor.BLUE => Color.Blue, + SDColor.PURPLE => Color.Purple, + SDColor.DEFAULT => null, + SDColor.RANDOM => null, + _ => Color.White + }; + } + + public static char GetChatColor(this SDColor color) { + return color switch { + SDColor.RED => ChatColors.Red, + SDColor.ORANGE => ChatColors.Orange, + SDColor.YELLOW => ChatColors.Yellow, + SDColor.GREEN => ChatColors.Green, + SDColor.CYAN => ChatColors.LightBlue, + SDColor.BLUE => ChatColors.Blue, + SDColor.PURPLE => ChatColors.Purple, + SDColor.DEFAULT => ChatColors.White, + SDColor.RANDOM => ChatColors.White, + _ => ChatColors.White + }; + } + + public static Color? PickRandom(this SDColor color) { + var n = new Random().Next(Enum.GetValues().Length); + var available = Enum.GetValues() + .Where(c => color.HasFlag(c) && c.GetColor() != null) + .ToList(); + + // Gang bought the random perk, but no colors, sillies! + if (available.Count == 0) return null; + + return available[n % available.Count].GetColor(); + } +} \ No newline at end of file diff --git a/mod/Gangs.SpecialDayColorPerk/SDColorBootstrap.cs b/mod/Gangs.SpecialDayColorPerk/SDColorBootstrap.cs new file mode 100644 index 00000000..ae82df0c --- /dev/null +++ b/mod/Gangs.SpecialDayColorPerk/SDColorBootstrap.cs @@ -0,0 +1,12 @@ +using GangsAPI.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Gangs.SpecialDayColorPerk; + +public class SDColorBootstrap { + public SDColorBootstrap(IServiceProvider collection) { + new SDColorCommand(collection).Start(); + collection.GetRequiredService() + .Perks.Add(new SDColorPerk(collection)); + } +} \ No newline at end of file diff --git a/mod/Gangs.SpecialDayColorPerk/SDColorCommand.cs b/mod/Gangs.SpecialDayColorPerk/SDColorCommand.cs new file mode 100644 index 00000000..5a1ecd4b --- /dev/null +++ b/mod/Gangs.SpecialDayColorPerk/SDColorCommand.cs @@ -0,0 +1,131 @@ +using Gangs.BombIconPerk; +using GangsAPI; +using GangsAPI.Data; +using GangsAPI.Data.Command; +using GangsAPI.Exceptions; +using GangsAPI.Extensions; +using GangsAPI.Perks; +using GangsAPI.Permissions; +using GangsAPI.Services; +using GangsAPI.Services.Commands; +using GangsAPI.Services.Gang; +using GangsAPI.Services.Menu; +using GangsAPI.Services.Player; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Stats.Perk.Smoke; + +namespace Gangs.SpecialDayColorPerk; + +public class SDColorCommand(IServiceProvider provider) : ICommand { + private readonly ICommandManager commands = + provider.GetRequiredService(); + + private readonly IEcoManager eco = provider.GetRequiredService(); + + private readonly IGangChatPerk? gangChat = + provider.GetService(); + + private readonly IGangManager gangs = + provider.GetRequiredService(); + + private readonly IGangStatManager gangStats = + provider.GetRequiredService(); + + private readonly IStringLocalizer localizer = + provider.GetRequiredService(); + + private readonly IMenuManager menus = + provider.GetRequiredService(); + + private readonly IPlayerManager players = + provider.GetRequiredService(); + + private readonly IRankManager ranks = + provider.GetRequiredService(); + + public string Name => "css_sdcolor"; + public string[] Usage => [""]; + + public void Start() { commands.RegisterCommand(this); } + + public async Task Execute(PlayerWrapper? executor, + CommandInfoWrapper info) { + if (executor == null) return CommandResult.PLAYER_ONLY; + var player = await players.GetPlayer(executor.Steam) + ?? throw new PlayerNotFoundException(executor.Steam); + if (player.GangId == null) { + info.ReplySync(localizer.Get(MSG.NOT_IN_GANG)); + return CommandResult.SUCCESS; + } + + var gang = await gangs.GetGang(player.GangId.Value) + ?? throw new GangNotFoundException(player.GangId.Value); + + var (success, data) = + await gangStats.GetForGang(gang, SDColorPerk.STAT_ID); + + if (!success || data == null) data = new SDColorData(); + + if (info.ArgCount == 1) { + var menu = new SDColorMenu(provider, data); + await menus.OpenMenu(executor, menu); + return CommandResult.SUCCESS; + } + + SDColor color; + var query = string.Join('_', info.Args.Skip(1)).ToUpper(); + if (!int.TryParse(info[1], out var iconInt) || iconInt < 0) { + if (!Enum.TryParse(query, out color)) { + info.ReplySync(localizer.Get(MSG.COMMAND_INVALID_PARAM, info[1], + "a positive integer")); + return CommandResult.SUCCESS; + } + } else { color = (SDColor)iconInt; } + + if (!data.Unlocked.HasFlag(color)) { + var (canPurchase, minRank) = await ranks.CheckRank(player, + Perm.PURCHASE_PERKS); + + if (!canPurchase) { + info.ReplySync(localizer.Get(MSG.GENERIC_NOPERM_RANK, minRank.Name)); + return CommandResult.SUCCESS; + } + + var cost = color.GetCost(); + if (await eco.TryPurchase(executor, cost, + item: "Bomb Icon: " + color.ToString().ToTitleCase()) < 0) + return CommandResult.SUCCESS; + + data.Unlocked |= color; + data.Equipped = color; + + await gangStats.SetForGang(gang, SDColorPerk.STAT_ID, data); + + if (gangChat == null) return CommandResult.SUCCESS; + + await gangChat.SendGangChat(player, gang, + localizer.Get(MSG.PERK_PURCHASED, color.ToString())); + return CommandResult.SUCCESS; + } + + if (data.Equipped == color) return CommandResult.SUCCESS; + + var (canManage, required) = + await ranks.CheckRank(player, Perm.MANAGE_PERKS); + if (!canManage) { + info.ReplySync(localizer.Get(MSG.GENERIC_NOPERM_RANK, required.Name)); + return CommandResult.SUCCESS; + } + + data.Equipped = color; + await gangStats.SetForGang(gang, BombPerk.STAT_ID, data); + + if (gangChat == null) return CommandResult.SUCCESS; + + await gangChat.SendGangChat(player, gang, + localizer.Get(MSG.GANG_THING_SET, "SD Color", + color.ToString().ToTitleCase())); + return CommandResult.SUCCESS; + } +} \ No newline at end of file diff --git a/mod/Gangs.SpecialDayColorPerk/SDColorMenu.cs b/mod/Gangs.SpecialDayColorPerk/SDColorMenu.cs new file mode 100644 index 00000000..f8b799d2 --- /dev/null +++ b/mod/Gangs.SpecialDayColorPerk/SDColorMenu.cs @@ -0,0 +1,66 @@ +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Utils; +using Gangs.SpecialDayColorPerk; +using GangsAPI.Data; +using GangsAPI.Extensions; +using GangsAPI.Menu; +using GangsAPI.Services.Commands; +using Microsoft.Extensions.DependencyInjection; + +namespace Stats.Perk.Smoke; + +public class SDColorMenu(IServiceProvider provider, SDColorData data) + : AbstractPagedMenu(provider, NativeSenders.Chat) { + private readonly ICommandManager commands = + provider.GetRequiredService(); + + // Method to sort smoke colors + private int CompareSmokeColors(SDColor a, SDColor b) { + // If the icon is equipped, it should be first + if (a == data.Equipped) return -1; + if (b == data.Equipped) return 1; + + // If icon is unlocked, it should be next + // If both are unlocked, sort by cost (highest first) + if (data.Unlocked.HasFlag(a)) { + if (data.Unlocked.HasFlag(b)) return a.GetCost().CompareTo(b.GetCost()); + return -1; + } + + // If both are locked, sort by cost (lowest first) + if (data.Unlocked.HasFlag(b)) return 1; + return a.GetCost().CompareTo(b.GetCost()); + } + + + override protected Task> GetItems(PlayerWrapper player) { + var list = Enum.GetValues().ToList(); + list.Sort(CompareSmokeColors); + list.Insert(0, 0); + return Task.FromResult(list); + } + + override protected Task HandleItemSelection(PlayerWrapper player, + List items, int selectedIndex) { + commands.ProcessCommand(player, CommandCallingContext.Chat, "css_sdcolor", + items[selectedIndex].ToString()); + Close(player); + return Task.CompletedTask; + } + + override protected Task FormatItem(PlayerWrapper player, int index, + SDColor item) { + var name = item.ToString().ToTitleCase(); + if (item == 0) + return Task.FromResult( + $" {ChatColors.DarkBlue}Gang Perks: {ChatColors.LightBlue}Special Day Colors"); + if (item == data.Equipped) + return Task.FromResult( + $"{index}. {item.GetChatColor()}{name} {ChatColors.Green}({ChatColors.Lime}Equipped{ChatColors.Green})"); + if (data.Unlocked.HasFlag(item)) + return Task.FromResult( + $"{index}. {item.GetChatColor()}{name} {ChatColors.Green}({ChatColors.Grey}Unlocked{ChatColors.Green})"); + return Task.FromResult( + $"{index}. {item.GetChatColor()}{name} {ChatColors.DarkRed}({ChatColors.LightRed}{item.GetCost()}{ChatColors.DarkRed})"); + } +} \ No newline at end of file diff --git a/mod/Gangs.SpecialDayColorPerk/SDColorPerk.cs b/mod/Gangs.SpecialDayColorPerk/SDColorPerk.cs new file mode 100644 index 00000000..274f5f33 --- /dev/null +++ b/mod/Gangs.SpecialDayColorPerk/SDColorPerk.cs @@ -0,0 +1,45 @@ +using System.Diagnostics; +using Gangs.BaseImpl; +using GangsAPI.Data.Gang; +using GangsAPI.Services.Gang; +using GangsAPI.Services.Menu; +using Microsoft.Extensions.DependencyInjection; +using Stats.Perk.Smoke; + +namespace Gangs.SpecialDayColorPerk; + +public class SDColorPerk(IServiceProvider provider) + : BasePerk(provider) { + public override string StatId => STAT_ID; + public override string Name => "Special Day Color"; + public const string STAT_ID = "jb_sd_color"; + + private readonly IGangStatManager gangStats = + provider.GetRequiredService(); + + public override string? Description + => "Change the color of your gang during special days!"; + + public override Task GetCost(IGangPlayer player) { + return Task.FromResult(null); + } + + public override async Task GetMenu(IGangPlayer player) { + Debug.Assert(player.GangId != null, "player.GangId != null"); + var (success, data) = + await gangStats.GetForGang(player.GangId.Value, STAT_ID); + if (!success || data == null) data = new SDColorData(); + return new SDColorMenu(Provider, data); + } + + public override Task OnPurchase(IGangPlayer player) { + return Task.CompletedTask; + } + + public override SDColorData Value { get; set; } = new(); +} + +public class SDColorData { + public SDColor Unlocked { get; set; } + public SDColor Equipped { get; set; } +} \ No newline at end of file diff --git a/mod/Jailbreak.SpecialDay/Jailbreak.SpecialDay.csproj b/mod/Jailbreak.SpecialDay/Jailbreak.SpecialDay.csproj index e4b3a2ad..78f28541 100644 --- a/mod/Jailbreak.SpecialDay/Jailbreak.SpecialDay.csproj +++ b/mod/Jailbreak.SpecialDay/Jailbreak.SpecialDay.csproj @@ -10,11 +10,15 @@ + + + ..\..\public\Jailbreak.Public\Mixin\GangsAPI.dll + ..\..\public\Jailbreak.Public\Mixin\MStatsShared.dll diff --git a/mod/Jailbreak.SpecialDay/SpecialDayManager.cs b/mod/Jailbreak.SpecialDay/SpecialDayManager.cs index c2b6491e..591f2999 100644 --- a/mod/Jailbreak.SpecialDay/SpecialDayManager.cs +++ b/mod/Jailbreak.SpecialDay/SpecialDayManager.cs @@ -1,11 +1,22 @@ -using CounterStrikeSharp.API.Core; +using System.Drawing; +using System.Net; +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Modules.Utils; +using Gangs.SpecialDayColorPerk; +using GangsAPI.Data; +using GangsAPI.Perks; +using GangsAPI.Services.Gang; +using GangsAPI.Services.Player; using Jailbreak.Formatting.Extensions; using Jailbreak.Public; +using Jailbreak.Public.Extensions; using Jailbreak.Public.Mod.SpecialDay; using Jailbreak.Public.Mod.SpecialDay.Enums; using Jailbreak.Public.Utils; using Jailbreak.SpecialDay.SpecialDays; +using Microsoft.Extensions.DependencyInjection; using MStatsShared; namespace Jailbreak.SpecialDay; @@ -24,10 +35,49 @@ public bool InitiateSpecialDay(SDType type) { if (CurrentSD is ISpecialDayMessageProvider messaged) messaged.Locale.SpecialDayStart.ToAllChat(); + assignGangColors(); CurrentSD.Setup(); return true; } + private void assignGangColors() { + if (API.Gangs == null) return; + var players = API.Gangs.Services.GetService(); + var gangStats = API.Gangs.Services.GetService(); + if (players == null || gangStats == null) return; + var gangCache = new Dictionary(); + foreach (var player in Utilities.GetPlayers().Where(p => !p.IsBot)) { + var wrapper = new PlayerWrapper(player); + Task.Run(async () => { + var gangPlayer = await players.GetPlayer(wrapper.Steam); + if (gangPlayer?.GangId == null) return; + var gangId = gangPlayer.GangId.Value; + if (!gangCache.TryGetValue(gangId, out var color)) { + var (success, data) = + await gangStats.GetForGang(gangId, + SDColorPerk.STAT_ID); + if (!success || data == null) { + gangCache[gangId] = null; + return; + } + + color = data.Equipped; + gangCache[gangId] = color; + } + + + if (color != null) { + wrapper.PrintToChat(ChatColors.Grey + "Your gang will be " + + color.Value.GetChatColor() + color.Value.ToString().ToTitleCase() + + ChatColors.Grey + " during this special day."); + var toSet = color.Value.GetColor() ?? color.Value.PickRandom(); + if (toSet != null) + await Server.NextFrameAsync(() => player.SetColor(toSet.Value)); + } + }); + } + } + [GameEventHandler] public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info) { if (RoundUtil.IsWarmup()) return HookResult.Continue;