399 lines
19 KiB
Forth
399 lines
19 KiB
Forth
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 checkHasStock (item : StoreItem) player =
|
|
if item.Stock > 0 || item.LimitStock = false
|
|
then Ok player
|
|
else Error $"{item.Item.Name} is out of stock! Check back later to purchase"
|
|
|
|
let checkHasSufficientFunds (item : Item) player =
|
|
match item.Attributes with
|
|
| CanBuy price ->
|
|
if player.Bank - price >= 0<GBT>
|
|
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 checkDoesntExceedStackCap (item : Item) player =
|
|
let itemCount =
|
|
player.Inventory
|
|
|> List.countBy (fun i -> i.Id)
|
|
|> List.tryFind (fst >> ((=) item.Id))
|
|
|> Option.map snd
|
|
match item.Attributes , itemCount with
|
|
| CanStack max , Some count ->
|
|
if count >= max
|
|
then Error $"You own the maximum allowed amount {item.Name}!"
|
|
else Ok player
|
|
| _ , Some _ -> Error $"You already own this item"
|
|
| _ -> Ok player
|
|
|
|
let checkSoldItemAlready (item : 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 getItemEmbeds owned (items : StoreItem list) =
|
|
items
|
|
|> List.countBy (fun item -> item.Item.Id)
|
|
|> List.map (fun (id,count) -> items |> List.find (fun i -> i.Item.Id = id) , count )
|
|
|> List.map (fun (item,count) ->
|
|
let embed = DiscordEmbedBuilder()
|
|
if not owned && item.LimitStock then
|
|
embed.AddField("Stock", $"{item.Stock}", true) |> ignore
|
|
item.Item.Attributes
|
|
|> List.iter (function
|
|
| Buyable price -> embed.AddField("Price 💰", (if price = 0<GBT> then "Free" else $"{price} $GBT"), true) |> ignore
|
|
| Attackable power ->
|
|
let title = match item.Item.Type with ItemType.Hack -> "$GBT Reward" | _ -> "Power"
|
|
embed.AddField($"{title} |", string power, true) |> ignore
|
|
| RateLimitable time ->
|
|
let title = match item.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
|
|
| Stackable max ->
|
|
if owned then
|
|
embed.AddField($"Total Owned |", $"{count}", true) |> ignore
|
|
else
|
|
embed.AddField($"Max Allowed |", $"{max}", true) |> ignore
|
|
| Modifiable effects ->
|
|
let fx =
|
|
effects
|
|
|> List.map (fun f ->
|
|
match f.Effect with
|
|
| Min i -> $"{f.TargetStat} Min + {i}"
|
|
| Max i -> $"{f.TargetStat} Max + {i}"
|
|
| Add i ->
|
|
let str = if i > 0 then "Boost" else "Penalty"
|
|
$"{f.TargetStat} {str} + {i}"
|
|
| RateMultiplier i -> $"{f.TargetStat} Multiplier - i")
|
|
|> String.concat "\n"
|
|
embed.AddField($"Effect - Amount ", $"{fx}", true) |> ignore
|
|
| _ -> ())
|
|
embed
|
|
.WithColor(WeaponClass.getClassEmbedColor item.Item)
|
|
.WithTitle($"{item.Item.Name}")
|
|
|> ignore
|
|
if String.IsNullOrWhiteSpace(item.Item.IconUrl)
|
|
then embed
|
|
else embed.WithThumbnail(item.Item.IconUrl))
|
|
|> List.map (fun e -> e.Build())
|
|
|> Seq.ofList
|
|
|
|
let getBuyItemsEmbed storeId (playerInventory : Inventory) (storeInventory : StoreItem list) =
|
|
let embeds = getItemEmbeds false storeInventory
|
|
let buttons =
|
|
storeInventory
|
|
|> List.map (fun item ->
|
|
let owned = playerInventory |> List.exists (fun i -> i.Id = item.Item.Id)
|
|
let inStock = item.Available && (item.Stock > 0 || item.LimitStock = false)
|
|
match owned , inStock with
|
|
| false , true -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}-{storeId}", $"Buy {item.Item.Name}")
|
|
| false , false -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}-{storeId}", $"{item.Item.Name} (Out of Stock)", true)
|
|
| true , _ -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}-{storeId}", $"Own {item.Item.Name}", true)
|
|
:> DiscordComponent)
|
|
|
|
let builder =
|
|
DiscordFollowupMessageBuilder()
|
|
.AddEmbeds(embeds)
|
|
.AsEphemeral(true)
|
|
buttons
|
|
|> List.chunkBySize 5
|
|
|> List.iter (fun btns -> builder.AddComponents(btns) |> ignore)
|
|
builder
|
|
|
|
let purchaseItemEmbed (item : Item) =
|
|
let embed = DiscordEmbedBuilder()
|
|
embed.ImageUrl <- item.IconUrl
|
|
embed.Title <- $"Purchased {item.Name}"
|
|
match item.Type with
|
|
| ItemType.Jpeg ->
|
|
if item.Id.Contains "RAFFLE" then
|
|
embed.Description <- $"Congratulations! You are in the draw for the {item.Name}. The winner will be announced shortly"
|
|
embed.ImageUrl <- item.Description
|
|
else
|
|
embed.Description <- $"Congratulations! You own the rights to the {item.Name} NFT. Please create a ticket in the support channel and we will transfer to your wallet"
|
|
| _ -> embed.Description <- $"Purchased {item.Name}"
|
|
embed
|
|
|
|
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 showJpegsEmbed (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction ctx (fun player -> async {
|
|
let jpegs =
|
|
player.Inventory
|
|
|> Inventory.getItemsByType ItemType.Jpeg
|
|
|> List.map (fun i -> { StoreId = "BACKALLEY" ; Item = i ; Stock = 1 ; LimitStock = false ; Available = true })
|
|
let embeds = getItemEmbeds true jpegs
|
|
let builder = DiscordFollowupMessageBuilder().AddEmbeds(embeds).AsEphemeral(true)
|
|
do! ctx.FollowUp builder |> Async.AwaitTask
|
|
})
|
|
|
|
let buy storeId (filterBy : ItemType option) (ctx : IDiscordContext) =
|
|
executePlayerAction ctx (fun player -> async {
|
|
try
|
|
let! items = DbService.getStoreItems storeId
|
|
if items.Length > 0 then
|
|
let items' =
|
|
match filterBy with
|
|
| Some itemType -> items |> List.filter (fun item -> item.Item.Type = itemType)
|
|
| None -> items
|
|
let itemStore = getBuyItemsEmbed storeId player.Inventory items'
|
|
do! ctx.FollowUp itemStore |> Async.AwaitTask
|
|
do! Analytics.buyItemCommand (ctx.GetDiscordMember()) storeId
|
|
else
|
|
do! Messaging.sendFollowUpMessage ctx "This channel doesn't have anything to sell"
|
|
with ex -> printfn $"{ex.Message}"
|
|
})
|
|
|
|
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.buyItemCommand (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 storeId = ctx.GetInteractionId().Split("-").[2]
|
|
let! storeInventory = DbService.getStoreItems storeId
|
|
let storeItem = storeInventory |> List.find (fun si -> si.Item.Id = itemId)
|
|
let item = storeInventory |> List.map (fun i -> i.Item) |> Inventory.findItemById itemId
|
|
do! player
|
|
|> checkHasSufficientFunds item
|
|
>>= checkHasStock storeItem
|
|
>>= checkDoesntExceedStackCap item
|
|
|> handleResultWithResponse ctx (fun player -> async {
|
|
let price = match item.Attributes with CanBuy price -> price | _ -> 0<GBT>
|
|
do! DbService.updatePlayerCurrency -price player.DiscordId |> Async.Ignore
|
|
do! DbService.addToPlayerInventory player.DiscordId item |> Async.Ignore
|
|
if storeItem.LimitStock = true && storeItem.Stock > 0 then
|
|
do! DbService.decrementItemStock item |> Async.Ignore
|
|
let builder = DiscordFollowupMessageBuilder().AsEphemeral(true)
|
|
builder.AddEmbed(purchaseItemEmbed (item)) |> ignore
|
|
do! ctx.FollowUp builder |> Async.AwaitTask
|
|
do! Analytics.buyWeaponButton (ctx.GetDiscordMember()) item.Name price
|
|
})
|
|
})
|
|
|
|
let handleSell (ctx : IDiscordContext) itemId =
|
|
executePlayerAction ctx (fun player -> async {
|
|
let item = player.Inventory |> Inventory.findItemById itemId
|
|
do!
|
|
player
|
|
|> checkSoldItemAlready item
|
|
|> handleResultWithResponse ctx (fun player -> async {
|
|
match item.Attributes with
|
|
| CanSell price ->
|
|
do!
|
|
[ DbService.updatePlayerCurrency price player.DiscordId |> Async.Ignore
|
|
DbService.removeFromPlayerInventory player.DiscordId item |> Async.Ignore
|
|
DbService.removeShieldEvent player.DiscordId itemId |> Async.Ignore
|
|
sendFollowUpMessage ctx $"Sold {item.Name} for {price}! New Balance: {player.Bank + price}"
|
|
Analytics.sellWeaponButton (ctx.GetDiscordMember()) item price ]
|
|
|> Async.Parallel
|
|
|> Async.Ignore
|
|
| _ -> ()
|
|
})
|
|
})
|
|
|
|
let consume (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction ctx (fun player -> async {
|
|
match player.Inventory |> Inventory.getFoods with
|
|
| [] -> do! Messaging.sendFollowUpMessage ctx "You do not have any items to consume"
|
|
| items ->
|
|
// TODO: You have to provide the correct store id here
|
|
let items' = items |> List.map (fun i -> { StoreId = i.Id ; Item = i ; Stock = 1 ; LimitStock = false ; Available = true })
|
|
let embeds = getItemEmbeds true items'
|
|
let builder = DiscordFollowupMessageBuilder().AddEmbeds(embeds).AsEphemeral(true)
|
|
do! ctx.FollowUp builder |> Async.AwaitTask
|
|
})
|
|
|
|
let handleConsume (ctx : IDiscordContext) itemId = PlayerInteractions.executePlayerAction ctx (fun player -> async {
|
|
let item = player.Inventory |> Inventory.findItemById itemId
|
|
match player.Inventory |> Inventory.getFoods with
|
|
| [] -> do! Messaging.sendFollowUpMessage ctx "You do not have any items to consume"
|
|
| items ->
|
|
let items' = items |> List.map (fun i -> { StoreId = i.Id ; Item = i ; Stock = 1 ; LimitStock = false ; Available = true })
|
|
let embeds = getItemEmbeds true items'
|
|
let builder = DiscordFollowupMessageBuilder().AddEmbeds(embeds).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 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
|
|
})
|
|
|
|
let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) =
|
|
let ctx = DiscordEventContext event :> IDiscordContext
|
|
let id = ctx.GetInteractionId()
|
|
let itemId = id.Split("-").[1]
|
|
let storeId = id.Split("-").[2]
|
|
match id with
|
|
| id when id.StartsWith("Buy") -> handleBuyItem ctx itemId
|
|
| id when id.StartsWith("Sell") -> handleSell ctx itemId
|
|
| id when id.StartsWith("Consume") -> handleConsume ctx itemId
|
|
| id when id.StartsWith("ShowJpegInventory") -> buy storeId None ctx
|
|
| id when id.StartsWith("ShowHacks") -> buy storeId (Some ItemType.Hack) ctx
|
|
| id when id.StartsWith("ShowShields") -> buy storeId (Some ItemType.Shield) ctx
|
|
| _ ->
|
|
task {
|
|
let builder = DiscordInteractionResponseBuilder()
|
|
builder.IsEphemeral <- true
|
|
builder.Content <- $"Incorrect Action identifier {id}"
|
|
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
|
|
}
|
|
|
|
let sendBackalleyEmbed (ctx : IDiscordContext) =
|
|
async {
|
|
try
|
|
let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelBackAlley)
|
|
let builder = DiscordMessageBuilder()
|
|
let embed = DiscordEmbedBuilder()
|
|
embed.ImageUrl <- "https://s7.gifyu.com/images/ezgif.com-gif-maker-23203b9dca779ba7cf.gif"
|
|
embed.Title <- "Jpeg Alley"
|
|
embed.Color <- DiscordColor.Black
|
|
embed.Description <- "Hey, what do you want kid? Did you come alone?"
|
|
builder.AddEmbed embed |> ignore
|
|
let button = DiscordButtonComponent(ButtonStyle.Success, $"ShowJpegInventory-0-BACKALLEY", $"Show me your stash") :> DiscordComponent
|
|
builder.AddComponents [| button |] |> ignore
|
|
|
|
do! GuildEnvironment.botClientStore.Value.SendMessageAsync(channel, builder)
|
|
|> Async.AwaitTask
|
|
|> Async.Ignore
|
|
with e ->
|
|
printfn $"Error trying to get channel Jpeg Alley\n\n{e.Message}"
|
|
} |> Async.RunSynchronously
|
|
|
|
let sendArmoryEmbed (ctx : IDiscordContext) =
|
|
async {
|
|
try
|
|
let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelArmory)
|
|
let builder = DiscordMessageBuilder()
|
|
let embed = DiscordEmbedBuilder()
|
|
embed.ImageUrl <- "https://s10.gifyu.com/images/ezgif.com-gif-maker-1696ae238f96d4dfc1.gif"
|
|
embed.Title <- "Armory"
|
|
embed.Color <- DiscordColor.Black
|
|
embed.Description <- "Buy Shields to protect yourself or buy Hacks to extract $GBT from others"
|
|
builder.AddEmbed embed |> ignore
|
|
let btn1 = DiscordButtonComponent(ButtonStyle.Success, $"ShowHacks-0-ARMORY", $"Hacks") :> DiscordComponent
|
|
let btn2 = DiscordButtonComponent(ButtonStyle.Success, $"ShowShields-0-ARMORY", $"Shields") :> DiscordComponent
|
|
builder.AddComponents [| btn1 ; btn2 |] |> ignore
|
|
|
|
do! GuildEnvironment.botClientStore.Value.SendMessageAsync(channel, builder)
|
|
|> Async.AwaitTask
|
|
|> Async.Ignore
|
|
with e ->
|
|
printfn $"Error trying to get channel Armory\n\n{e.Message}"
|
|
} |> Async.RunSynchronously
|
|
|
|
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
|
|
// }
|
|
|
|
// [<SlashCommand("buy-item", "Purchase an item")>]
|
|
// member _.BuyItem (ctx : InteractionContext) = buy (DiscordInteractionContext ctx)
|
|
|
|
// [<SlashCommand("buy-hack", "Purchase a hack so you can take money from other Degenz")>]
|
|
// member _.BuyHack (ctx : InteractionContext) =
|
|
// enforceChannel (DiscordInteractionContext(ctx)) buy
|
|
//
|
|
// [<SlashCommand("buy-shield", "Purchase a hack shield so you can protect your GBT")>]
|
|
// member this.BuyShield (ctx : InteractionContext) =
|
|
// enforceChannel (DiscordInteractionContext(ctx)) buy
|
|
|
|
[<SlashCommand("sell-hack", "Sell a hack for GoodBoyTokenz")>]
|
|
member this.SellHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell "Hacks" (Inventory.getItemsByType ItemType.Hack))
|
|
|
|
[<SlashCommand("sell-shield", "Sell a shield for GoodBoyTokenz")>]
|
|
member this.SellShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" (Inventory.getItemsByType ItemType.Shield))
|
|
|
|
// [<SlashCommand("consume", "Consume a food item")>]
|
|
// member this.Consume (ctx : InteractionContext) = consume (DiscordInteractionContext ctx)
|
|
//
|
|
[<SlashCommand("jpegs", "Check your inventory")>]
|
|
member this.Inventory (ctx : InteractionContext) =
|
|
showJpegsEmbed (DiscordInteractionContext ctx)
|
|
|
|
// [<SlashCommand("stats", "Check your stats")>]
|
|
// member this.Stats (ctx : InteractionContext) =
|
|
// showStats (DiscordInteractionContext ctx)
|
|
|