Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/raffle #29

Merged
merged 2 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Gangs.sln
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatsTracker", "src\StatsTr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EcoRewards", "src\EcoRewards\EcoRewards.csproj", "{253C7948-3411-4860-BDDE-B1CA23FCE4DC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raffle", "Raffle\Raffle.csproj", "{05B36B8C-F430-411B-9B8D-61EB14D5E5FC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -78,6 +80,10 @@ Global
{253C7948-3411-4860-BDDE-B1CA23FCE4DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{253C7948-3411-4860-BDDE-B1CA23FCE4DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{253C7948-3411-4860-BDDE-B1CA23FCE4DC}.Release|Any CPU.Build.0 = Release|Any CPU
{05B36B8C-F430-411B-9B8D-61EB14D5E5FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{05B36B8C-F430-411B-9B8D-61EB14D5E5FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{05B36B8C-F430-411B-9B8D-61EB14D5E5FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{05B36B8C-F430-411B-9B8D-61EB14D5E5FC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{74B15261-4B12-4EF6-859A-E46B315E7DD3} = {3AB7703F-880F-4A41-96EE-B891FA888C65}
Expand All @@ -90,5 +96,6 @@ Global
{1899055E-62B8-4907-85A2-DFE22531729E} = {32D392D5-C809-45A1-A5FB-58C941D86057}
{B850CFA3-AFE8-4012-8BC2-9A4BC12B9748} = {AC07CD29-5C9D-4AD1-99C7-01DABAB8D0EC}
{253C7948-3411-4860-BDDE-B1CA23FCE4DC} = {AC07CD29-5C9D-4AD1-99C7-01DABAB8D0EC}
{05B36B8C-F430-411B-9B8D-61EB14D5E5FC} = {AC07CD29-5C9D-4AD1-99C7-01DABAB8D0EC}
EndGlobalSection
EndGlobal
10 changes: 10 additions & 0 deletions Raffle/IRaffleManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Raffle;

public interface IRaffleManager {
Raffle? Raffle { get; }

bool StartRaffle(int buyIn);
bool AreEntriesOpen();
void SetEntriesOpen(float seconds);
void DrawWinner();
}
27 changes: 27 additions & 0 deletions Raffle/Raffle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Raffle;

public class Raffle(int buyIn) {
private readonly HashSet<ulong> players = [];

public int BuyIn => buyIn;

public int Value => players.Count * buyIn;

public int TotalPlayers => players.Count;

public void AddPlayer(ulong player) { players.Add(player); }

/// <summary>
/// Get the winner of the raffle
/// </summary>
/// <returns>The winner, or null if there are no players</returns>
public ulong? GetWinner() {
if (players.Count == 0) return null;
var random = new Random();
var winnerIndex = random.Next(players.Count);
// remove the winner from the list of players
var winner = players.ElementAt(winnerIndex);
players.Remove(winner);
return winner;
}
}
13 changes: 13 additions & 0 deletions Raffle/Raffle.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\src\GangsAPI\GangsAPI.csproj"/>
</ItemGroup>

</Project>
12 changes: 12 additions & 0 deletions Raffle/RaffleCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using GangsAPI.Extensions;
using Microsoft.Extensions.DependencyInjection;

namespace Raffle;

public static class RaffleCollection {
public static void RegisterRaffle(this IServiceCollection provider) {
provider.AddPluginBehavior<IRaffleManager, RaffleManager>();
provider.AddPluginBehavior<RaffleCommand>();
provider.AddPluginBehavior<StartRaffleCommand>();
}
}
37 changes: 37 additions & 0 deletions Raffle/RaffleCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using GangsAPI.Data;
using GangsAPI.Data.Command;
using GangsAPI.Services;
using GangsAPI.Services.Commands;
using Microsoft.Extensions.DependencyInjection;

namespace Raffle;

public class RaffleCommand(IServiceProvider provider) : ICommand {
private readonly IEcoManager eco = provider.GetRequiredService<IEcoManager>();

private readonly IRaffleManager raffle =
provider.GetRequiredService<IRaffleManager>();

public string Name => "css_raffle";

public async Task<CommandResult> Execute(PlayerWrapper? executor,
CommandInfoWrapper info) {
if (executor == null) return CommandResult.PLAYER_ONLY;
if (info.ArgCount != 1) return CommandResult.PRINT_USAGE;

if (raffle.Raffle == null)
// no raffle is currently running
return CommandResult.SUCCESS;

if (!raffle.AreEntriesOpen())
// entries are closed
return CommandResult.SUCCESS;

if (await eco.TryPurchase(executor, raffle.Raffle.BuyIn, true, "Raffle Ticket",
true) < 0)
return CommandResult.SUCCESS;

raffle.Raffle.AddPlayer(executor.Steam);
return CommandResult.SUCCESS;
}
}
105 changes: 105 additions & 0 deletions Raffle/RaffleManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Cvars;
using GangsAPI;
using Microsoft.Extensions.Localization;
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;

namespace Raffle;

public class RaffleManager(IStringLocalizer locale)
: IPluginBehavior, IRaffleManager {
public static FakeConVar<float> CV_RAFFLE_CHANCE =
new("cs2_gangs_raffle_chance", "The chance of a raffle starting per round",
0.1f);

public static FakeConVar<int> CV_RAFFLE_COOLDOWN =
new("cs2_gangs_raffle_cooldown", "Minimum number of rounds between raffles",
2);

public static FakeConVar<int> CV_RAFFLE_MINIMUM =
new("cs2_gangs_raffle_min", "Minimum amount per player", 2);

public static FakeConVar<int> CV_RAFFLE_MAXIMUM =
new("cs2_gangs_raffle_max", "Maximum amount per player", 2);

public static FakeConVar<float> CV_RAFFLE_DURATION =
new("cs2_gangs_raffle_duration", "Time to give playeres to enter raffle",
20);

private static readonly Random rng = new();
private int cooldownRounds;

private Timer? entryTimer;
private BasePlugin? plugin;

public void Start(BasePlugin? plugin, bool hotReload) {
if (plugin == null) return;
this.plugin = plugin;
}

public Raffle? Raffle { get; private set; }

public bool StartRaffle(int buyIn) {
if (Raffle != null || plugin == null) return false;
Raffle = new Raffle(buyIn);

SetEntriesOpen(CV_RAFFLE_DURATION.Value);
return true;
}

public bool AreEntriesOpen() { return entryTimer != null; }

public void SetEntriesOpen(float seconds) {
entryTimer?.Kill();
if (plugin == null || Raffle == null) return;
Server.PrintToChatAll(locale.Get(MSG.RAFFLE_BEGIN, Raffle?.BuyIn ?? 0));

entryTimer = plugin.AddTimer(seconds, () => {
entryTimer = null;
if (Raffle == null) return;

Server.PrintToChatAll(locale.Get(MSG.RAFFLE_PRE_ANNOUNCE, Raffle.Value,
Raffle.TotalPlayers));

plugin.AddTimer(5, DrawWinner);
});
}

public void DrawWinner() {
if (Raffle == null || plugin == null) return;
ulong? winner;
do { winner = Raffle.GetWinner(); } while (winner != null
&& Raffle.TotalPlayers > 0);

if (winner == null) {
Server.PrintToChatAll(locale.Get(MSG.GENERIC_ERROR_INFO,
"Could not find a winner"));
Raffle = null;
return;
}

var name = Utilities.GetPlayerFromSteamId(winner.Value)?.PlayerName
?? winner.ToString() ?? "";

Server.PrintToChatAll(locale.Get(MSG.RAFFLE_WINNER, name,
(1.0f / (Raffle.TotalPlayers + 1)).ToString("P1")));
Raffle = null;
}

[GameEventHandler]
public HookResult OnRoundStart(EventRoundStart ev, GameEventInfo info) {
if (RoundUtil.IsWarmup()) return HookResult.Continue;
if (cooldownRounds > 0) {
cooldownRounds--;
return HookResult.Continue;
}

if (rng.NextDouble() > CV_RAFFLE_CHANCE.Value) return HookResult.Continue;
var amo = rng.Next(CV_RAFFLE_MINIMUM.Value, CV_RAFFLE_MAXIMUM.Value);
StartRaffle(amo);
cooldownRounds = CV_RAFFLE_COOLDOWN.Value;
return HookResult.Continue;
}
}
42 changes: 42 additions & 0 deletions Raffle/RoundUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using CounterStrikeSharp.API;

namespace Raffle;

public static class RoundUtil {
public static int GetTimeElapsed() {
var gamerules = ServerExtensions.GetGameRules();
if (gamerules == null) return 0;
var freezeTime = gamerules.FreezeTime;
return (int)(Server.CurrentTime - gamerules.RoundStartTime - freezeTime);
}

public static int GetTimeRemaining() {
var gamerules = ServerExtensions.GetGameRules();
if (gamerules == null) return 0;
return gamerules.RoundTime - GetTimeElapsed();
}

public static void SetTimeRemaining(int seconds) {
var gamerules = ServerExtensions.GetGameRules();
if (gamerules == null) return;
gamerules.RoundTime = GetTimeElapsed() + seconds;
var proxy = ServerExtensions.GetGameRulesProxy();
if (proxy == null) return;
Utilities.SetStateChanged(proxy, "CCSGameRulesProxy", "m_pGameRules");
}

public static void AddTimeRemaining(int time) {
var gamerules = ServerExtensions.GetGameRules();
if (gamerules == null) return;
gamerules.RoundTime += time;

var proxy = ServerExtensions.GetGameRulesProxy();
if (proxy == null) return;
Utilities.SetStateChanged(proxy, "CCSGameRulesProxy", "m_pGameRules");
}

public static bool IsWarmup() {
var rules = ServerExtensions.GetGameRules();
return rules == null || rules.WarmupPeriod;
}
}
25 changes: 25 additions & 0 deletions Raffle/ServerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;

namespace Raffle;

public static class ServerExtensions {
/// <summary>
/// Get the current CCSGameRules for the server
/// </summary>
/// <returns></returns>
public static CCSGameRules? GetGameRules() {
// From killstr3ak
return Utilities
.FindAllEntitiesByDesignerName<CCSGameRulesProxy>("cs_gamerules")
.First()
.GameRules;
}

public static CCSGameRulesProxy? GetGameRulesProxy() {
// From killstr3ak
return Utilities
.FindAllEntitiesByDesignerName<CCSGameRulesProxy>("cs_gamerules")
.FirstOrDefault();
}
}
26 changes: 26 additions & 0 deletions Raffle/StartRaffleCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using GangsAPI.Data;
using GangsAPI.Data.Command;
using GangsAPI.Services.Commands;
using Microsoft.Extensions.DependencyInjection;

namespace Raffle;

public class StartRaffleCommand(IServiceProvider provider) : ICommand {
private readonly IRaffleManager raffle =
provider.GetRequiredService<IRaffleManager>();

public string Name => "css_startraffle";
public string[] RequiredFlags => ["@css/root"];
public string[] Usage => ["", "<amount>"];

public Task<CommandResult> Execute(PlayerWrapper? executor,
CommandInfoWrapper info) {
var amo = 100;
if (info.ArgCount == 2)
if (!int.TryParse(info.Args[1], out amo))
return Task.FromResult(CommandResult.PRINT_USAGE);

raffle.StartRaffle(amo);
return Task.FromResult(CommandResult.SUCCESS);
}
}
5 changes: 4 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,8 @@
"rank.cannot.owner": "%prefix%You cannot {0} as the owner, use %color.command%/gang transfer <player>%color.default% to transfer ownership.",
"command.invite.doorpolicy": "%prefix%Your door policy must be set to invite-only to send invites.",
"menu.format.invitation": "%color.special%{0}%color.default% invited %color.target%{1}%color.default% on %color.emph%{2}%color.default%.",
"menu.format.request": "%color.target%{0}%color.default% requested to join on %color.emph%{1}%color.default%."
"menu.format.request": "%color.target%{0}%color.default% requested to join on %color.emph%{1}%color.default%.",
"raffle.begin": "%prefix%A raffle for %color.currency%{0} %currency%%s%%color.default% has begun. Type %color.command%/raffle %color.default% to enter.",
"raffle.preannounce": "%prefix%Raffle closed! Collected %color.currency%{0} %currency%%s% across %color.number%{1}%color.default% entrant%s%.",
"raffle.winner": "%prefix%%color.target%{0}%color.default% won the raffle with a %color.number%{1}%color.default% chance!"
}
2 changes: 2 additions & 0 deletions src/CS2/Gangs/GangServiceCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Localization;
using Mock;
using Raffle;
using SQLImpl;
using Stats.Perk;
using StatsTracker;
Expand Down Expand Up @@ -41,6 +42,7 @@ public void ConfigureServices(IServiceCollection serviceCollection) {
serviceCollection.RegisterStatsTracker();
serviceCollection.RegisterPerks();
serviceCollection.RegisterRewards();
serviceCollection.RegisterRaffle();

serviceCollection.AddPluginBehavior<PlayerJoinCreationListener>();
serviceCollection
Expand Down
1 change: 1 addition & 0 deletions src/CS2/Gangs/Gangs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\Raffle\Raffle.csproj" />
<ProjectReference Include="..\..\EcoRewards\EcoRewards.csproj"/>
<ProjectReference Include="..\..\StatsTracker\StatsTracker.csproj"/>
<ProjectReference Include="..\Commands\Commands.csproj"/>
Expand Down
9 changes: 8 additions & 1 deletion src/GangsAPI/MSG.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ public enum MSG {
COMMAND_GANG_RESTRICTED,
COMMAND_INVITE_DOORPOLICY,
MENU_FORMAT_INVITATION,
MENU_FORMAT_REQUEST, }
MENU_FORMAT_REQUEST,
RAFFLE_BEGIN,
RAFFLE_PRE_ANNOUNCE,
RAFFLE_WINNER
}

public static class LocaleExtensions {
public static string Key(this MSG msg) {
Expand Down Expand Up @@ -230,6 +234,9 @@ public static string Key(this MSG msg) {
MSG.COMMAND_INVITE_DOORPOLICY => "command.invite.doorpolicy",
MSG.MENU_FORMAT_INVITATION => "menu.format.invitation",
MSG.MENU_FORMAT_REQUEST => "menu.format.request",
MSG.RAFFLE_BEGIN => "raffle.begin",
MSG.RAFFLE_PRE_ANNOUNCE => "raffle.preannounce",
MSG.RAFFLE_WINNER => "raffle.winner",
_ => throw new ArgumentOutOfRangeException(nameof(msg), msg, null)
};
}
Expand Down
Loading