module Degenz.Store open System.Threading.Tasks open DSharpPlus.Entities open DSharpPlus open DSharpPlus.EventArgs open DSharpPlus.SlashCommands open Degenz open Degenz.Embeds open Degenz.Messaging let checkHasSufficientFunds (item : BattleItem) player = if player.Bank - item.Cost >= 0 then Ok player else Error $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT" let checkAlreadyOwnsItem (item : BattleItem) player = if player.Arsenal |> Array.exists (fun w -> item.Id = w.Id) then Error $"You already own {item.Name}!" else Ok player let checkSoldItemAlready (item : BattleItem) player = if player.Arsenal |> Array.exists (fun w -> item.Id = w.Id) then Ok player else Error $"{item.Name} not found in your arsenal! Looks like you sold it already." let checkHasItemsInArsenal itemType player = if player.Arsenal |> Array.filter (fun i -> i.Type = itemType ) |> Array.length > 0 then Ok player else Error $"You currently have no {itemType}s in your arsenal to sell!" let battleItemFormat (items : BattleItem array) = match items with | [||] -> "None" | _ -> items |> Array.toList |> List.map (fun i -> i.Name) |> String.concat ", " let actionFormat (actions : Action array) = match actions with | [||] -> "None" | _ -> let hacks , defenses = actions |> Array.partition (fun act -> match act.Type with Attack _ -> true | Defense -> false) let hacks = hacks |> Array.take (min hacks.Length 10) hacks |> Array.append defenses |> Array.map (fun act -> let item = Armory.getItem act.ActionId match act.Type with | Attack atk -> let cooldown = getTimeText false Game.SameTargetAttackCooldown act.Timestamp $"Hacked {atk.Target.Name} {cooldown} ago" | Defense -> let cooldown = getTimeText true (System.TimeSpan.FromMinutes(int item.Cooldown)) act.Timestamp $"{item.Name} Shield active for {cooldown}") |> String.concat "\n" let statusFormat p = $"**Hacks:** {Player.hacks p |> battleItemFormat}\n **Shields:** {Player.getShields p |> battleItemFormat}\n **Hack Attacks:**\n{Player.attacks p |> actionFormat}\n **Active Shields:**\n{Player.defenses p |> actionFormat}" // TODO: There's a 1000 character limit for embeds, so you need to filter by N last actions let arsenal (ctx : InteractionContext) = Game.executePlayerInteraction ctx (fun player -> async { let updatedPlayer = Player.removeExpiredActions false player let builder = DiscordFollowupMessageBuilder() let embed = DiscordEmbedBuilder() embed.AddField("Arsenal", statusFormat updatedPlayer) |> ignore builder.AddEmbed(embed) |> ignore builder.IsEphemeral <- true do! ctx.FollowUpAsync(builder) |> Async.AwaitTask |> Async.Ignore do! DbService.updatePlayer updatedPlayer }) let buy itemType (ctx : InteractionContext) = Game.executePlayerInteraction ctx (fun player -> async { let itemStore = Embeds.getBuyItemsEmbed player itemType Armory.battleItems do! ctx.Interaction.CreateFollowupMessageAsync(itemStore) |> Async.AwaitTask |> Async.Ignore }) let sell itemType (ctx : InteractionContext) = Game.executePlayerInteraction 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 }) // TODO: When you buy a shield, prompt the user to activate it let handleBuyItem (event : ComponentInteractionCreateEventArgs) itemId = Game.executePlayerEvent event (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 { 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" }) }) let handleSell (event : ComponentInteractionCreateEventArgs) itemId = Game.executePlayerEvent event (fun player -> async { let item = Armory.getItem itemId do! player |> checkSoldItemAlready item |> handleResultWithResponseFromEvent event (fun player -> async { let updatedPlayer = { player with Bank = player.Bank + item.Cost Arsenal = player.Arsenal |> Array.filter (fun i -> i.Id <> itemId) Actions = if item.Type = ItemType.Shield then player.Actions |> Array.filter (fun a -> a.ActionId <> itemId) else player.Actions } do! DbService.updatePlayer updatedPlayer do! sendFollowUpMessage event $"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 | _ -> task { let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true builder.Content <- $"Incorrect Action identifier {event.Id}" do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask } type Store() = inherit ApplicationCommandModule () let enforceChannel (ctx : InteractionContext) (storeFn : InteractionContext -> Task) = match ctx.Channel.Id with | id when id = GuildEnvironment.channelArmory -> storeFn ctx | _ -> task { let msg = $"You must go to <#{GuildEnvironment.channelArmory}> channel to buy/sell or check your arsenal" do! Messaging.sendSimpleResponse ctx msg } [] member this.Arsenal (ctx : InteractionContext) = arsenal ctx [] member _.BuyHack (ctx : InteractionContext) = enforceChannel ctx (buy ItemType.Hack) [] member this.BuyShield (ctx : InteractionContext) = enforceChannel ctx (buy ItemType.Shield) [] member this.SellHack (ctx : InteractionContext) = enforceChannel ctx (sell ItemType.Hack) [] member this.SellShield (ctx : InteractionContext) = enforceChannel ctx (sell ItemType.Shield)