diff --git a/Bot/Embeds.fs b/Bot/Embeds.fs index 9859f02..4a7b5c2 100644 --- a/Bot/Embeds.fs +++ b/Bot/Embeds.fs @@ -1,7 +1,7 @@ module Degenz.Embeds open System -open DSharpPlus.EventArgs +open Degenz.Messaging open Degenz.Types open DSharpPlus.Entities @@ -96,9 +96,9 @@ let responseCreatedShield (shield : BattleItem) = .AddEmbed(embed) .AsEphemeral(true) -let eventSuccessfulHack (event : ComponentInteractionCreateEventArgs) target prize = +let eventSuccessfulHack (ctx : IDiscordContext) target prize = DiscordMessageBuilder() - .WithContent($"{event.User.Username} successfully hacked <@{target.DiscordId}> for a total of {prize} GoodBoyTokenz") + .WithContent($"{ctx.GetDiscordMember().Username} successfully hacked <@{target.DiscordId}> for a total of {prize} GoodBoyTokenz") let getBuyItemsEmbed (player : PlayerData) (itemType : ItemType) (store : BattleItem array) = let embeds , buttons = @@ -166,7 +166,7 @@ let getArsenalEmbed (player : PlayerData) = DiscordEmbedBuilder() .AddField( "Arsenal", Arsenal.statusFormat player )) -let getAchievementEmbed (player : PlayerData) description achievement = +let getAchievementEmbed description achievement = let embed = DiscordEmbedBuilder() GuildEnvironment.botUserHackerBattle diff --git a/Bot/Game.fs b/Bot/Game.fs index 158f2aa..39a629d 100644 --- a/Bot/Game.fs +++ b/Bot/Game.fs @@ -6,6 +6,7 @@ open DSharpPlus.Entities open DSharpPlus.EventArgs open DSharpPlus.SlashCommands open Degenz.DbService +open Degenz.Messaging module Game = let HackPrize = 10 @@ -28,34 +29,17 @@ module Game = | BattleClass.Penetration -> ( ShieldId.Cypher , HackId.RemoteAccess ) | BattleClass.Exploit -> ( ShieldId.Encryption , HackId.Worm ) - let executePlayerInteraction (ctx : InteractionContext) (dispatch : PlayerData -> Async) = + let executePlayerAction (ctx : IDiscordContext) (dispatch : PlayerData -> Async) = async { let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true builder.Content <- "Content" - do! ctx.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, builder) - |> Async.AwaitTask - let! playerResult = tryFindPlayer ctx.Member.Id + do! ctx.Respond InteractionResponseType.DeferredChannelMessageWithSource builder |> Async.AwaitTask + let! playerResult = tryFindPlayer (ctx.GetDiscordMember().Id) match playerResult with | Some player -> do! dispatch player - | None -> do! Messaging.sendFollowUpMessageFromCtx ctx "You are currently not a hacker, first use the /redpill command to become one" - } |> Async.StartAsTask - :> Task - - // TODO J: Create an abstraction for these two helper functions - let executePlayerEvent (event : ComponentInteractionCreateEventArgs) (dispatch : PlayerData -> Async) = - async { - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- "Content" - do! event.Interaction.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, builder) - |> Async.AwaitTask - let! playerResult = tryFindPlayer event.User.Id - match playerResult with - | Some player -> do! dispatch player - | None -> do! Messaging.sendInteractionEvent event "You are currently not a hacker, first use the /redpill command to become one" - } |> Async.StartAsTask - :> Task + | None -> do! Messaging.sendSimpleResponse ctx "You are currently not a hacker, first use the /redpill command to become one" + } |> Async.StartAsTask :> Task module Player = let getItems itemType (player : PlayerData) = player.Arsenal |> Array.filter (fun i -> i.Type = itemType) diff --git a/Bot/HackerBattle.fs b/Bot/HackerBattle.fs index e8b3a74..298f708 100644 --- a/Bot/HackerBattle.fs +++ b/Bot/HackerBattle.fs @@ -90,39 +90,37 @@ let updateCombatants attacker defender hack prize = |> Async.Parallel |> Async.Ignore -let successfulHack (event : ComponentInteractionCreateEventArgs) attacker defender hack = +let successfulHack (ctx : IDiscordContext) attacker defender hack = async { do! updateCombatants attacker defender hack Game.HackPrize let embed = Embeds.responseSuccessfulHack true defender.DiscordId (Armory.getItem hack) - do! event.Interaction.CreateFollowupMessageAsync(embed) - |> Async.AwaitTask - |> Async.Ignore + do! ctx.FollowUp embed |> Async.AwaitTask - let builder = Embeds.eventSuccessfulHack event defender Game.HackPrize - let channel = event.Guild.GetChannel(GuildEnvironment.channelEventsHackerBattle) + let builder = Embeds.eventSuccessfulHack ctx defender Game.HackPrize + let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelEventsHackerBattle) do! channel.SendMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore } -let failedHack (event : ComponentInteractionCreateEventArgs) attacker defender hack = +let failedHack (ctx : IDiscordContext) attacker defender hack = async { let msg = $"Hack failed! {defender.Name} was able to mount a successful defense! You lost {Game.ShieldPrize} $GBT!" - do! sendFollowUpMessage event msg + do! sendFollowUpMessage ctx msg do! updateCombatants attacker defender hack -Game.ShieldPrize let builder = DiscordMessageBuilder() - builder.WithContent($"Hacking attempt failed! <@{defender.DiscordId}> defended hack from {event.User.Username} and stole {Game.ShieldPrize} $GBT from them! ") |> ignore - let channel = (event.Guild.GetChannel(GuildEnvironment.channelEventsHackerBattle)) + builder.WithContent($"Hacking attempt failed! <@{defender.DiscordId}> defended hack from {ctx.GetDiscordMember().Username} and stole {Game.ShieldPrize} $GBT from them! ") |> ignore + let channel = (ctx.GetGuild().GetChannel(GuildEnvironment.channelEventsHackerBattle)) do! channel.SendMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore } -let attack (target : DiscordUser) (ctx : InteractionContext) = - Game.executePlayerInteraction ctx (fun attacker -> async { +let attack (target : DiscordUser) (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun attacker -> async { let! defender = DbService.tryFindPlayer target.Id match defender with | Some defender -> @@ -135,20 +133,18 @@ let attack (target : DiscordUser) (ctx : InteractionContext) = |> function | Ok atkr -> let embed = Embeds.pickHack "Attack" atkr defender false - ctx.FollowUpAsync(embed) - |> Async.AwaitTask - |> Async.Ignore - | Error msg -> sendFollowUpMessageFromCtx ctx msg + ctx.FollowUp(embed) |> Async.AwaitTask + | Error msg -> sendFollowUpMessage ctx msg | None -> if target.IsBot - then do! sendFollowUpMessageFromCtx ctx $"{target.Username} is a bot, pick a real human to hack" - else do! sendFollowUpMessageFromCtx ctx "Your target is not connected to the network, they must join first by using the /redpill command" + 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" }) -let handleAttack (event : ComponentInteractionCreateEventArgs) = - Game.executePlayerEvent event (fun attacker -> async { - let split = event.Id.Split("-") +let handleAttack (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun attacker -> async { + let split = ctx.GetInteractionId().Split("-") let hackId = int split.[1] let hack = enum(hackId) let ( resultId , targetId ) = UInt64.TryParse split.[2] @@ -165,28 +161,26 @@ let handleAttack (event : ComponentInteractionCreateEventArgs) = | Ok atkr -> runHackerBattle defender (Armory.getItem (int hackId)) |> function - | false -> successfulHack event atkr defender hackId - | true -> failedHack event attacker defender hackId - | Error msg -> Messaging.sendFollowUpMessage event msg - | _ -> do! Messaging.sendFollowUpMessage event "Error occurred processing attack" + | false -> successfulHack ctx atkr defender hackId + | true -> failedHack ctx attacker defender hackId + | Error msg -> Messaging.sendFollowUpMessage ctx msg + | _ -> do! Messaging.sendFollowUpMessage ctx "Error occurred processing attack" }) -let defend (ctx : InteractionContext) = - Game.executePlayerInteraction ctx (fun player -> async { +let defend (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun player -> async { if Player.getShields player |> Array.length > 0 then let p = Player.removeExpiredActions false player let embed = Embeds.pickDefense "Defend" p false - do! ctx.FollowUpAsync(embed) - |> Async.AwaitTask - |> Async.Ignore + do! ctx.FollowUp embed |> Async.AwaitTask else let msg = $"You currently do not have any Shields to protect yourself from hacks. Please go to the <#{GuildEnvironment.channelArmory}> and purchase one." - do! Messaging.sendFollowUpMessageFromCtx ctx msg + do! Messaging.sendFollowUpMessage ctx msg }) -let handleDefense (event : ComponentInteractionCreateEventArgs) = - Game.executePlayerEvent event (fun player -> async { - let split = event.Id.Split("-") +let handleDefense (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun player -> async { + let split = ctx.GetInteractionId().Split("-") let shieldId = int split.[1] let shield = Armory.getItem shieldId @@ -194,16 +188,14 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) = |> checkPlayerOwnsWeapon shieldId >>= checkPlayerHasShieldSlotsAvailable shield >>= checkItemHasCooldown shieldId - |> handleResultWithResponseFromEvent event (fun p -> async { + |> handleResultWithResponseFromEvent ctx (fun p -> async { let embed = Embeds.responseCreatedShield (Armory.getItem shieldId) - do! event.Interaction.CreateFollowupMessageAsync(embed) - |> Async.AwaitTask - |> Async.Ignore + do! ctx.FollowUp embed |> Async.AwaitTask let defense = { ActionId = shieldId ; Type = Defense ; Timestamp = DateTime.UtcNow } do! DbService.updatePlayer <| { player with Actions = Array.append [| defense |] player.Actions } let builder = DiscordMessageBuilder() - builder.WithContent($"{event.User.Username} has protected their system!") |> ignore - let channel = event.Guild.GetChannel(GuildEnvironment.channelEventsHackerBattle) + builder.WithContent($"{ctx.GetDiscordMember().Username} has protected their system!") |> ignore + let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelEventsHackerBattle) do! channel.SendMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore @@ -211,40 +203,38 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) = }) let handleButtonEvent (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) = + let eventCtx = DiscordEventContext event :> IDiscordContext match event.Id with - | id when id.StartsWith("Attack") -> handleAttack event - | id when id.StartsWith("Defend") -> handleDefense event - | id when id.StartsWith("Trainer") -> Trainer.handleButtonEvent event |> Async.StartAsTask :> Task + | 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 | _ -> task { let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true - builder.Content <- $"Incorrect Action identifier {event.Id}" - do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask + builder.Content <- $"Incorrect Action identifier {eventCtx.GetInteractionId()}" + do! eventCtx.Respond InteractionResponseType.ChannelMessageWithSource builder |> Async.AwaitTask } -let arsenal (ctx : InteractionContext) = - Game.executePlayerInteraction ctx (fun player -> async { +let arsenal (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun player -> async { let updatedPlayer = Player.removeExpiredActions false player let builder = DiscordFollowupMessageBuilder() let embed = DiscordEmbedBuilder() embed.AddField("Arsenal", Arsenal.statusFormat updatedPlayer) |> ignore builder.AddEmbed(embed) |> ignore builder.IsEphemeral <- true - do! ctx.FollowUpAsync(builder) - |> Async.AwaitTask - |> Async.Ignore + do! ctx.FollowUp(builder) |> Async.AwaitTask do! DbService.updatePlayer updatedPlayer }) type HackerGame() = inherit ApplicationCommandModule () - let enforceChannels (ctx : InteractionContext) (trainerFn : InteractionContext -> Task) (battleFn : InteractionContext -> Task) = - match ctx.Channel.Id with + let enforceChannels (ctx : IDiscordContext) (trainerFn : IDiscordContext -> Task) (battleFn : IDiscordContext -> Task) = + match ctx.GetChannel().Id with | id when id = GuildEnvironment.channelTraining -> - let hasTraineeRole = Seq.exists (fun (r : DiscordRole) -> r.Id = GuildEnvironment.roleTrainee) ctx.Member.Roles + let hasTraineeRole = Seq.exists (fun (r : DiscordRole) -> r.Id = GuildEnvironment.roleTrainee) (ctx.GetDiscordMember().Roles) if hasTraineeRole then trainerFn ctx else @@ -263,15 +253,15 @@ type HackerGame() = [] member this.Arsenal (ctx : InteractionContext) = - enforceChannels ctx (Trainer.handleArsenal) arsenal + enforceChannels (DiscordInteractionContext ctx) (Trainer.handleArsenal) arsenal [] member this.AttackCommand (ctx : InteractionContext, [] target : DiscordUser) = - enforceChannels ctx (Trainer.attack target) (attack target) + enforceChannels (DiscordInteractionContext ctx) (Trainer.attack target) (attack target) [] member this.ShieldCommand (ctx : InteractionContext) = - enforceChannels ctx Trainer.defend defend + enforceChannels (DiscordInteractionContext ctx) Trainer.defend defend // [] member this.TestAutoComplete (ctx : InteractionContext) = diff --git a/Bot/SlotMachine.fs b/Bot/SlotMachine.fs index b401ed9..456db2d 100644 --- a/Bot/SlotMachine.fs +++ b/Bot/SlotMachine.fs @@ -5,6 +5,7 @@ open System.Threading.Tasks open DSharpPlus open DSharpPlus.Entities open DSharpPlus.SlashCommands +open Degenz.Messaging open Degenz.Types let slots = [| "https://i.ibb.co/pKqZdr7/cherry.png" ; "https://i.ibb.co/JnghQsL/lemon.jpg" ; "https://i.ibb.co/1JTFPSs/seven.png" |] @@ -14,7 +15,7 @@ type SlotMachine() = [] member this.Spin (ctx : InteractionContext) = - Game.executePlayerInteraction ctx (fun player -> async { + Game.executePlayerAction (DiscordInteractionContext ctx) (fun player -> async { let sleepTime = 1000 let random = Random(System.Guid.NewGuid().GetHashCode()) let results = [ random.Next(0, 3) ; random.Next(0, 3) ; random.Next(0, 3)] diff --git a/Bot/Store.fs b/Bot/Store.fs index 332318e..285f10c 100644 --- a/Bot/Store.fs +++ b/Bot/Store.fs @@ -6,7 +6,6 @@ open DSharpPlus open DSharpPlus.EventArgs open DSharpPlus.SlashCommands open Degenz -open Degenz.Embeds open Degenz.Messaging let checkHasSufficientFunds (item : BattleItem) player = @@ -29,47 +28,41 @@ let checkHasItemsInArsenal itemType player = then Ok player else Error $"You currently have no {itemType}s in your arsenal to sell!" -let buy itemType (ctx : InteractionContext) = - Game.executePlayerInteraction ctx (fun player -> async { +let buy itemType (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun player -> async { let itemStore = Embeds.getBuyItemsEmbed player itemType Armory.battleItems - do! ctx.Interaction.CreateFollowupMessageAsync(itemStore) - |> Async.AwaitTask - |> Async.Ignore + do! ctx.FollowUp itemStore |> Async.AwaitTask }) -let sell itemType (ctx : InteractionContext) = - Game.executePlayerInteraction ctx (fun player -> async { +let sell itemType (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun player -> async { match checkHasItemsInArsenal itemType player with - | Ok _ -> - let itemStore = Embeds.getSellItemsEmbed itemType player - - do! ctx.FollowUpAsync(itemStore) - |> Async.AwaitTask - |> Async.Ignore - | Error e -> do! sendFollowUpMessageFromCtx ctx e + | Ok _ -> let itemStore = Embeds.getSellItemsEmbed itemType player + do! ctx.FollowUp(itemStore) |> Async.AwaitTask + | Error e -> do! sendFollowUpMessage ctx e }) // TODO: When you buy a shield, prompt the user to activate it -let handleBuyItem (event : ComponentInteractionCreateEventArgs) itemId = - Game.executePlayerEvent event (fun player -> async { +let handleBuyItem (ctx : IDiscordContext) itemId = + Game.executePlayerAction ctx (fun player -> async { let item = Armory.battleItems |> Array.find (fun w -> w.Id = itemId) do! player |> checkHasSufficientFunds item >>= checkAlreadyOwnsItem item - |> handleResultWithResponseFromEvent event (fun player -> async { + |> handleResultWithResponseFromEvent ctx (fun player -> async { let newBalance = player.Bank - item.Cost let p = { player with Bank = newBalance ; Arsenal = Array.append [| item |] player.Arsenal } do! DbService.updatePlayer p - do! sendFollowUpMessage event $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining" + do! sendFollowUpMessage ctx $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining" }) }) -let handleSell (event : ComponentInteractionCreateEventArgs) itemId = - Game.executePlayerEvent event (fun player -> async { +let handleSell (ctx : IDiscordContext) itemId = + Game.executePlayerAction ctx (fun player -> async { let item = Armory.getItem itemId do! player |> checkSoldItemAlready item - |> handleResultWithResponseFromEvent event (fun player -> async { + |> handleResultWithResponseFromEvent ctx (fun player -> async { let updatedPlayer = { player with Bank = player.Bank + item.Cost @@ -80,29 +73,30 @@ let handleSell (event : ComponentInteractionCreateEventArgs) itemId = else player.Actions } do! DbService.updatePlayer updatedPlayer - do! sendFollowUpMessage event $"Sold {item.Type} {item.Name} for {item.Cost}! Current Balance: {updatedPlayer.Bank}" + do! sendFollowUpMessage ctx $"Sold {item.Type} {item.Name} for {item.Cost}! Current Balance: {updatedPlayer.Bank}" }) }) let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) = - let itemId = int <| event.Id.Split("-").[1] - match event.Id with - | id when id.StartsWith("Buy") -> handleBuyItem event itemId - | id when id.StartsWith("Sell") -> handleSell event itemId + let ctx = DiscordEventContext event :> IDiscordContext + let id = ctx.GetInteractionId() + let itemId = int <| id.Split("-").[1] + match id with + | id when id.StartsWith("Buy") -> handleBuyItem ctx itemId + | id when id.StartsWith("Sell") -> handleSell ctx itemId | _ -> task { let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true - builder.Content <- $"Incorrect Action identifier {event.Id}" - do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask + builder.Content <- $"Incorrect Action identifier {id}" + do! ctx.Respond InteractionResponseType.ChannelMessageWithSource builder |> Async.AwaitTask } type Store() = inherit ApplicationCommandModule () - let enforceChannel (ctx : InteractionContext) (storeFn : InteractionContext -> Task) = - match ctx.Channel.Id with + let enforceChannel (ctx : IDiscordContext) (storeFn : IDiscordContext -> Task) = + match ctx.GetChannel().Id with | id when id = GuildEnvironment.channelArmory -> storeFn ctx | _ -> task { @@ -111,14 +105,14 @@ type Store() = } [] - member _.BuyHack (ctx : InteractionContext) = enforceChannel ctx (buy ItemType.Hack) + member _.BuyHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy ItemType.Hack) [] - member this.BuyShield (ctx : InteractionContext) = enforceChannel ctx (buy ItemType.Shield) + member this.BuyShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy ItemType.Shield) [] - member this.SellHack (ctx : InteractionContext) = enforceChannel ctx (sell ItemType.Hack) + member this.SellHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell ItemType.Hack) [] - member this.SellShield (ctx : InteractionContext) = enforceChannel ctx (sell ItemType.Shield) + member this.SellShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell ItemType.Shield) diff --git a/Bot/Trainer.fs b/Bot/Trainer.fs index f46f15f..d982452 100644 --- a/Bot/Trainer.fs +++ b/Bot/Trainer.fs @@ -4,7 +4,6 @@ open System.Text open DSharpPlus open DSharpPlus.Entities open DSharpPlus.EventArgs -open DSharpPlus.SlashCommands open Degenz.Types open Degenz.Messaging @@ -30,8 +29,8 @@ let sendInitialEmbed (client : DiscordClient) = |> Async.Ignore } |> Async.RunSynchronously -let handleTrainerStep1 (event : ComponentInteractionCreateEventArgs) = - Game.executePlayerEvent event (fun player -> async { +let handleTrainerStep1 (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun player -> async { let shieldMessage , weaponName = if Player.getShields player |> Array.isEmpty then $"You do not have any Shields in your arsenal, take the `{defaultShield.Name}` shield, you can use it for now.\n\n" , defaultShield.Name @@ -39,20 +38,19 @@ let handleTrainerStep1 (event : ComponentInteractionCreateEventArgs) = let name = Player.getShields player |> Array.tryHead |> Option.defaultValue defaultShield |> fun w -> w.Name $"Looks like you have `{name}` in your arsenal… 👀\n\n" , name - let membr = event.User :?> DiscordMember - let role = event.Guild.GetRole(GuildEnvironment.roleTrainee) - do! membr.GrantRoleAsync(role) + let role = ctx.GetGuild().GetRole(GuildEnvironment.roleTrainee) + do! ctx.GetDiscordMember().GrantRoleAsync(role) |> Async.AwaitTask - do! sendFollowUpMessage event + do! sendFollowUpMessage ctx ("Beautopia© is a dangerous place... quick, put up a SHIELD 🛡 before another Degen hacks you, and steals your 💰$GBT.\n\n" + shieldMessage + "To enable it, you need to run the `/shield` slash command.\n\n" + $"Type the `/shield` command now, then select - `{weaponName}`\n") }) -let defend (ctx : InteractionContext) = - Game.executePlayerInteraction ctx (fun player -> async { +let defend (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun player -> async { let playerWithShields = match Player.getShields player with | [||] -> { player with Arsenal = [| defaultShield |] } @@ -60,9 +58,7 @@ let defend (ctx : InteractionContext) = let embed = Embeds.pickDefense "Trainer-2" playerWithShields true - do! ctx.FollowUpAsync(embed) - |> Async.AwaitTask - |> Async.Ignore + do! ctx.FollowUp(embed) |> Async.AwaitTask }) let handleDefenseMsg shieldId hackId = { @@ -73,25 +69,25 @@ let handleDefenseMsg shieldId hackId = { + "Shields only protect you for a LIMITED TIME, so remember to keep them mounted at all times, or risk getting hacked!" } -let handleDefense (event : ComponentInteractionCreateEventArgs) = - Game.executePlayerEvent event (fun player -> async { - let sendMessage' = sendFollowUpMessage event - let split = event.Id.Split("-") +let handleDefense (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun player -> async { + let sendMessage' = sendFollowUpMessage ctx + let split = ctx.GetInteractionId().Split("-") let shieldId = enum(int split.[2]) let shield = Armory.getItem (int shieldId) - let embed = Embeds.responseCreatedShield (shield) - do! event.Interaction.CreateFollowupMessageAsync(embed) |> Async.AwaitTask |> Async.Ignore + let embed = Embeds.responseCreatedShield shield + do! ctx.FollowUp embed |> Async.AwaitTask do! Async.Sleep 4000 let weakHack = Game.getGoodAgainst shield.Class do! sendMessage' $"Ok, good, let me make sure that worked.\n\nI'll try to **hack** you now with **{snd weakHack}**" do! Async.Sleep 5000 do! sendMessage' $"❌ HACKING FAILED!\n\n{player.Name} defended hack from <@{GuildEnvironment.botIdHackerBattle}>!" do! Async.Sleep 4000 - do! sendFollowUpMessageWithButton event (handleDefenseMsg shieldId (snd weakHack)) + do! sendFollowUpMessageWithButton ctx (handleDefenseMsg shieldId (snd weakHack)) }) -let handleTrainerStep3 (event : ComponentInteractionCreateEventArgs) = - Game.executePlayerEvent event (fun player -> async { +let handleTrainerStep3 (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun player -> async { let hackMessage , weaponName = if Player.getHacks player |> Array.isEmpty then $"You do not have any Hacks in your arsenal, take this `{defaultHack.Name}`, you can use it for now.\n\n" , defaultHack.Name @@ -99,7 +95,7 @@ let handleTrainerStep3 (event : ComponentInteractionCreateEventArgs) = let name = Player.getHacks player |> Array.tryHead |> Option.defaultValue defaultHack |> fun w -> w.Name $"Looks like you have `{name}` in your arsenal...\n\n" , name - do! sendFollowUpMessage event + do! sendFollowUpMessage ctx ("Now let’s **HACK!** 💻\n\n" + "I want you to **HACK ME**...\n\n" + hackMessage @@ -107,8 +103,8 @@ let handleTrainerStep3 (event : ComponentInteractionCreateEventArgs) = + $"Type the `/hack` command now, then choose me - <@{GuildEnvironment.botIdHackerBattle}> as your target, and select `{weaponName}`") }) -let attack (target : DiscordUser) (ctx : InteractionContext) = - Game.executePlayerInteraction ctx (fun player -> async { +let attack (target : DiscordUser) (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun player -> async { let isRightTarget = target.Id = GuildEnvironment.botIdHackerBattle match isRightTarget with | true -> @@ -119,27 +115,23 @@ let attack (target : DiscordUser) (ctx : InteractionContext) = let bot = { player with DiscordId = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" } let embed = Embeds.pickHack "Trainer-4" playerWithAttacks bot true - do! ctx.FollowUpAsync(embed) - |> Async.AwaitTask - |> Async.Ignore + do! ctx.FollowUp(embed) |> Async.AwaitTask | false -> let builder = DiscordFollowupMessageBuilder() builder.Content <- "You picked the wrong target, you dufus. Try again, this time pick me!" builder.IsEphemeral <- true - do! ctx.FollowUpAsync(builder) - |> Async.AwaitTask - |> Async.Ignore + do! ctx.FollowUp(builder) |> Async.AwaitTask }) -let handleAttack (event : ComponentInteractionCreateEventArgs) = - Game.executePlayerEvent event (fun player -> async { - let sendMessage' = sendFollowUpMessage event +let handleAttack (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun player -> async { + let sendMessage' = sendFollowUpMessage ctx do! Async.Sleep 1000 let hack = Player.getHacks player |> Array.tryHead |> Option.defaultValue defaultHack let embed = Embeds.responseSuccessfulHack false GuildEnvironment.botIdHackerBattle hack - do! event.Interaction.CreateFollowupMessageAsync(embed) |> Async.AwaitTask |> Async.Ignore + do! ctx.FollowUp(embed) |> Async.AwaitTask do! Async.Sleep 5000 do! sendMessage' ("🎉 **Congratulations**\n\n" @@ -201,43 +193,37 @@ let handleAttack (event : ComponentInteractionCreateEventArgs) = |> Array.append player.Arsenal } do! DbService.updatePlayer updatedPlayer - do! sendFollowUpMessage event (sb.ToString()) + do! sendFollowUpMessage ctx (sb.ToString()) else - do! sendFollowUpMessage event ($"Your training is now complete. If you want to buy more **HACKS & SHIELDS**, go to the <#{GuildEnvironment.channelArmory}> **NOW** and type the `/buy-hack` and `/buy-shield` commands! 😱") - let role = event.Guild.GetRole(GuildEnvironment.roleTrainee) - let ``member`` = event.User :?> DiscordMember - do! ``member``.RevokeRoleAsync(role) + do! sendFollowUpMessage ctx ($"Your training is now complete. If you want to buy more **HACKS & SHIELDS**, go to the <#{GuildEnvironment.channelArmory}> **NOW** and type the `/buy-hack` and `/buy-shield` commands! 😱") + let role = ctx.GetGuild().GetRole(GuildEnvironment.roleTrainee) + do! ctx.GetDiscordMember().RevokeRoleAsync(role) |> Async.AwaitTask }) -let handleArsenal (ctx : InteractionContext) = - Game.executePlayerInteraction ctx (fun player -> async { +let handleArsenal (ctx : IDiscordContext) = + Game.executePlayerAction ctx (fun player -> async { let updatedPlayer = Player.removeExpiredActions false player let embed = Embeds.getArsenalEmbed updatedPlayer - do! ctx.FollowUpAsync(embed) - |> Async.AwaitTask - |> Async.Ignore + do! ctx.FollowUp(embed) |> Async.AwaitTask do! Async.Sleep 3000 - let embed = Embeds.getAchievementEmbed player "You completed the Training Dojo and collected loot." trainerAchievement - do! ctx.FollowUpAsync(embed) - |> Async.AwaitTask - |> Async.Ignore + let embed = Embeds.getAchievementEmbed "You completed the Training Dojo and collected loot." trainerAchievement + do! ctx.FollowUp(embed) |> Async.AwaitTask do! Async.Sleep 2000 - let role = ctx.Guild.GetRole(GuildEnvironment.roleTrainee) - do! ctx.Member.RevokeRoleAsync(role) - |> Async.AwaitTask + let role = ctx.GetGuild().GetRole(GuildEnvironment.roleTrainee) + do! ctx.GetDiscordMember().RevokeRoleAsync(role) |> Async.AwaitTask - do! sendFollowUpMessageFromCtx ctx $"Now get out of there and go hack other Degenz in the <#{GuildEnvironment.channelBattle}> channel!" + do! sendFollowUpMessage ctx $"Now get out of there and go hack other Degenz in the <#{GuildEnvironment.channelBattle}> channel!" }) -let handleButtonEvent (event : ComponentInteractionCreateEventArgs) = +let handleButtonEvent (ctx : IDiscordContext) = async { - let split = event.Id.Split("-") + let split = ctx.GetInteractionId().Split("-") match int split.[1] with - | 1 -> do! handleTrainerStep1 event |> Async.AwaitTask - | 2 -> do! handleDefense event |> Async.AwaitTask - | 3 -> do! handleTrainerStep3 event |> Async.AwaitTask - | 4 -> do! handleAttack event |> Async.AwaitTask - | _ -> do! sendFollowUpMessage event "No action found" + | 1 -> do! handleTrainerStep1 ctx |> Async.AwaitTask + | 2 -> do! handleDefense ctx |> Async.AwaitTask + | 3 -> do! handleTrainerStep3 ctx |> Async.AwaitTask + | 4 -> do! handleAttack ctx |> Async.AwaitTask + | _ -> do! sendFollowUpMessage ctx "No action found" } diff --git a/Shared/Shared.fs b/Shared/Shared.fs index 04f4a73..59c3dd0 100644 --- a/Shared/Shared.fs +++ b/Shared/Shared.fs @@ -1,6 +1,7 @@ namespace Degenz open System +open System.Threading.Tasks open DSharpPlus open DSharpPlus.Entities open DSharpPlus.EventArgs @@ -71,7 +72,7 @@ module Types = type Action = { ActionId : int Type : ActionType - Timestamp : System.DateTime } + Timestamp : DateTime } [] type PlayerData = @@ -98,6 +99,48 @@ module Messaging = Message : string } + type DiscordContext = + | Interaction of InteractionContext + | Event of ComponentInteractionCreateEventArgs + + type IDiscordContext = + abstract member Respond : InteractionResponseType -> DiscordInteractionResponseBuilder -> Task + abstract member FollowUp : DiscordFollowupMessageBuilder -> Task + abstract member GetDiscordMember : unit -> DiscordMember + abstract member GetGuild : unit -> DiscordGuild + abstract member GetInteractionId : unit -> string + abstract member GetChannel : unit -> DiscordChannel + + type DiscordInteractionContext(ctx : InteractionContext) = + interface IDiscordContext with + member this.Respond responseType builder = + async { + do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask + } |> Async.StartAsTask :> Task + member this.FollowUp(builder) = + async { + do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore + } |> Async.StartAsTask :> Task + member this.GetDiscordMember() = ctx.Member + member this.GetGuild() = ctx.Guild + member this.GetInteractionId() = string ctx.InteractionId + member this.GetChannel() = ctx.Channel + + type DiscordEventContext(ctx : ComponentInteractionCreateEventArgs) = + interface IDiscordContext with + member this.Respond responseType builder = + async { + do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask + } |> Async.StartAsTask :> Task + member this.FollowUp(builder) = + async { + do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore + } |> Async.StartAsTask :> Task + member this.GetDiscordMember() = ctx.User :?> DiscordMember + member this.GetGuild() = ctx.Guild + member this.GetInteractionId() = ctx.Id + member this.GetChannel() = ctx.Channel + let getTimeText isCooldown (timespan : TimeSpan) timestamp = let span = if isCooldown @@ -116,82 +159,46 @@ module Messaging = let minutesRemaining = if remaining.Hours = 0 then remaining.Minutes + 1 else remaining.Minutes $"{hours}{minutesRemaining}min" - let sendSimpleResponse (ctx: InteractionContext) msg = + let sendSimpleResponse (ctx: IDiscordContext) msg = async { let builder = DiscordInteractionResponseBuilder() builder.Content <- msg builder.AsEphemeral true |> ignore - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask + do! ctx.Respond InteractionResponseType.ChannelMessageWithSource builder |> Async.AwaitTask } - let sendFollowUpMessage (event : ComponentInteractionCreateEventArgs) msg = + let sendFollowUpMessage (ctx : IDiscordContext) msg = async { let builder = DiscordFollowupMessageBuilder() builder.IsEphemeral <- true builder.Content <- msg - do! event.Interaction.CreateFollowupMessageAsync(builder) - |> Async.AwaitTask - |> Async.Ignore + do! ctx.FollowUp(builder) |> Async.AwaitTask } - let sendFollowUpMessageFromCtx (ctx : InteractionContext) msg = - async { - let builder = DiscordFollowupMessageBuilder() - builder.IsEphemeral <- true - builder.Content <- msg - do! ctx.FollowUpAsync(builder) - |> Async.AwaitTask - |> Async.Ignore - } - - let sendFollowUpMessageWithEmbed (event : ComponentInteractionCreateEventArgs) (embed : DiscordEmbed) msg = - async { - let builder = - DiscordFollowupMessageBuilder() - .AsEphemeral(true) - .WithContent(msg) - .AddEmbed(embed) - do! event.Interaction.CreateFollowupMessageAsync(builder) - |> Async.AwaitTask - |> Async.Ignore - } - - let sendFollowUpMessageWithButton (event : ComponentInteractionCreateEventArgs) interactiveMessage = + let sendFollowUpMessageWithButton (ctx : IDiscordContext) interactiveMessage = async { let builder = DiscordFollowupMessageBuilder() let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText) :> DiscordComponent builder.AddComponents [| button |] |> ignore builder.IsEphemeral <- true builder.Content <- interactiveMessage.Message - do! event.Interaction.CreateFollowupMessageAsync(builder) - |> Async.AwaitTask - |> Async.Ignore + do! ctx.FollowUp(builder) |> Async.AwaitTask } - let updateMessageWithGreyedOutButtons (event : ComponentInteractionCreateEventArgs) interactiveMessage = + let updateMessageWithGreyedOutButtons (ctx : IDiscordContext) interactiveMessage = async { let builder = DiscordInteractionResponseBuilder() let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText, true) :> DiscordComponent builder.AddComponents [| button |] |> ignore builder.IsEphemeral <- true builder.Content <- interactiveMessage.Message - do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) - |> Async.AwaitTask - } - - let sendInteractionEvent (event : ComponentInteractionCreateEventArgs) msg = - async { - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- msg - do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask + do! ctx.Respond InteractionResponseType.UpdateMessage builder |> Async.AwaitTask } let handleResultWithResponse ctx fn (player : Result) = match player with | Ok p -> fn p - | Error e -> async { do! sendFollowUpMessageFromCtx ctx e } + | Error e -> async { do! sendFollowUpMessage ctx e } let handleResultWithResponseFromEvent event fn (player : Result) = match player with