module Degenz.Store open System open System.Threading.Tasks open DSharpPlus.Entities open DSharpPlus open DSharpPlus.EventArgs open DSharpPlus.SlashCommands open Degenz open Degenz.Messaging open Degenz.PlayerInteractions let getBuyItemsEmbed (playerInventory : Inventory) (storeInventory : Inventory) = let embeds , buttons = storeInventory |> List.map (fun item -> let embed = DiscordEmbedBuilder() match item.Attributes with | CanBuy price -> embed.AddField("Price 💰", (if price = 0 then "Free" else $"{price} $GBT"), true) |> ignore | CanAttack power -> let title = match item.Type with ItemType.Hack -> "$GBT Reward" | _ -> "Power" embed.AddField($"{title} |", string power, true) |> ignore | CanExpire time -> let title = match item.Type with ItemType.Hack -> "Cooldown" | ItemType.Shield -> "Active For" | _ -> "Expires" let ts = TimeSpan.FromMinutes(int time) let timeStr = if ts.Hours = 0 then $"{ts.Minutes} mins" else $"{ts.Hours} hours" embed.AddField($"{title} |", timeStr, true) |> ignore | CanConsume effects | CanPassive effects -> let fx = effects |> List.map (fun f -> match f.Effect with | Min i -> $"Min - {f.TargetStat}" | Max i -> $"Max - {f.TargetStat}" | Booster i -> let str = if i > 0 then "Boost" else "Penalty" $"{str} - {f.TargetStat}" | RateMultiplier i -> $"Multiplier - {f.TargetStat}") |> String.concat "\n" embed.AddField($"Effect - Amount |", $"{fx}", true) |> ignore | _ -> () embed .WithColor(WeaponClass.getClassEmbedColor item) .WithThumbnail(Embeds.getItemIcon item.Id) .WithTitle($"{item.Name}") |> ignore let button = if playerInventory |> List.exists (fun i -> i.Id = item.Id) then DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Buy-{item.Id}", $"Own {item.Name}", true) else DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Buy-{item.Id}", $"Buy {item.Name}") ( embed.Build() , button :> DiscordComponent )) |> List.unzip DiscordFollowupMessageBuilder() .AddEmbeds(embeds) .AddComponents(buttons) .AsEphemeral(true) let getSellEmbed (items : Inventory) = let embeds , buttons = items |> List.choose (fun item -> match item.Attributes with | CanSell price -> let builder = DiscordEmbedBuilder() .AddField("Sell For 💰", $"{price} $GBT", true) .WithTitle($"{item.Name}") .WithColor(WeaponClass.getClassEmbedColor item) .Build() let button = DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Sell-{item.Id}", $"Sell {item.Name}") :> DiscordComponent Some ( builder , button ) | _ -> None) |> List.unzip // TODO: We should alert the user that they have no sellable items DiscordFollowupMessageBuilder() .AddEmbeds(embeds) .AddComponents(buttons) .AsEphemeral(true) let checkHasSufficientFunds (item : Item) player = match item.Attributes with | CanBuy price -> if player.Bank - price >= 0 then Ok player else Error $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT" | _ -> Error $"{item.Name} item cannot be bought" let checkAlreadyOwnsItem (item : Item) player = if player.Inventory |> List.exists (fun w -> item.Id = w.Id) then Error $"You already own {item.Name}!" else Ok player let checkSoldItemAlready item player = if player.Inventory |> List.exists (fun i -> item.Id = i.Id) then Ok player else Error $"{item.Name} not found in your inventory! Looks like you sold it already." let checkHasItemsInArsenal itemType items player = if List.isEmpty items |> not then Ok player else Error $"You currently have no {itemType} in your arsenal to sell!" let buy itemType (ctx : IDiscordContext) = executePlayerAction ctx (fun player -> async { let playerItems = Inventory.getItemsByType itemType player.Inventory let armoryItems = Inventory.getItemsByType itemType Armory.weapons let itemStore = getBuyItemsEmbed playerItems armoryItems do! ctx.FollowUp itemStore |> Async.AwaitTask do! Analytics.buyWeaponCommand (ctx.GetDiscordMember()) itemType }) let sell itemType getItems (ctx : IDiscordContext) = executePlayerAction ctx (fun player -> async { let items = getItems player.Inventory match checkHasItemsInArsenal itemType items player with | Ok _ -> let itemStore = getSellEmbed items do! ctx.FollowUp(itemStore) |> Async.AwaitTask | Error e -> do! sendFollowUpMessage ctx e do! Analytics.sellWeaponCommand (ctx.GetDiscordMember()) itemType }) // TODO: When you buy a shield, prompt the user to activate it let handleBuyItem (ctx : IDiscordContext) itemId = executePlayerAction ctx (fun player -> async { let item = Armory.weapons |> Inventory.findItemById itemId do! player |> checkHasSufficientFunds item >>= checkAlreadyOwnsItem item |> handleResultWithResponse ctx (fun player -> async { let price = match item.Attributes with CanBuy price -> price | _ -> 0 let newBalance = player.Bank - price let p = { player with Bank = newBalance ; Inventory = item::player.Inventory } do! DbService.updatePlayer p |> Async.Ignore do! sendFollowUpMessage ctx $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining" do! Analytics.buyWeaponButton (ctx.GetDiscordMember()) item.Name price }) }) let handleSell (ctx : IDiscordContext) itemId = executePlayerAction ctx (fun player -> async { let item = Armory.weapons |> Inventory.findItemById itemId do! player |> checkSoldItemAlready item |> handleResultWithResponse ctx (fun player -> async { match item.Attributes with | CanSell price -> let updatedPlayer = { player with Bank = player.Bank + price Inventory = player.Inventory |> List.filter (fun i -> i.Id <> itemId) } do! [ DbService.updatePlayer updatedPlayer |> Async.Ignore DbService.removeShieldEvent updatedPlayer.DiscordId itemId |> Async.Ignore sendFollowUpMessage ctx $"Sold {item.Name} for {price}! Current Balance: {updatedPlayer.Bank}" Analytics.sellWeaponButton (ctx.GetDiscordMember()) item price ] |> Async.Parallel |> Async.Ignore | _ -> () }) }) let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) = 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 {id}" do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask } let showInventoryEmbed (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction ctx (fun player -> async { let embeds , buttons = player.Inventory |> List.map (fun item -> let embed = DiscordEmbedBuilder() match item.Attributes with | CanBuy price -> embed.AddField("Price 💰", (if price = 0 then "Free" else $"{price} $GBT"), true) |> ignore | CanAttack power -> let title = match item.Type with ItemType.Hack -> "$GBT Reward" | _ -> "Power" embed.AddField($"{title} |", string power, true) |> ignore | CanExpire time -> let title = match item.Type with ItemType.Hack -> "Cooldown" | ItemType.Shield -> "Active For" | _ -> "Expires" let ts = TimeSpan.FromMinutes(int time) let timeStr = if ts.Hours = 0 then $"{ts.Minutes} mins" else $"{ts.Hours} hours" embed.AddField($"{title} |", timeStr, true) |> ignore | CanConsume effects | CanPassive effects -> let fx = effects |> List.map (fun f -> match f.Effect with | Min i -> $"Min - {f.TargetStat}" | Max i -> $"Max - {f.TargetStat}" | Booster i -> let str = if i > 0 then "Boost" else "Penalty" $"{str} - {f.TargetStat}" | RateMultiplier i -> $"Multiplier - {f.TargetStat}") |> String.concat "\n" embed.AddField($"Effect - Amount |", $"{fx}", true) |> ignore | _ -> () embed .WithColor(WeaponClass.getClassEmbedColor item) .WithThumbnail(Embeds.getItemIcon item.Id) .WithTitle($"{item.Name}") |> ignore let button = // if playerInventory |> List.exists (fun i -> i.Id = item.Id) // then DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Buy-{item.Id}", $"Own {item.Name}", true) // else DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Buy-{item.Id}", $"Buy {item.Name}") DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Sell-{item.Id}", $"Sell {item.Name}") ( embed.Build() , button :> DiscordComponent )) |> List.unzip let builder = DiscordFollowupMessageBuilder() .AddEmbeds(embeds) // .AddComponents(buttons) .AsEphemeral(true) do! ctx.FollowUp builder |> Async.AwaitTask }) let showStats (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction ctx (fun player -> async { let embed = DiscordEmbedBuilder() PlayerStats.stats |> List.iter (fun statConfig -> let playerStat = PlayerStats.getPlayerStat statConfig player // let diffSymbol lhs rhs = if lhs < rhs then "-" elif lhs = rhs then "" else "+" let min = match statConfig.BaseRange.Min = playerStat.ModRange.Min with | true -> $"{statConfig.BaseRange.Min}" | false -> $"{statConfig.BaseRange.Min} (+{playerStat.ModRange.Min}) " let max = match statConfig.BaseRange.Max = playerStat.ModRange.Max with | true -> $"{statConfig.BaseRange.Max}" | false -> $"{statConfig.BaseRange.Max} (+{playerStat.ModRange.Max}) " let field = $"{min} |---------------| {max}" embed.AddField(string statConfig.Id , field) |> ignore) let builder = DiscordFollowupMessageBuilder() .AddEmbed(embed) .AsEphemeral(true) do! ctx.FollowUp builder |> Async.AwaitTask }) type Store() = 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 } // let checkChannel (ctx : IDiscordContext) (storeFn : IDiscordContext -> Task) = let checkChannel (ctx : IDiscordContext) = match ctx.GetChannel().Id with | id when id = GuildEnvironment.channelBackAlley -> buy ItemType.Hack ctx | id when id = GuildEnvironment.channelArmory -> buy ItemType.Shield ctx | id when id = GuildEnvironment.channelMarket -> buy ItemType.Food ctx | id when id = GuildEnvironment.channelAccessoryShop -> buy ItemType.Accessory ctx | _ -> task { let msg = $"This channel doesn't have any items to sell. Try <#{GuildEnvironment.channelArmory}>" do! Messaging.sendSimpleResponse ctx msg } [] member _.BuyItem (ctx : InteractionContext) = checkChannel (DiscordInteractionContext ctx) [] member _.BuyHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy ItemType.Hack) [] member this.BuyShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy ItemType.Shield) [] member this.SellHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell "Hacks" (Inventory.getItemsByType ItemType.Hack)) [] member this.SellShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" (Inventory.getItemsByType ItemType.Shield)) [] member this.Consume (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext ctx) (sell "Food" (Inventory.getItemsByType ItemType.Food)) [] member this.Inventory (ctx : InteractionContext) = showInventoryEmbed (DiscordInteractionContext ctx) [] member this.Stats (ctx : InteractionContext) = showStats (DiscordInteractionContext ctx)