diff --git a/DbService/DbService.fs b/DbService/DbService.fs index bd06df1..0f8fcc0 100644 --- a/DbService/DbService.fs +++ b/DbService/DbService.fs @@ -1,6 +1,6 @@ module DegenzGame.DbService -open DegenzGame.Shared +open System open DegenzGame.Shared open MongoDB.Bson open MongoDB.Driver @@ -16,7 +16,7 @@ let players = db.GetCollection("players") let tryFindPlayer (id : uint64) : Async = async { - let filter = Builders.Filter.Eq((fun p -> p.Player.DiscordId), id) + let filter = Builders.Filter.Eq((fun e -> e.Player.DiscordId), id) let! player = players.FindAsync(filter) |> Async.AwaitTask return match player.ToEnumerable() |> Seq.toList with | [] -> None @@ -33,7 +33,27 @@ let insertNewPlayer (player : Player) = let removePlayer (memberId : uint64) = async { // TODO: Check the result of this delete operation - return! players.DeleteOneAsync (fun p -> p.Player.DiscordId = memberId) + return! players.DeleteOneAsync (fun e -> e.Player.DiscordId = memberId) |> Async.AwaitTask |> Async.Ignore } + +let updateAttacks (playerId : uint64) (attacks : Attack array) = + async { + let filter = Builders.Filter.Eq((fun e -> e.Player.DiscordId), playerId) + let update = Builders.Update.Set((fun e -> e.Player.Attacks), attacks) + return! players.UpdateOneAsync(filter, update) |> Async.AwaitTask |> Async.Ignore + } + +let updatePlayer player = + async { + let filter = Builders.Filter.Eq((fun e -> e.Player.DiscordId), player.DiscordId) + let update = Builders.Update.Set((fun e -> e.Player), player) + return! players.UpdateOneAsync(filter, update) |> Async.AwaitTask |> Async.Ignore + } + +let getTopPlayers number = + async { + let! entries = players.Find(fun _ -> true).SortBy(fun e -> e.Player.Bank).Limit(Nullable(number)).ToListAsync() |> Async.AwaitTask + return entries |> Seq.map (fun e -> e.Player) + } diff --git a/DegenzGame.sln b/DegenzGame.sln index d08053a..0fd26a1 100644 --- a/DegenzGame.sln +++ b/DegenzGame.sln @@ -9,7 +9,7 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Shared", "Shared\Shared.fsp EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Store", "Store\Store.fsproj", "{CD88B0A6-DE42-4087-9B33-48FF84201633}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "PlayerRegistration", "PlayerRegistration\PlayerRegistration.fsproj", "{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}" +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "PlayerInteractions", "PlayerInteractions\PlayerInteractions.fsproj", "{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}" EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "DbService", "DbService\DbService.fsproj", "{B1D3E1CC-451C-42D4-B054-D64E75E1A3B9}" EndProject diff --git a/HackerBattle/Commands.fs b/HackerBattle/Commands.fs index 26f9cbb..b85bfee 100644 --- a/HackerBattle/Commands.fs +++ b/HackerBattle/Commands.fs @@ -7,9 +7,6 @@ open DSharpPlus.Entities open DSharpPlus.EventArgs open DSharpPlus.SlashCommands open DegenzGame.Shared -open DegenzGame.DbService -open DegenzGame.Functions -open MongoDB.Driver [] // Degenz Server @@ -17,18 +14,17 @@ open MongoDB.Driver // My server let battleChannel = 927449884204867664uL - let attack (ctx : InteractionContext) (target : DiscordUser) = async { // TODO: We need to check if the player has any active embed hacks going, if not they can cheat - let! attacker = tryFindPlayer ctx.Member.Id - let! defender = tryFindPlayer target.Id + let! attacker = DbService.tryFindPlayer ctx.Member.Id + let! defender = DbService.tryFindPlayer target.Id match attacker , defender with | Some attacker , Some defender -> - let updatedAttacks = removeExpiredActions (TimeSpan.FromMinutes(5)) (fun (atk : Attack) -> atk.Timestamp) attacker.Attacks - let filter = Builders.Filter.Eq((fun p -> p.DiscordId), attacker.DiscordId) - let update = Builders.Update.Set((fun p -> p.Attacks), updatedAttacks) - let! _ = players.UpdateOneAsync(filter, update) |> Async.AwaitTask + let updatedAttacks = + attacker.Attacks + |> removeExpiredActions (TimeSpan.FromMinutes(5)) (fun (atk : Attack) -> atk.Timestamp) + do! DbService.updateAttacks attacker.DiscordId updatedAttacks if updatedAttacks.Length < 2 then let builder = DiscordInteractionResponseBuilder() builder.AddEmbed (constructEmbed "Pick the hack you wish to use.") |> ignore @@ -62,13 +58,11 @@ let attack (ctx : InteractionContext) (target : DiscordUser) = let defend (ctx : InteractionContext) = async { - let! player = tryFindPlayer ctx.Member.Id + let! player = DbService.tryFindPlayer ctx.Member.Id match player with | Some player -> let updatedDefenses = removeExpiredActions (TimeSpan.FromMinutes(60)) (fun (pro : Defense) -> pro.Timestamp) player.Defenses - let filter = Builders.Filter.Eq((fun p -> p.DiscordId), player.DiscordId) - let update = Builders.Update.Set((fun p -> p.Defenses), updatedDefenses) - let! _ = players.UpdateOneAsync(filter, update) |> Async.AwaitTask + do! DbService.updatePlayer <| { player with Defenses = updatedDefenses } if updatedDefenses.Length < 2 then let builder = DiscordInteractionResponseBuilder() builder.AddEmbed (constructEmbed "Pick a defense to mount for a duration of time") |> ignore @@ -96,36 +90,6 @@ let defend (ctx : InteractionContext) = } |> Async.StartAsTask :> Task -let status (ctx : InteractionContext) = - async { - let! player = tryFindPlayer ctx.Member.Id - match player with - | Some p -> - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- Functions.statusFormat p - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - | None -> do! notYetAHackerMsg ctx - } |> Async.StartAsTask - :> Task - -let leaderboard (ctx : InteractionContext) = - async { - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - - let! leaders = players.Find(fun _ -> true).SortBy(fun p -> p.Bank).Limit(10).ToListAsync() |> Async.AwaitTask - let content = - leaders.ToArray() - |> Array.mapi (fun i p -> $"{i + 1}. {p.Bank} {p.Name}") - |> String.concat "\n" - builder.Content <- if not <| String.IsNullOrEmpty content then content else "There are no active hackers" - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - } |> Async.StartAsTask - :> Task - let handleAttack (event : ComponentInteractionCreateEventArgs) = let updatePlayer amount attack p = { p with Attacks = Array.append [| attack |] p.Attacks ; Bank = MathF.Max(p.Bank + amount, 0f) } @@ -133,8 +97,8 @@ let handleAttack (event : ComponentInteractionCreateEventArgs) = let split = event.Id.Split("-") let ( resultHack , weapon ) = Weapon.TryParse(split.[1]) let ( resultId , targetId ) = UInt64.TryParse split.[2] - let! resultPlayer = tryFindPlayer event.User.Id - let! resultTarget = tryFindPlayer targetId + let! resultPlayer = DbService.tryFindPlayer event.User.Id + let! resultTarget = DbService.tryFindPlayer targetId match resultPlayer , resultTarget , resultHack , resultId with | Some player , Some target , true , true -> let wasSuccessfulHack = @@ -147,8 +111,7 @@ let handleAttack (event : ComponentInteractionCreateEventArgs) = | false -> let prize = 0.1726f let attack = { HackType = enum(weapon) ; Timestamp = DateTime.UtcNow ; Target = { Id = targetId ; Name = split.[3] } } - let filter = Builders.Filter.Eq((fun p -> p.DiscordId), player.DiscordId) - let! _ = players.ReplaceOneAsync(filter, updatePlayer prize attack player) |> Async.AwaitTask + do! DbService.updatePlayer <| updatePlayer prize attack player let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true @@ -171,8 +134,7 @@ let handleAttack (event : ComponentInteractionCreateEventArgs) = |> Async.AwaitTask let attack = { HackType = enum(weapon) ; Timestamp = DateTime.UtcNow ; Target = { Id = targetId ; Name = split.[3] } } - let filter = Builders.Filter.Eq((fun p -> p.DiscordId), player.DiscordId) - let! _ = players.ReplaceOneAsync(filter, updatePlayer loss attack player) |> Async.AwaitTask + do! DbService.updatePlayer <| updatePlayer loss attack player let builder = DiscordMessageBuilder() builder.WithContent($"{event.User.Username} failed to hack <@{targetId}>!") |> ignore @@ -192,7 +154,7 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) = async { let split = event.Id.Split("-") let ( shieldResult , shield ) = Shield.TryParse(split.[1]) - let! playerResult = tryFindPlayer event.User.Id + let! playerResult = DbService.tryFindPlayer event.User.Id match playerResult , shieldResult with | Some player , true -> let builder = DiscordInteractionResponseBuilder() @@ -202,9 +164,7 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) = |> Async.AwaitTask let defense = { DefenseType = shield ; Timestamp = DateTime.UtcNow } - let filter = Builders.Filter.Eq((fun p -> p.DiscordId), player.DiscordId) - let update = Builders.Update.Set((fun p -> p.Defenses), Array.append [| defense |] player.Defenses ) - let! _ = players.UpdateOneAsync(filter, update) |> Async.AwaitTask + do! DbService.updatePlayer <| { player with Defenses = Array.append [| defense |] player.Defenses } let builder = DiscordMessageBuilder() builder.WithContent($"{event.User.Username} has protected their system!") |> ignore @@ -220,7 +180,7 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) = |> Async.AwaitTask } -let handleButtonEvent (client : DiscordClient) (event : ComponentInteractionCreateEventArgs) = +let handleButtonEvent (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) = async { return! match event.Id with | id when id.StartsWith("Attack") -> handleAttack event diff --git a/HackerBattle/HackerBattle.fsproj b/HackerBattle/HackerBattle.fsproj index c567858..3390018 100644 --- a/HackerBattle/HackerBattle.fsproj +++ b/HackerBattle/HackerBattle.fsproj @@ -14,7 +14,6 @@ - diff --git a/HackerBattle/Program.fs b/HackerBattle/Program.fs index 50809a7..a994180 100644 --- a/HackerBattle/Program.fs +++ b/HackerBattle/Program.fs @@ -18,12 +18,6 @@ type HackerGame() = [] member this.DefendCommand (ctx : InteractionContext) = Commands.defend ctx - [] - member this.Status (ctx : InteractionContext) = Commands.status ctx - - [] - member this.Leaderboard (ctx : InteractionContext) = Commands.leaderboard ctx - let config = DiscordConfiguration() config.Token <- "OTIyNDIyMDIyMTI1MDEwOTU1.YcBOcw.JxfW1CSIwEO7j6RbRFCnPZ-HoTk" config.TokenType <- TokenType.Bot diff --git a/PlayerRegistration/.dockerignore b/PlayerInteractions/.dockerignore similarity index 100% rename from PlayerRegistration/.dockerignore rename to PlayerInteractions/.dockerignore diff --git a/PlayerRegistration/Dockerfile b/PlayerInteractions/Dockerfile similarity index 100% rename from PlayerRegistration/Dockerfile rename to PlayerInteractions/Dockerfile diff --git a/PlayerRegistration/PlayerRegistration.fsproj b/PlayerInteractions/PlayerInteractions.fsproj similarity index 91% rename from PlayerRegistration/PlayerRegistration.fsproj rename to PlayerInteractions/PlayerInteractions.fsproj index 565f091..9db4136 100644 --- a/PlayerRegistration/PlayerRegistration.fsproj +++ b/PlayerInteractions/PlayerInteractions.fsproj @@ -4,6 +4,7 @@ Exe net6.0 Linux + PlayerRegistration diff --git a/PlayerRegistration/Program.fs b/PlayerInteractions/Program.fs similarity index 65% rename from PlayerRegistration/Program.fs rename to PlayerInteractions/Program.fs index b9e66d2..3336644 100644 --- a/PlayerRegistration/Program.fs +++ b/PlayerInteractions/Program.fs @@ -1,8 +1,9 @@ - +open System open System.Threading.Tasks -open DegenzGame.DbService +open DSharpPlus.Entities open DSharpPlus open DSharpPlus.SlashCommands +open DegenzGame open DegenzGame.Shared module Commands = @@ -30,14 +31,14 @@ module Commands = let addHackerRole (ctx : InteractionContext) = async { - let! player = tryFindPlayer ctx.Member.Id + let! player = DbService.tryFindPlayer ctx.Member.Id let! newPlayer = match player with | Some _ -> async.Return false | None -> async { do! newPlayer ctx.Member.Username ctx.Member.Id - |> insertNewPlayer + |> DbService.insertNewPlayer for role in ctx.Guild.Roles do if role.Value.Name = "Hacker" then @@ -64,13 +65,44 @@ module Commands = do! ctx.Member.RevokeRoleAsync(role) |> Async.AwaitTask - do! removePlayer ctx.Member.Id + do! DbService.removePlayer ctx.Member.Id do! ctx.CreateResponseAsync("You are now lame", true) |> Async.AwaitTask } |> Async.StartAsTask :> Task + let leaderboard (ctx : InteractionContext) = + async { + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + + let! leaders = DbService.getTopPlayers 10 + let content = + leaders + |> Seq.toArray + |> Array.mapi (fun i p -> $"{i + 1}. {p.Bank} {p.Name}") + |> String.concat "\n" + builder.Content <- if not <| String.IsNullOrEmpty content then content else "There are no active hackers" + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + } |> Async.StartAsTask + :> Task + + let status (ctx : InteractionContext) = + async { + let! player = DbService.tryFindPlayer ctx.Member.Id + match player with + | Some p -> + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- statusFormat p + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + | None -> do! notYetAHackerMsg ctx + } |> Async.StartAsTask + :> Task + type EmptyGlobalCommandToAvoidFamousDuplicateSlashCommandsBug() = inherit ApplicationCommandModule () @@ -83,6 +115,12 @@ type PlayerRegistration() = [] member _.RemoveHackerRole (ctx : InteractionContext) = Commands.removeHackerRole ctx + [] + member this.Status (ctx : InteractionContext) = Commands.status ctx + + [] + member this.Leaderboard (ctx : InteractionContext) = Commands.leaderboard ctx + let config = DiscordConfiguration() config.Token <- "OTIyNDIyMDIyMTI1MDEwOTU1.YcBOcw.JxfW1CSIwEO7j6RbRFCnPZ-HoTk" diff --git a/PlayerRegistration/paket.references b/PlayerInteractions/paket.references similarity index 100% rename from PlayerRegistration/paket.references rename to PlayerInteractions/paket.references diff --git a/HackerBattle/Functions.fs b/Shared/Shared.fs similarity index 50% rename from HackerBattle/Functions.fs rename to Shared/Shared.fs index d153564..974a772 100644 --- a/HackerBattle/Functions.fs +++ b/Shared/Shared.fs @@ -1,9 +1,80 @@ -module DegenzGame.Functions +module DegenzGame.Shared open System open DSharpPlus open DSharpPlus.Entities -open DegenzGame.Shared +open DSharpPlus.SlashCommands + +type ActionClass = + | Network + | Exploit + | Penetration + +type Weapon = + | Virus = 0 + | Ransom = 1 + | Worm = 2 + | DDos = 3 + | Crack = 4 + | Injection = 5 + +type Shield = + | Firewall = 0 + | PortScan = 1 + | Encryption = 2 + | Hardening = 4 + | Sanitation = 5 + | Cypher = 3 + +let getClass = function + | 0 | 1 -> Network + | 2 | 3 -> Exploit + | 4 | _ -> Penetration + +type HackResult = + | Strong + | Weak + +[] +type DiscordPlayer = { + Id : uint64 + Name : string +} + +[] +type Attack = { + HackType : Weapon + Target : DiscordPlayer + Timestamp : DateTime +} + +[] +type Defense = { + DefenseType : Shield + Timestamp : DateTime +} + +[] +type Player = { + DiscordId : uint64 + Name : string + Weapons : Weapon array + Shields : Shield array + Attacks : Attack array + Defenses : Defense array + Bank : single +} + +let createSimpleResponseAsync msg (ctx : InteractionContext) = + async { + let builder = DiscordInteractionResponseBuilder() + builder.Content <- msg + builder.AsEphemeral true |> ignore + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + } + +let notYetAHackerMsg = createSimpleResponseAsync "You are not currently a hacker, first use the /redpill command to become one" let hackDescription = "" diff --git a/Shared/Shared.fsproj b/Shared/Shared.fsproj index a86cb3a..303067d 100644 --- a/Shared/Shared.fsproj +++ b/Shared/Shared.fsproj @@ -5,7 +5,7 @@ true - + diff --git a/Shared/Types.fs b/Shared/Types.fs deleted file mode 100644 index 94ba8d9..0000000 --- a/Shared/Types.fs +++ /dev/null @@ -1,77 +0,0 @@ -module DegenzGame.Shared - -open System -open DSharpPlus -open DSharpPlus.Entities -open DSharpPlus.SlashCommands - -type ActionClass = - | Network - | Exploit - | Penetration - -type Weapon = - | Virus = 0 - | Ransom = 1 - | Worm = 2 - | DDos = 3 - | Crack = 4 - | Injection = 5 - -type Shield = - | Firewall = 0 - | PortScan = 1 - | Encryption = 2 - | Hardening = 4 - | Sanitation = 5 - | Cypher = 3 - -let getClass = function - | 0 | 1 -> Network - | 2 | 3 -> Exploit - | 4 | _ -> Penetration - -type HackResult = - | Strong - | Weak - -[] -type DiscordPlayer = { - Id : uint64 - Name : string -} - -[] -type Attack = { - HackType : Weapon - Target : DiscordPlayer - Timestamp : DateTime -} - -[] -type Defense = { - DefenseType : Shield - Timestamp : DateTime -} - -[] -type Player = { - DiscordId : uint64 - Name : string - Weapons : Weapon array - Shields : Shield array - Attacks : Attack array - Defenses : Defense array - Bank : single -} - -let createSimpleResponseAsync msg (ctx : InteractionContext) = - async { - let builder = DiscordInteractionResponseBuilder() - builder.Content <- msg - builder.AsEphemeral true |> ignore - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - } - -let notYetAHackerMsg = createSimpleResponseAsync "You are not currently a hacker, first use the /redpill command to become one" \ No newline at end of file