module Degenz.Thief open System open System.Threading.Tasks open DSharpPlus 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 | VictimRanAway let getRandomStealBtnLabels () = let rand = Random(Guid.NewGuid().GetHashCode()) let affirmative = [| "LFG" ; "YOLO" ; "IDGAF" |] let negative = [| "NOPE" ; "IM OUT" ; "BAIL" |] ( affirmative.[rand.Next(0, 3)] , negative.[rand.Next(0, 3)] ) let chanceOfSuccessMsg = function | amt when amt < 0.20 -> "Looking pretty bad" | amt when amt < 0.50 -> "I mean, maybe" | amt when amt < 0.80 -> "I think you got this" | _ -> "Totally worth it" let targetEvaluationMsg = function | amt when amt < 0.20 -> "but man, they look swole" | amt when amt < 0.50 -> "but they look a little confident" | amt when amt < 0.80 -> "and they're looking a little nervous" | _ -> "and they look weak af man" let payout defenderBank chance = let rand = Random(Guid.NewGuid().GetHashCode()) let baseAmount = defenderBank * 0.1 * (1.0 - chance) let randomBonus = baseAmount * rand.NextDouble() * chance |> ceil let randomAmount = baseAmount + randomBonus |> int max 1 randomAmount let getStealEmbed chance prize (target : PlayerData) = let buttons = let yes , no = getRandomStealBtnLabels () [ DiscordButtonComponent(ButtonStyle.Success, $"Steal-yes-{target.DiscordId}-{target.Name}-{chance}-{prize}", yes) DiscordButtonComponent(ButtonStyle.Danger, $"Steal-no", no) ] |> Seq.cast let embed = DiscordEmbedBuilder() .AddField("Chance of Success", $"{chanceOfSuccessMsg chance}", true) .AddField("Payout", $"{prize}", true) .WithDescription($"{target.Name} is coming towards you in a dark alley, {targetEvaluationMsg chance}") .WithImageUrl("https://cdnb.artstation.com/p/assets/images/images/017/553/457/large/maarten-hof-backalley-mainshot.jpg") .WithTitle($"Steal Money") DiscordFollowupMessageBuilder() .AddEmbed(embed) .AddComponents(buttons) .AsEphemeral(true) let getResultEmbed targetName result = let resultMsg , msg , img = match result with | Success -> "You successfully robbed the poor bastard" , "Your mean ugly face and athletic physique intimidated your poor victim into giving you their money" , "https://f8n-production-collection-assets.imgix.net/0x3B3ee1931Dc30C1957379FAc9aba94D1C48a5405/127502/QmPLPg1CLovKzS7mP8QkrMoHws1D4VZTzpfbZBALLwKZ5b/nft.png" | WentToPrison -> "Looks like you went to prison" , $"{targetName} knows Karate and elbowed you in the nose. While unconscious, they called the cops. You're on your way to prison now... " , "https://thumbs.dreamstime.com/b/vector-pixel-art-prisoner-isolated-cartoon-vector-pixel-art-prisoner-129807237.jpg" | VictimRanAway -> "You tried to snatch their money and they ran away" , $"{targetName} got nervous seeing a shadowy figure and ran in the opposite direction" , "https://i.imgur.com/NLHMvVK.jpg" DiscordEmbedBuilder() .AddField("Result" , resultMsg) .WithDescription(msg) .WithImageUrl(img) .WithTitle($"Robbery Results") 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 // 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 let dp = { DiscordPlayer.Id = targetId ; DiscordPlayer.Name = targetName } let stealAction = { ActionId = StealActionId ; Type = Attack { AttackResult.Result = false ; AttackResult.Target = dp } ; Timestamp = DateTime.UtcNow } // TODO: Send event to the hall of privacy 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 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 VictimRanAway do! DbService.updatePlayer { player with Actions = Array.append [| stealAction |] player.Actions } do! Messaging.sendFollowUpEmbed ctx (embed.Build()) | false , true -> let embed = getResultEmbed targetName WentToPrison do! DbService.updatePlayer { player with Actions = Array.append [| stealAction |] player.Actions } do! Messaging.sendFollowUpEmbed ctx (embed.Build()) do! Async.Sleep 2000 let role = ctx.GetGuild().GetRole(GuildEnvironment.rolePrisoner) do! ctx.GetDiscordMember().GrantRoleAsync(role) |> Async.AwaitTask } if answer = "yes" then let targetId = uint64 split.[2] Game.executePlayerActionWithTargetId targetId ctx (fun attacker defender -> async { do! attacker |> checkVictimStealingCooldown defender >>= checkThiefCooldown |> handleResultWithResponse ctx handleYes }) else async { let builder = DiscordInteractionResponseBuilder() builder.Content <- "I thought better of it" do! ctx.Respond InteractionResponseType.UpdateMessage builder |> Async.AwaitTask } |> Async.StartAsTask :> Task type StealGame() = inherit ApplicationCommandModule () let enforceChannel (ctx : IDiscordContext) (storeFn : IDiscordContext -> Task) = match ctx.GetChannel().Id with | id when id = GuildEnvironment.channelThievery -> storeFn ctx | _ -> task { let msg = $"You must go to <#{GuildEnvironment.channelThievery}> channel if you want to mug some unsuspecting doofs" do! Messaging.sendSimpleResponse ctx msg } [] member this.Steal (ctx : InteractionContext, [] target : DiscordUser) = enforceChannel (DiscordInteractionContext ctx) (steal target) // steal target (DiscordInteractionContext ctx)