274 lines
12 KiB
Forth

module Degenz.Thief
open System
open System.Threading.Tasks
open DSharpPlus
open DSharpPlus.Entities
open DSharpPlus.EventArgs
open DSharpPlus.SlashCommands
open Degenz.Messaging
let ThiefCooldown = TimeSpan.FromMinutes(1)
let VictimRecovery = TimeSpan.FromHours(4)
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 btnId = $"Steal-yes-{target.DiscordId}-{target.Name}-{amount}"
[ DiscordButtonComponent(ButtonStyle.Success, btnId, "Do it") ]
|> 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
|> fun p -> p.Events
|> 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
let checkThiefCooldown attacker =
attacker
|> Player.removeExpiredActions
|> fun p -> p.Events
|> Array.tryFind (fun pe -> pe.Type = PlayerEventType.Steal)
|> 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 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) =
Game.executePlayerActionWithTarget target ctx (fun thief victim -> async { do!
thief
|> checkVictimStealingCooldown victim
>>= checkThiefCooldown
|> handleResultWithResponse ctx (fun _ -> async {
let cappedPrize , winPercentage , wasCapped =
calculateWinPercentage amount (int victim.Bank) thief.Traits.Strength victim.Traits.Strength
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<DiscordComponent>
let cappedMsg = if wasCapped then $"They only have {cappedPrize} $GBT though... " else ""
let strengthMsg =
match thief.Traits.Strength - victim.Traits.Strength 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 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
IsInstigator = true
Cooldown = ThiefCooldown.Minutes * 1<mins>
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
IsInstigator = false
Cooldown = VictimRecovery.Minutes * 1<mins>
}
let actions = t |> Player.removeExpiredActions |> 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
IsInstigator = true
Cooldown = ThiefCooldown.Minutes * 1<mins>
}
let actions = thief |> Player.removeExpiredActions |> 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
|> 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
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 ()
[<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)