265 lines
12 KiB
Forth
265 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
|
|
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<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
|
|
| 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
|
|
|> Array.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<GBT> 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
|
|
|> Array.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 victim.Stats.Strength
|
|
// TODO: Readd stats
|
|
calculateWinPercentage amount (int victim.Bank) 0 0
|
|
|
|
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 =
|
|
// TODO: Readd stats
|
|
match 0 - 0 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]
|
|
// TODO: Readd stats
|
|
let prize , winPercentage , _ = calculateWinPercentage amount (int victim.Bank) 0 0
|
|
let prize = int prize * 1<GBT>
|
|
|
|
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 GuildEnvironment.pgDb targetId with
|
|
| Some t ->
|
|
let mugged = {
|
|
Type = Stealing ( false , thief.basicPlayer )
|
|
Timestamp = DateTime.UtcNow
|
|
Cooldown = VictimRecovery.Minutes * 1<mins>
|
|
}
|
|
do! DbService.updatePlayer GuildEnvironment.pgDb { t with Bank = max (t.Bank - prize) 0<GBT> } |> Async.Ignore
|
|
do! DbService.addPlayerEvent GuildEnvironment.pgDb victim.DiscordId mugged |> Async.Ignore
|
|
| None -> ()
|
|
|
|
let stole = {
|
|
Type = Stealing ( true , dp )
|
|
Cooldown = ThiefCooldown.Minutes * 1<mins>
|
|
Timestamp = DateTime.UtcNow
|
|
}
|
|
do! DbService.updatePlayer GuildEnvironment.pgDb { thief with Bank = thief.Bank + prize } |> Async.Ignore
|
|
do! DbService.addPlayerEvent GuildEnvironment.pgDb 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<mins>
|
|
Timestamp = DateTime.UtcNow
|
|
}
|
|
do! DbService.addPlayerEvent GuildEnvironment.pgDb 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 ()
|
|
|
|
[<SlashCommand("steal", "Steal some money from another player, but you might go to prison if caught")>]
|
|
member this.Steal (ctx : InteractionContext,
|
|
[<Option("amount", "How much you would like to steal")>] amount : int64,
|
|
[<Option("target", "Who do you want to steal from?")>] target : DiscordUser) =
|
|
// enforceChannel (DiscordInteractionContext ctx) (steal target amount)
|
|
steal target (int amount) (DiscordInteractionContext ctx)
|
|
|
|
|