229 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Forth
		
	
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Forth
		
	
	
	
	
	
| module Degenz.Thief
 | |
| 
 | |
| open System
 | |
| open System.Threading.Tasks
 | |
| open DSharpPlus
 | |
| open DSharpPlus.Entities
 | |
| open DSharpPlus.SlashCommands
 | |
| open Degenz.Messaging
 | |
| 
 | |
| let ThiefCooldown = TimeSpan.FromMinutes(1)
 | |
| let VictimRecovery = TimeSpan.FromHours(6)
 | |
| 
 | |
| 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  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 amount (chance : double) prize (target : PlayerData) =
 | |
|     let chance = int (chance * 100.0)
 | |
|     let buttons =
 | |
| //        let yes , no = getRandomStealBtnLabels ()
 | |
|         let btnId = $"Steal-yes-{target.DiscordId}-{target.Name}-{amount}"
 | |
|         [ DiscordButtonComponent(ButtonStyle.Success, btnId, "Do it") ]
 | |
| //          DiscordButtonComponent(ButtonStyle.Danger, $"Steal-no", no) ]
 | |
|         |> Seq.cast<DiscordComponent>
 | |
|     let embed =
 | |
|         DiscordEmbedBuilder()
 | |
|           .AddField("Chance of Success", $"{chance}%%", true)
 | |
|           .AddField("Payout", $"{prize}", true)
 | |
|           .WithTitle($"Steal $GBT from {target.Name}")
 | |
| 
 | |
|     DiscordFollowupMessageBuilder()
 | |
|         .AddEmbed(embed)
 | |
|         .AddComponents(buttons)
 | |
|         .AsEphemeral(true)
 | |
| 
 | |
| let getResultEmbed chance prize (bank : int<GBT>) 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
 | |
|         | VictimRanAway ->
 | |
|             embed.AddField("Result" , $"Ran Away", true) |> ignore
 | |
|             $"Target **ran away**...\n\n{thief.Name} saw a shadowy figure and got nervous so they ran in the opposite direction"
 | |
|             , "https://i.imgur.com/NLHMvVK.jpg"
 | |
|             , DiscordColor.Grayple
 | |
|         | WentToPrison ->
 | |
|             embed.AddField("Result" , $"Imprisoned", true) |> ignore
 | |
|             $"You're going to **PRISON**...\n\n{thief.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)
 | |
| 
 | |
| let checkVictimStealingCooldown defender attacker =
 | |
|     defender
 | |
|     |> Player.removeExpiredActions false
 | |
|     |> Player.getShieldEvents
 | |
|     |> Array.tryFind (fun pe -> pe.Type = PlayerEventType.Steal && pe.Result = PlayerEventResult.Negative)
 | |
|     |> 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.getHackEvents
 | |
|     |> Array.tryFind (fun pe -> pe.Type = PlayerEventType.Steal)
 | |
|     |> 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 calculateWinPercentage amountRequested bank attackerStrength defenderStrength =
 | |
|     let powerPercentage = float (attackerStrength - defenderStrength) * 0.005 + 0.1
 | |
|     let cappedAmount = float bank * 0.5
 | |
|     let cappedRequest = min amountRequested (cappedAmount |> ceil)
 | |
|     let wagerPercentage = 1.0 - (cappedRequest / cappedAmount)
 | |
|     // Max chance of success is 90.0%
 | |
|     ( cappedRequest , max 0.0 (wagerPercentage * 0.7 + powerPercentage * 1.3 ) / 2.0 )
 | |
| 
 | |
| //calculateWinPercentage 50 200 100 85
 | |
| 
 | |
| let steal target amount (ctx : IDiscordContext) =
 | |
|     Game.executePlayerActionWithTarget target ctx (fun attacker defender -> async { do!
 | |
|         attacker
 | |
|         |> checkVictimStealingCooldown defender
 | |
|         >>= checkThiefCooldown
 | |
|         |> handleResultWithResponse ctx (fun _ -> async {
 | |
|             let cappedPrize , winPercentage = calculateWinPercentage amount (int defender.Bank) attacker.Traits.Strength defender.Traits.Strength
 | |
|             let embed = getStealEmbed amount winPercentage cappedPrize defender
 | |
| 
 | |
|             do! ctx.FollowUp(embed) |> Async.AwaitTask
 | |
|         })
 | |
|     })
 | |
