module Degenz.Thief open System open System.Threading.Tasks open DSharpPlus open DSharpPlus.Entities open DSharpPlus.EventArgs open DSharpPlus.SlashCommands open Degenz.Messaging open Degenz.PlayerInteractions let ThiefCooldown = TimeSpan.FromMinutes(1) let VictimRecovery = TimeSpan.FromHours(1) type StealResult = | Success | WentToPrison //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 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 getResultEmbed chance prize (bank : int) thief (victim : DiscordPlayer) result = let embed = DiscordEmbedBuilder() .AddField("Thief", $"<@{thief.DiscordId}>") .AddField("Victim", $"<@{victim.Id}>") .AddField("Chance of Success" , $"{chance * 100.0 |> int}%%", true) let msg , img , color = match result with | Success -> embed.AddField("Loot" , $"{prize}", true) .AddField("New GoodBoyTokenⒸ Balance", $"`{bank}` => 💰 `{bank + prize}` (+{prize}) ") |> ignore ($"\n\n You **STOLE** `{prize} $GBT` 🎉\n\n" + "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" , DiscordColor.Green | WentToPrison -> embed.AddField("Result" , $"Imprisoned", true) |> ignore $"You're going to **PRISON**...\n\n{victim.Name} knows Karate and elbowed you in the nose. While unconscious, they called the cops." , "https://thumbs.dreamstime.com/b/vector-pixel-art-prisoner-isolated-cartoon-vector-pixel-art-prisoner-129807237.jpg" , DiscordColor.Red embed.WithTitle($"Steal Results") .WithColor(color) .WithDescription(msg) // .WithImageUrl(img) // TODO: See if we are going to keep this let checkVictimStealingCooldown defender attacker = defender |> Player.removeExpiredActions |> fun p -> p.Events |> List.tryFind (fun e -> match e.Type with Stealing _ -> true | _ -> false) |> 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 let checkTargetHasFunds target player = match target.Bank <= 0 with | true -> Error $"Looks like the poor bastard has no $GBT... pick a different victim." | false -> Ok player let checkPrizeRequestZero request player = match request <= 0 with | true -> Error $"You don't want to steal anything? Were you dropped on your head as a kid? Pick a different amount" | false -> Ok player let checkThiefCooldown attacker = attacker |> Player.removeExpiredActions |> fun p -> p.Events |> List.tryFind (fun pe -> match pe.Type with Stealing(instigator, _) -> instigator | _ -> false) |> function | Some act -> 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." | None -> Ok attacker let checkPlayerIsAttackingThemselves defender attacker = match attacker.DiscordId = defender.DiscordId with | true -> Error "You think you're clever? You can't steal your own money, you dingleberry." | false -> Ok attacker let calculateWinPercentage amountRequested bank attackerStrength defenderStrength = let powerPercentage = float (attackerStrength - defenderStrength) * 0.005 + 0.1 // let cappedAmount = float bank * 0.5 let cappedAmount = float bank let cappedRequest , wasCapped = if amountRequested > cappedAmount then ( cappedAmount , true ) else ( amountRequested , false ) let wagerPercentage = 1.0 - (cappedRequest / cappedAmount) // Max chance of success is 90.0% ( int cappedRequest , max 0.0 (wagerPercentage * 0.7 + powerPercentage * 1.3 ) / 2.0 , wasCapped ) //calculateWinPercentage 50 200 100 85 let steal target amount (ctx : IDiscordContext) = executePlayerActionWithTarget target ctx (fun thief victim -> async { do! thief |> checkPlayerIsAttackingThemselves victim // |> checkVictimStealingCooldown victim >>= checkTargetHasFunds victim >>= checkPrizeRequestZero amount |> handleResultWithResponse ctx (fun _ -> async { let cappedPrize , winPercentage , wasCapped = calculateWinPercentage amount (int victim.Bank) thief.Stats.Strength.Amount victim.Stats.Strength.Amount let chance = int (winPercentage * 100.0) let buttons = let btnId = $"Steal-yes-{victim.DiscordId}-{victim.Name}-{cappedPrize}" [ DiscordButtonComponent(ButtonStyle.Success, btnId, $"Steal {cappedPrize} $GBT") ] |> Seq.cast let cappedMsg = if wasCapped then $"They only have {cappedPrize} $GBT though... " else "" let strengthMsg = match thief.Stats.Strength.Amount - victim.Stats.Strength.Amount with | diff when diff < -50 -> "much stronger" | diff when diff < 0 -> "stronger" | diff when diff < 50 -> "weaker" | _ -> "much weaker" let msg = DiscordFollowupMessageBuilder() .WithContent($"Want to steal from <@{victim.DiscordId}>? They look {strengthMsg} than us so we have a **{chance}%% of success**. {cappedMsg}What do you say?") .AddComponents(buttons) .AsEphemeral(true) do! ctx.FollowUp(msg) |> Async.AwaitTask }) }) let handleSteal (ctx : IDiscordContext) = let tokens = ctx.GetInteractionId().Split("-") let answer = tokens.[1] let handleYes (victim : PlayerData) (thief : PlayerData) = async { let targetId = uint64 tokens.[2] let targetName = tokens.[3] let amount = int tokens.[4] let prize , winPercentage , _ = calculateWinPercentage amount (int victim.Bank) thief.Stats.Strength.Amount victim.Stats.Strength.Amount let prize = int prize * 1 let rand = Random(Guid.NewGuid().GetHashCode()) let num = rand.NextDouble() let dp = { DiscordPlayer.Id = targetId ; DiscordPlayer.Name = targetName } let getResultEmbed' = getResultEmbed winPercentage prize thief.Bank thief dp // TODO: Send event to the hall of privacy // TODO: We need to check if the player is on cooldown match winPercentage >= num with | true -> let embed = getResultEmbed' Success do! Messaging.sendFollowUpEmbed ctx (embed.Build()) match! DbService.tryFindPlayer targetId with | Some t -> let mugged = { Type = Stealing ( false , thief.toDiscordPlayer ) Timestamp = DateTime.UtcNow Cooldown = VictimRecovery.Minutes * 1 } do! DbService.updatePlayer { t with Bank = max (t.Bank - prize) 0 } |> Async.Ignore do! DbService.addPlayerEvent victim.DiscordId mugged |> Async.Ignore | None -> () let stole = { Type = Stealing ( true , dp ) Cooldown = ThiefCooldown.Minutes * 1 Timestamp = DateTime.UtcNow } do! DbService.updatePlayer { thief with Bank = thief.Bank + prize } |> Async.Ignore do! DbService.addPlayerEvent victim.DiscordId stole |> Async.Ignore let builder = DiscordMessageBuilder() builder.WithContent($"{thief.Name} stole {prize} from <@{victim.DiscordId}>!") |> ignore let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelEventsHackerBattle) do! channel.SendMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore | false -> let embed = getResultEmbed' WentToPrison let imprisoned = { Type = Imprison Cooldown = ThiefCooldown.Minutes * 1 Timestamp = DateTime.UtcNow } do! DbService.addPlayerEvent victim.DiscordId imprisoned |> Async.Ignore do! Messaging.sendFollowUpEmbed ctx (embed.Build()) do! Async.Sleep 2000 let role = ctx.GetGuild().GetRole(GuildEnvironment.rolePrisoner) do! ctx.GetDiscordMember().GrantRoleAsync(role) |> Async.AwaitTask let builder = DiscordMessageBuilder() builder.WithContent($"{thief.Name} went to prison for trying to steal from {victim.Name}!") |> ignore let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelEventsHackerBattle) do! channel.SendMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore } if answer = "yes" then let targetId = uint64 tokens.[2] executePlayerActionWithTargetId true targetId ctx (fun attacker defender -> async { do! attacker |> Player.removeExpiredActions // |> checkVictimStealingCooldown defender |> checkThiefCooldown >>= checkTargetHasFunds defender |> handleResultWithResponse ctx (handleYes defender ) }) else async { let builder = DiscordInteractionResponseBuilder() builder.Content <- "I thought better of it" do! ctx.Respond(InteractionResponseType.UpdateMessage, builder) |> Async.AwaitTask } |> Async.StartAsTask :> Task let handleStealButton (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) = let eventCtx = DiscordEventContext event :> IDiscordContext match event.Id with | id when id.StartsWith("Steal") -> handleSteal eventCtx | _ -> task { let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true builder.Content <- $"Incorrect Action identifier {eventCtx.GetInteractionId()}" do! eventCtx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask } type StealGame() = inherit ApplicationCommandModule () [] member this.Steal (ctx : InteractionContext, [] amount : int64, [] target : DiscordUser) = // enforceChannel (DiscordInteractionContext ctx) (steal target amount) steal target (int amount) (DiscordInteractionContext ctx)