Create IDiscordContext abstraction to simplify code
This commit is contained in:
parent
d563303f90
commit
dd33492418
@ -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
|
||||
|
28
Bot/Game.fs
28
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<GBT>
|
||||
@ -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<unit>) =
|
||||
let executePlayerAction (ctx : IDiscordContext) (dispatch : PlayerData -> Async<unit>) =
|
||||
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<unit>) =
|
||||
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)
|
||||
|
@ -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>(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() =
|
||||
|
||||
[<SlashCommand("arsenal", "Get the Hacks and Shields you own, and which ones are active")>]
|
||||
member this.Arsenal (ctx : InteractionContext) =
|
||||
enforceChannels ctx (Trainer.handleArsenal) arsenal
|
||||
enforceChannels (DiscordInteractionContext ctx) (Trainer.handleArsenal) arsenal
|
||||
|
||||
[<SlashCommand("hack", "Send a hack attack to another player")>]
|
||||
member this.AttackCommand (ctx : InteractionContext, [<Option("target", "The player you want to hack")>] target : DiscordUser) =
|
||||
enforceChannels ctx (Trainer.attack target) (attack target)
|
||||
enforceChannels (DiscordInteractionContext ctx) (Trainer.attack target) (attack target)
|
||||
|
||||
[<SlashCommand("shield", "Create a passive shield that will protect you for a certain time")>]
|
||||
member this.ShieldCommand (ctx : InteractionContext) =
|
||||
enforceChannels ctx Trainer.defend defend
|
||||
enforceChannels (DiscordInteractionContext ctx) Trainer.defend defend
|
||||
|
||||
// [<SlashCommand("test-autocomplete", "Create a passive defense that will last 24 hours")>]
|
||||
member this.TestAutoComplete (ctx : InteractionContext) =
|
||||
|
@ -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() =
|
||||
|
||||
[<SlashCommand("spin", "Want to try your luck?")>]
|
||||
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)]
|
||||
|
66
Bot/Store.fs
66
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() =
|
||||
}
|
||||
|
||||
[<SlashCommand("buy-hack", "Purchase a hack attack you can use to earn GoodBoyTokenz")>]
|
||||
member _.BuyHack (ctx : InteractionContext) = enforceChannel ctx (buy ItemType.Hack)
|
||||
member _.BuyHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy ItemType.Hack)
|
||||
|
||||
[<SlashCommand("buy-shield", "Purchase a hack shield so you can protect your GoodBoyTokenz")>]
|
||||
member this.BuyShield (ctx : InteractionContext) = enforceChannel ctx (buy ItemType.Shield)
|
||||
member this.BuyShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy ItemType.Shield)
|
||||
|
||||
[<SlashCommand("sell-hack", "Sell a hack for GoodBoyTokenz")>]
|
||||
member this.SellHack (ctx : InteractionContext) = enforceChannel ctx (sell ItemType.Hack)
|
||||
member this.SellHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell ItemType.Hack)
|
||||
|
||||
[<SlashCommand("sell-shield", "Sell a shield for GoodBoyTokenz")>]
|
||||
member this.SellShield (ctx : InteractionContext) = enforceChannel ctx (sell ItemType.Shield)
|
||||
member this.SellShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell ItemType.Shield)
|
||||
|
||||
|
104
Bot/Trainer.fs
104
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<ShieldId>(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"
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
||||
[<CLIMutable>]
|
||||
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<PlayerData, string>) =
|
||||
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<PlayerData, string>) =
|
||||
match player with
|
||||
|
Loading…
x
Reference in New Issue
Block a user