173 lines
7.4 KiB
Forth
173 lines
7.4 KiB
Forth
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<GBT>
|
|
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
|
|
}
|
|
|
|
[<SlashCommand("arsenal", "Get the Hacks and Shields you own, and which ones are active")>]
|
|
member this.Arsenal (ctx : InteractionContext) = arsenal ctx
|
|
|
|
[<SlashCommand("buy-hack", "Purchase a hack attack you can use to earn GoodBoyTokenz")>]
|
|
member _.BuyHack (ctx : InteractionContext) = enforceChannel 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)
|
|
|
|
[<SlashCommand("sell-hack", "Sell a hack for GoodBoyTokenz")>]
|
|
member this.SellHack (ctx : InteractionContext) = enforceChannel ctx (sell ItemType.Hack)
|
|
|
|
[<SlashCommand("sell-shield", "Sell a shield for GoodBoyTokenz")>]
|
|
member this.SellShield (ctx : InteractionContext) = enforceChannel ctx (sell ItemType.Shield)
|
|
|