| 
 | |
| let handleSteal (ctx : IDiscordContext) =
 | |
|     let split = ctx.GetInteractionId().Split("-")
 | |
|     let answer = split.[1]
 | |
| 
 | |
|     let handleYes (victim : PlayerData) (thief : PlayerData) = async {
 | |
|         let targetId = uint64 split.[2]
 | |
|         let targetName = split.[3]
 | |
|         let amount = int split.[4]
 | |
|         let prize , winPercentage = calculateWinPercentage amount (int victim.Bank) thief.Traits.Strength victim.Traits.Strength
 | |
|         let prize = int prize * 1<GBT>
 | |
| 
 | |
|         let rand = Random(Guid.NewGuid().GetHashCode())
 | |
| 
 | |
|         let num = rand.NextDouble()
 | |
|         let result = winPercentage >= num , rand.Next(0,3) = 0
 | |
|         let dp = { DiscordPlayer.Id = targetId ; DiscordPlayer.Name = targetName }
 | |
|         let stealAction result = { ItemId = -1 ; Type = PlayerEventType.Steal ; Result = result ; Adversary = dp ; Timestamp = DateTime.UtcNow }
 | |
|         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 result with
 | |
|         | true , _ ->
 | |
|             let embed = getResultEmbed' Success
 | |
| //            let xp = 25
 | |
| //            embed.AddField("XP Gained", $"{xp}") |> ignore
 | |
|             do! Messaging.sendFollowUpEmbed ctx (embed.Build())
 | |
|             match! DbService.tryFindPlayer targetId with
 | |
|             | Some t ->
 | |
|                 let mugged = { ItemId = -1 ; Type = PlayerEventType.Steal ; Result = PlayerEventResult.Negative ; Adversary = thief.basicPlayer ; Timestamp = DateTime.UtcNow }
 | |
|                 let actions = t |> Player.removeExpiredActions false |> fun p -> Array.append [| mugged |] p.Events
 | |
|                 do! DbService.updatePlayer { t with Bank = max (t.Bank - prize) 0<GBT> ; Events = actions }
 | |
|             | None -> ()
 | |
|             let stole = { ItemId = -1 ; Type = PlayerEventType.Steal ; Result = PlayerEventResult.Positive ; Adversary = dp ; Timestamp = DateTime.UtcNow }
 | |
|             let actions = thief |> Player.removeExpiredActions false |> fun p -> Array.append [| stole |] p.Events
 | |
|             do! DbService.updatePlayer { thief with Bank = thief.Bank + prize ; XP = thief.XP + 0 ; Events = actions }
 | |
| //            do! Async.Sleep 2000
 | |
| //            do! ctx.FollowUp (XP.getRewardsEmbed 1 player) |> Async.AwaitTask
 | |
|         | false , false ->
 | |
|             let embed = getResultEmbed' VictimRanAway
 | |
|             do! DbService.updatePlayer { thief with Events = Array.append [| stealAction PlayerEventResult.Neutral |] thief.Events }
 | |
|             do! Messaging.sendFollowUpEmbed ctx (embed.Build())
 | |
|         | false , true ->
 | |
|             let embed = getResultEmbed' WentToPrison
 | |
|             do! DbService.updatePlayer { thief with Events = Array.append [| stealAction PlayerEventResult.Neutral |] thief.Events }
 | |
|             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 true targetId ctx (fun attacker defender -> async {
 | |
|             do! attacker
 | |
|                 |> Player.removeExpiredActions false
 | |
|                 |> checkVictimStealingCooldown defender
 | |
|                 >>= checkThiefCooldown
 | |
|                 |> 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
 | |
| 
 | |
| 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
 | |
|             }
 | |
| 
 | |
|     [<SlashCommand("steal", "Steal some money from another player, but you might go to prison if caught")>]
 | |
|     member this.Steal (ctx : InteractionContext,
 | |
|                        [<Option("target", "Who do you want to steal from?")>] target : DiscordUser,
 | |
|                        [<Option("amount", "How much you would like to steal")>] amount : double) =
 | |
| //        enforceChannel (DiscordInteractionContext ctx) (steal target amount)
 | |
|         steal target amount (DiscordInteractionContext ctx)
 | |
| 
 | |
| 
 |