201 lines
8.7 KiB
Forth

module Degenz.Thief
open System
open System.Threading.Tasks
open DSharpPlus
open DSharpPlus.Entities
open DSharpPlus.SlashCommands
open Degenz.Messaging
[<Literal>]
let StealActionId = 12
[<Literal>]
let VictimDefenseActionId = 12
let ThiefCooldown = TimeSpan.FromMinutes(1)
let VictimRecovery = TimeSpan.FromHours(12)
type StealResult =
| Success
| WentToPrison
| TargetRanAway
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<DiscordComponent>
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"
| TargetRanAway ->
"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<GBT>
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<GBT> ; 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
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.channelArmory -> storeFn ctx
| _ ->
task {
let msg = $"You must go to <#{GuildEnvironment.channelArmory}> channel to buy or sell weapons"
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) =
// enforceChannel (DiscordInteractionContext ctx) (steal target)
steal target (DiscordInteractionContext ctx)