From bb9c04fe6e5e75a3d12887aac041c6e7338799fd Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sun, 13 Feb 2022 03:12:55 +0700 Subject: [PATCH] First pass on thief game --- Bot/Bot.fs | 10 ++-- Bot/Bot.fsproj | 3 +- Bot/Game.fs | 19 ++++++- Bot/HackerBattle.fs | 33 +++++------- Bot/PlayerInteractions.fs | 2 + Bot/Thief.fs | 102 ++++++++++++++++++++++++++++++++++++++ DbService/DbService.fs | 2 + Shared/Shared.fs | 28 +++++++++++ 8 files changed, 173 insertions(+), 26 deletions(-) create mode 100644 Bot/Thief.fs diff --git a/Bot/Bot.fs b/Bot/Bot.fs index 46b1190..42e576b 100644 --- a/Bot/Bot.fs +++ b/Bot/Bot.fs @@ -6,6 +6,7 @@ open DSharpPlus.SlashCommands open Degenz open Degenz.HackerBattle open Degenz.Store +open Degenz.Thief open Emzi0767.Utilities //open Degenz.SlotMachine @@ -36,12 +37,13 @@ let storeBot = new DiscordClient(storeConfig) //let slotMachineBot = new DiscordClient(slotMachineConfig) //let clients = [| hackerBattleBot ; storeBot ; slotMachineBot |] -let sc1 = hackerBattleBot.UseSlashCommands() -let sc2 = storeBot.UseSlashCommands() +let hackerCommands = hackerBattleBot.UseSlashCommands() +let storeCommands = storeBot.UseSlashCommands() //let sc3 = slotMachineBot.UseSlashCommands() -sc1.RegisterCommands(guild); -sc2.RegisterCommands(guild); +hackerCommands.RegisterCommands(guild); +hackerCommands.RegisterCommands(guild); +storeCommands.RegisterCommands(guild); //sc3.RegisterCommands(guild); hackerBattleBot.add_ComponentInteractionCreated(AsyncEventHandler(HackerBattle.handleButtonEvent)) diff --git a/Bot/Bot.fsproj b/Bot/Bot.fsproj index ea7d573..66e530b 100644 --- a/Bot/Bot.fsproj +++ b/Bot/Bot.fsproj @@ -16,9 +16,10 @@ + + - diff --git a/Bot/Game.fs b/Bot/Game.fs index 39a629d..4e0b88b 100644 --- a/Bot/Game.fs +++ b/Bot/Game.fs @@ -38,7 +38,24 @@ module Game = let! playerResult = tryFindPlayer (ctx.GetDiscordMember().Id) match playerResult with | Some player -> do! dispatch player - | None -> do! Messaging.sendSimpleResponse ctx "You are currently not a hacker, first use the /redpill command to become one" + | None -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one" + } |> Async.StartAsTask :> Task + + let executePlayerWithTargetAction (targetPlayer : DiscordUser) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async) = + async { + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- "Content" + do! ctx.Respond InteractionResponseType.DeferredChannelMessageWithSource builder |> Async.AwaitTask + let! playerResult = tryFindPlayer (ctx.GetDiscordMember().Id) + let! targetResult = tryFindPlayer targetPlayer.Id + match playerResult , targetResult with + | Some player , Some target -> do! dispatch player target + | None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one" + | _ , None -> + if targetPlayer.IsBot + then do! Messaging.sendFollowUpMessage ctx $"{targetPlayer.Username} is a bot, pick a real human to hack" + else do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command" } |> Async.StartAsTask :> Task module Player = diff --git a/Bot/HackerBattle.fs b/Bot/HackerBattle.fs index 298f708..e450d7d 100644 --- a/Bot/HackerBattle.fs +++ b/Bot/HackerBattle.fs @@ -120,26 +120,18 @@ let failedHack (ctx : IDiscordContext) attacker defender hack = } let attack (target : DiscordUser) (ctx : IDiscordContext) = - Game.executePlayerAction ctx (fun attacker -> async { - let! defender = DbService.tryFindPlayer target.Id - match defender with - | Some defender -> - do! attacker - |> checkAlreadyHackedTarget defender.DiscordId - (Player.removeExpiredActions true) - >>= checkHasEmptyHacks - >>= checkTargetHasMoney defender - >>= checkPlayerIsAttackingThemselves defender - |> function - | Ok atkr -> - let embed = Embeds.pickHack "Attack" atkr defender false - ctx.FollowUp(embed) |> Async.AwaitTask - | Error msg -> sendFollowUpMessage ctx msg - | None -> - if target.IsBot - then do! sendFollowUpMessage ctx $"{target.Username} is a bot, pick a real human to hack" - else do! sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command" - + Game.executePlayerWithTargetAction target ctx (fun attacker defender -> async { + do! attacker + |> checkAlreadyHackedTarget defender.DiscordId + (Player.removeExpiredActions true) + >>= checkHasEmptyHacks + >>= checkTargetHasMoney defender + >>= checkPlayerIsAttackingThemselves defender + |> function + | Ok atkr -> + let embed = Embeds.pickHack "Attack" atkr defender false + ctx.FollowUp(embed) |> Async.AwaitTask + | Error msg -> sendFollowUpMessage ctx msg }) let handleAttack (ctx : IDiscordContext) = @@ -208,6 +200,7 @@ let handleButtonEvent (_ : DiscordClient) (event : ComponentInteractionCreateEve | id when id.StartsWith("Attack") -> handleAttack eventCtx | id when id.StartsWith("Defend") -> handleDefense eventCtx | id when id.StartsWith("Trainer") -> Trainer.handleButtonEvent eventCtx |> Async.StartAsTask :> Task + | id when id.StartsWith("Steal") -> Thief.handleSteal eventCtx | _ -> task { let builder = DiscordInteractionResponseBuilder() diff --git a/Bot/PlayerInteractions.fs b/Bot/PlayerInteractions.fs index f292be3..5a97e0c 100644 --- a/Bot/PlayerInteractions.fs +++ b/Bot/PlayerInteractions.fs @@ -16,6 +16,8 @@ module Commands = Name = nickname Arsenal = [| hack ; shield |] Actions = [||] + XP = 0 + Stats = PlayerStats.empty Bank = 100 } let addHackerRole (ctx : InteractionContext) = diff --git a/Bot/Thief.fs b/Bot/Thief.fs new file mode 100644 index 0000000..84a90a7 --- /dev/null +++ b/Bot/Thief.fs @@ -0,0 +1,102 @@ +module Degenz.Thief + +open System +open System.Threading.Tasks +open DSharpPlus +open DSharpPlus.Entities +open DSharpPlus.SlashCommands +open Degenz.Messaging + +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 payoutChance targetBank chance = targetBank * 0.1 * (1.0 - chance) + +let getStealEmbed chance (target : PlayerData) (player : PlayerData) = + let buttons = + let yes , no = getRandomStealBtnLabels () + [ DiscordButtonComponent(ButtonStyle.Success, $"Steal-yes-{target.DiscordId}-{target.Name}-{chance}", yes) + DiscordButtonComponent(ButtonStyle.Danger, $"Steal-no", no) ] + |> Seq.cast + let embed = + DiscordEmbedBuilder() + .AddField("Chance of Success", $"{chanceOfSuccessMsg chance}", 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 steal target (ctx : IDiscordContext) = + Game.executePlayerWithTargetAction target ctx (fun attacker defender -> async { + let winPercentage = if attacker.Stats.Strength > defender.Stats.Strength then 1.0 else 0.0 + let embed = getStealEmbed winPercentage defender attacker + + do! ctx.FollowUp(embed) |> Async.AwaitTask + }) + +let handleSteal (ctx : IDiscordContext) = + let split = ctx.GetInteractionId().Split("-") + let answer = split.[1] + if answer = "yes" then + Game.executePlayerAction ctx (fun player -> async { + let targetId = uint64 split.[2] + let targetName = split.[3] + let chance = double split.[4] + + let rand = Random(Guid.NewGuid().GetHashCode()) + let result = rand.NextDouble() + + if chance >= result then + let! target = DbService.tryFindPlayer targetId + do! Messaging.sendFollowUpMessage ctx $"You stole {targetName} money" + else + do! Messaging.sendFollowUpMessage ctx "You failed miserably" + + return () + }) + 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 + } + + [] + member this.Steal (ctx : InteractionContext, [] target : DiscordUser) = +// enforceChannel (DiscordInteractionContext ctx) (steal target) + steal target (DiscordInteractionContext ctx) + + diff --git a/DbService/DbService.fs b/DbService/DbService.fs index 0579774..c32a3ba 100644 --- a/DbService/DbService.fs +++ b/DbService/DbService.fs @@ -67,6 +67,8 @@ let private mapBack (player : PlayerEntry) : PlayerData = { let atks = player.Attacks |> Array.map attackToAction let dfns = player.Defenses |> Array.map defenseToAction Array.append atks dfns + Stats = PlayerStats.empty + XP = 0 Bank = player.Bank * 1 } diff --git a/Shared/Shared.fs b/Shared/Shared.fs index 59c3dd0..c3c3c5b 100644 --- a/Shared/Shared.fs +++ b/Shared/Shared.fs @@ -74,12 +74,31 @@ module Types = Type : ActionType Timestamp : DateTime } + type StatAmount = int + type XPAmount = int + + type AttributeId = + | Strength = 0 + | Cunning = 1 + + type PlayerStats = { + Strength : int + Focus : int + } + with static member empty = { Strength = 0 ; Focus = 0 } + + type PlayerXP = { + Amount : XPAmount + } + [] type PlayerData = { DiscordId : uint64 Name : string Arsenal : BattleItem array Actions : Action array + Stats : PlayerStats + XP : int Bank : int } module Armory = @@ -175,6 +194,15 @@ module Messaging = do! ctx.FollowUp(builder) |> Async.AwaitTask } + let sendFollowUpEmbed (ctx : IDiscordContext) embed = + async { + let builder = + DiscordFollowupMessageBuilder() + .AsEphemeral(true) + .AddEmbed(embed) + do! ctx.FollowUp(builder) |> Async.AwaitTask + } + let sendFollowUpMessageWithButton (ctx : IDiscordContext) interactiveMessage = async { let builder = DiscordFollowupMessageBuilder()