From 04add71c6e0fff16e5f0bd368eeedd9ac81246d7 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sun, 13 Feb 2022 20:12:04 +0700 Subject: [PATCH] Anti spam cooldown for player and a 12 hour global cooldown for victim --- Bot/Game.fs | 26 +++++++-- Bot/HackerBattle.fs | 6 +- Bot/RockPaperScissors.fs | 2 +- Bot/Store.fs | 4 +- Bot/Thief.fs | 123 +++++++++++++++++++++++++++------------ Bot/Trainer.fs | 45 +++++++------- Shared/Shared.fs | 5 -- 7 files changed, 137 insertions(+), 74 deletions(-) diff --git a/Bot/Game.fs b/Bot/Game.fs index 4e0b88b..f2f64fd 100644 --- a/Bot/Game.fs +++ b/Bot/Game.fs @@ -41,15 +41,17 @@ module Game = | None -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one" } |> Async.StartAsTask :> Task - let executePlayerWithTargetAction (targetPlayer : DiscordUser) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async) = + let executePlayerActionWithTarget (targetPlayer : DiscordUser) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async) = async { let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true builder.Content <- "Content" do! ctx.Respond InteractionResponseType.DeferredChannelMessageWithSource builder |> Async.AwaitTask - let! playerResult = tryFindPlayer (ctx.GetDiscordMember().Id) - let! targetResult = tryFindPlayer targetPlayer.Id - match playerResult , targetResult with + let! players = + [ tryFindPlayer (ctx.GetDiscordMember().Id) + tryFindPlayer targetPlayer.Id ] + |> Async.Parallel + match players.[0] , players.[1] with | Some player , Some target -> do! dispatch player target | None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one" | _ , None -> @@ -58,6 +60,22 @@ module Game = else do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command" } |> Async.StartAsTask :> Task + let executePlayerActionWithTargetId (targetId : uint64) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async) = + async { + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- "Content" + do! ctx.Respond InteractionResponseType.DeferredChannelMessageWithSource builder |> Async.AwaitTask + let! players = + [ tryFindPlayer (ctx.GetDiscordMember().Id) + tryFindPlayer targetId ] + |> Async.Parallel + match players.[0] , players.[1] with + | Some player , Some target -> do! dispatch player target + | None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one" + | _ , None -> do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command" + } |> Async.StartAsTask :> Task + module Player = let getItems itemType (player : PlayerData) = player.Arsenal |> Array.filter (fun i -> i.Type = itemType) let getHacks (player : PlayerData) = getItems ItemType.Hack player diff --git a/Bot/HackerBattle.fs b/Bot/HackerBattle.fs index e450d7d..ebb2b41 100644 --- a/Bot/HackerBattle.fs +++ b/Bot/HackerBattle.fs @@ -9,8 +9,6 @@ open DSharpPlus.SlashCommands open Degenz open Degenz.Messaging -// TODO: Do not allow any attacks until the user has completed training -// TODO: Introduce second round of weapons, more expensive and with better stats let checkPlayerIsAttackingThemselves defender attacker = match attacker.DiscordId = defender.DiscordId with | true -> Error "You think you're clever? You can't hack yourself, pal." @@ -120,7 +118,7 @@ let failedHack (ctx : IDiscordContext) attacker defender hack = } let attack (target : DiscordUser) (ctx : IDiscordContext) = - Game.executePlayerWithTargetAction target ctx (fun attacker defender -> async { + Game.executePlayerActionWithTarget target ctx (fun attacker defender -> async { do! attacker |> checkAlreadyHackedTarget defender.DiscordId (Player.removeExpiredActions true) @@ -180,7 +178,7 @@ let handleDefense (ctx : IDiscordContext) = |> checkPlayerOwnsWeapon shieldId >>= checkPlayerHasShieldSlotsAvailable shield >>= checkItemHasCooldown shieldId - |> handleResultWithResponseFromEvent ctx (fun p -> async { + |> handleResultWithResponse ctx (fun p -> async { let embed = Embeds.responseCreatedShield (Armory.getItem shieldId) do! ctx.FollowUp embed |> Async.AwaitTask let defense = { ActionId = shieldId ; Type = Defense ; Timestamp = DateTime.UtcNow } diff --git a/Bot/RockPaperScissors.fs b/Bot/RockPaperScissors.fs index 2203e9b..5000587 100644 --- a/Bot/RockPaperScissors.fs +++ b/Bot/RockPaperScissors.fs @@ -28,7 +28,7 @@ let player1Won p1m p2m = | _ , _ -> Draw let playRPS target ctx = - Game.executePlayerWithTargetAction target ctx (fun attacker defender -> async { + Game.executePlayerActionWithTarget target ctx (fun attacker defender -> async { return () }) diff --git a/Bot/Store.fs b/Bot/Store.fs index 285f10c..951a0db 100644 --- a/Bot/Store.fs +++ b/Bot/Store.fs @@ -49,7 +49,7 @@ let handleBuyItem (ctx : IDiscordContext) itemId = do! player |> checkHasSufficientFunds item >>= checkAlreadyOwnsItem item - |> handleResultWithResponseFromEvent ctx (fun player -> async { + |> handleResultWithResponse ctx (fun player -> async { let newBalance = player.Bank - item.Cost let p = { player with Bank = newBalance ; Arsenal = Array.append [| item |] player.Arsenal } do! DbService.updatePlayer p @@ -62,7 +62,7 @@ let handleSell (ctx : IDiscordContext) itemId = let item = Armory.getItem itemId do! player |> checkSoldItemAlready item - |> handleResultWithResponseFromEvent ctx (fun player -> async { + |> handleResultWithResponse ctx (fun player -> async { let updatedPlayer = { player with Bank = player.Bank + item.Cost diff --git a/Bot/Thief.fs b/Bot/Thief.fs index 2382013..3efd4c5 100644 --- a/Bot/Thief.fs +++ b/Bot/Thief.fs @@ -7,6 +7,14 @@ open DSharpPlus.Entities open DSharpPlus.SlashCommands open Degenz.Messaging +[] +let StealActionId = 12 +[] +let VictimDefenseActionId = 12 + +let ThiefCooldown = TimeSpan.FromMinutes(1) +let VictimRecovery = TimeSpan.FromHours(12) + type StealResult = | Success | WentToPrison @@ -78,49 +86,92 @@ let getResultEmbed targetName result = .WithImageUrl(img) .WithTitle($"Robbery Results") -let steal target (ctx : IDiscordContext) = - Game.executePlayerWithTargetAction target ctx (fun attacker defender -> async { - let ``base`` = 0.5 - let winPercentage = double (attacker.Stats.Strength - defender.Stats.Strength) * 0.45 + ``base`` - let prize = payout (float defender.Bank) winPercentage - let embed = getStealEmbed winPercentage prize defender +let checkVictimStealingCooldown defender attacker = + defender + |> Player.getDefenses + |> Array.tryFind (fun act -> act.ActionId = VictimDefenseActionId) + |> function + | Some act -> + let cooldown = VictimRecovery - (DateTime.UtcNow - act.Timestamp) + let hours = if cooldown.Hours = 0 then "hour" else $"{cooldown.Hours} hours" + Error $"{defender.Name} was robbed recently so they won't be going out for at least another {hours}." + | None -> Ok attacker - do! ctx.FollowUp(embed) |> Async.AwaitTask +// TODO: Look for ways to generalize checking for action cooldowns +let checkThiefCooldown attacker = + attacker + |> Player.getAttacks + |> Array.tryFind (fun act -> act.ActionId = StealActionId) + |> function + | Some act -> + if ThiefCooldown > (DateTime.UtcNow - act.Timestamp) then + let cooldown = ThiefCooldown - (DateTime.UtcNow - act.Timestamp) + let minutes = if cooldown.Minutes = 0 then "minute" else $"{cooldown.Minutes} minutes" + Error $"Whoa there you clepto, wait at least another {minutes} before you try stealing again." + else + Ok attacker + | None -> Ok attacker + +let steal target (ctx : IDiscordContext) = + Game.executePlayerActionWithTarget target ctx (fun attacker defender -> async { + do! attacker + |> checkVictimStealingCooldown defender + >>= checkThiefCooldown + |> handleResultWithResponse ctx (fun player -> async { + let ``base`` = 0.5 + let winPercentage = double (attacker.Stats.Strength - defender.Stats.Strength) * 0.45 + ``base`` + let prize = payout (float defender.Bank) winPercentage + let embed = getStealEmbed winPercentage prize defender + + do! ctx.FollowUp(embed) |> Async.AwaitTask + }) }) let handleSteal (ctx : IDiscordContext) = let split = ctx.GetInteractionId().Split("-") let answer = split.[1] + + let handleYes player = async { + let targetId = uint64 split.[2] + let targetName = split.[3] + let chance = double split.[4] + let prize = int split.[5] * 1 + + let rand = Random(Guid.NewGuid().GetHashCode()) + + let result = chance >= rand.NextDouble() , rand.Next(0,3) = 0 + match result with + | true , _ -> + let xp = 15 + let embed = getResultEmbed targetName Success + embed.AddField("$GBT Stolen", string prize) |> ignore + embed.AddField("XP Gained", $"{xp}+") |> ignore + do! Messaging.sendFollowUpEmbed ctx (embed.Build()) + match! DbService.tryFindPlayer targetId with + | Some t -> + let action = { ActionId = VictimDefenseActionId ; Type = Defense ; Timestamp = DateTime.UtcNow } + do! DbService.updatePlayer { t with Bank = max (t.Bank - prize) 0 ; Actions = Array.append [| action |] t.Actions } + | None -> () + let dp = { DiscordPlayer.Id = targetId ; DiscordPlayer.Name = targetName } + let action = { ActionId = StealActionId ; Type = Attack { AttackResult.Result = true ; AttackResult.Target = dp } ; Timestamp = DateTime.UtcNow } + do! DbService.updatePlayer { player with Bank = player.Bank + prize ; XP = player.XP + xp ; Actions = Array.append [| action |] player.Actions } + | false , false -> + let embed = getResultEmbed targetName TargetRanAway + do! Messaging.sendFollowUpEmbed ctx (embed.Build()) + | false , true -> + let embed = getResultEmbed targetName WentToPrison + do! Messaging.sendFollowUpEmbed ctx (embed.Build()) + do! Async.Sleep 5000 + let role = ctx.GetGuild().GetRole(GuildEnvironment.rolePrisoner) + do! ctx.GetDiscordMember().GrantRoleAsync(role) |> Async.AwaitTask + } if answer = "yes" then - Game.executePlayerAction ctx (fun player -> async { - let targetId = uint64 split.[2] - let targetName = split.[3] - let chance = double split.[4] - let prize = int split.[5] * 1 - - let rand = Random(Guid.NewGuid().GetHashCode()) - - let result = chance >= rand.NextDouble() , rand.Next(0,3) = 0 - match result with - | true , _ -> - let xp = 15 - let embed = getResultEmbed targetName Success - embed.AddField("$GBT Stolen", string prize) |> ignore - embed.AddField("XP Gained", $"{xp}+") |> ignore - do! Messaging.sendFollowUpEmbed ctx (embed.Build()) - match! DbService.tryFindPlayer targetId with - | Some t -> do! DbService.updatePlayer { t with Bank = max (t.Bank - prize) 0 } - | None -> () - do! DbService.updatePlayer { player with Bank = player.Bank + prize ; XP = player.XP + xp } - | false , false -> - let embed = getResultEmbed targetName TargetRanAway - do! Messaging.sendFollowUpEmbed ctx (embed.Build()) - | false , true -> - let embed = getResultEmbed targetName WentToPrison - do! Messaging.sendFollowUpEmbed ctx (embed.Build()) - do! Async.Sleep 5000 - let role = ctx.GetGuild().GetRole(GuildEnvironment.rolePrisoner) - do! ctx.GetDiscordMember().GrantRoleAsync(role) |> Async.AwaitTask + let targetId = uint64 split.[2] + Game.executePlayerActionWithTargetId targetId ctx (fun attacker defender -> async { + do! attacker + |> checkVictimStealingCooldown defender + >>= checkThiefCooldown + |> handleResultWithResponse ctx handleYes }) else async { diff --git a/Bot/Trainer.fs b/Bot/Trainer.fs index d982452..0dccd0c 100644 --- a/Bot/Trainer.fs +++ b/Bot/Trainer.fs @@ -169,28 +169,29 @@ let handleAttack (ctx : IDiscordContext) = sb.AppendLine("To finish your training and collect the loot, type the `/arsenal` command **NOW**") |> ignore do! Async.Sleep 1000 let updatedPlayer = { - player with Bank = player.Bank + hackMoney + shieldMoney - Actions = [ - { Action.Timestamp = System.DateTime.UtcNow - Action.Type = - Attack { - Result = true - Target = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" } - } - ActionId = defaultHack.Id - } - if not hasShields && Array.exists (fun act -> act.ActionId = freeShield.Id) player.Actions |> not then { - Action.Timestamp = System.DateTime.UtcNow - Action.Type = Defense - Action.ActionId = freeShield.Id - } - ] |> Seq.toArray - |> Array.append player.Actions - Arsenal = [ - if not hasHacks then freeHack - if not hasShields then freeShield - ] |> Seq.toArray - |> Array.append player.Arsenal + player with + Bank = player.Bank + hackMoney + shieldMoney + Actions = [ + { Action.Timestamp = System.DateTime.UtcNow + Action.Type = + Attack { + Result = true + Target = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" } + } + ActionId = defaultHack.Id + } + if not hasShields && Array.exists (fun act -> act.ActionId = freeShield.Id) player.Actions |> not then { + Action.Timestamp = System.DateTime.UtcNow + Action.Type = Defense + Action.ActionId = freeShield.Id + } + ] |> Seq.toArray + |> Array.append player.Actions + Arsenal = [ + if not hasHacks then freeHack + if not hasShields then freeShield + ] |> Seq.toArray + |> Array.append player.Arsenal } do! DbService.updatePlayer updatedPlayer do! sendFollowUpMessage ctx (sb.ToString()) diff --git a/Shared/Shared.fs b/Shared/Shared.fs index c3c3c5b..c917f81 100644 --- a/Shared/Shared.fs +++ b/Shared/Shared.fs @@ -228,8 +228,3 @@ module Messaging = | Ok p -> fn p | Error e -> async { do! sendFollowUpMessage ctx e } - let handleResultWithResponseFromEvent event fn (player : Result) = - match player with - | Ok p -> fn p - | Error e -> async { do! sendFollowUpMessage event e } -