Refactor for new Item design

This commit is contained in:
Joseph Ferano 2022-04-27 09:53:27 +07:00
parent 7881acab4f
commit bf68d0ae4a
4 changed files with 162 additions and 204 deletions

View File

@ -108,19 +108,19 @@ let sellWeaponCommand (discordMember : DiscordMember) weaponType =
] ]
track "Sell Weapon Command Invoked" discordMember.Id data track "Sell Weapon Command Invoked" discordMember.Id data
let buyWeaponButton (discordMember : DiscordMember) (weapon : ItemDetails) = let buyWeaponButton (discordMember : DiscordMember) itemName itemPrice =
let data = [ let data = [
"user_display_name" , discordMember.Username "user_display_name" , discordMember.Username
"weapon_name" , weapon.Name "weapon_name" , itemName
"weapon_price" , string weapon.Price "weapon_price" , string itemPrice
] ]
track "Buy Weapon Button Clicked" discordMember.Id data track "Buy Weapon Button Clicked" discordMember.Id data
let sellWeaponButton (discordMember : DiscordMember) (weapon : ItemDetails) = let sellWeaponButton (discordMember : DiscordMember) (weapon : Item) price =
let data = [ let data = [
"user_display_name" , discordMember.Username "user_display_name" , discordMember.Username
"weapon_name" , weapon.Name "weapon_name" , weapon.Name
"weapon_price" , string weapon.Price "weapon_price" , string price
] ]
track "Sell Weapon Button Clicked" discordMember.Id data track "Sell Weapon Button Clicked" discordMember.Id data

View File

@ -7,39 +7,38 @@ open Degenz
open Newtonsoft.Json open Newtonsoft.Json
module Armory = module Armory =
let weapons : ItemDetails list = let weapons : Inventory =
let file = System.IO.File.ReadAllText("Items.json") let file = System.IO.File.ReadAllText("Items.json")
// let file = System.IO.File.ReadAllText("Bot/Items.json") // let file = System.IO.File.ReadAllText("Bot/Items.json")
JsonConvert.DeserializeObject<ItemDetails array>(file) JsonConvert.DeserializeObject<Inventory>(file)
|> Array.toList
module Inventory = module Inventory =
let getItemsByType itemType inventory = // let getItemsByType itemType inventory =
match itemType with // match itemType with
| ItemType.Hack -> inventory |> List.filter (fun item -> match item with Hack _ -> true | _ -> false) // | ItemType.Hack -> inventory |> List.filter (fun item -> match item with Hack _ -> true | _ -> false)
| ItemType.Shield -> inventory |> List.filter (fun item -> match item with Shield _ -> true | _ -> false) // | ItemType.Shield -> inventory |> List.filter (fun item -> match item with Shield _ -> true | _ -> false)
| ItemType.Food -> inventory |> List.filter (fun item -> match item with Food _ -> true | _ -> false) // | ItemType.Food -> inventory |> List.filter (fun item -> match item with Food _ -> true | _ -> false)
| ItemType.Accessory -> inventory |> List.filter (fun item -> match item with Accessory _ -> true | _ -> false) // | ItemType.Accessory -> inventory |> List.filter (fun item -> match item with Accessory _ -> true | _ -> false)
let findItemById id (inventory : Inventory) = inventory |> List.find (fun item -> item.Id = id) let findItemById id (inventory : Inventory) = inventory |> List.find (fun item -> item.Id = id)
let findHackById id inventory = // let findHackById id inventory =
inventory |> List.pick (fun item -> match item with | Hack h -> (if h.Item.Id = id then Some h else None) | _ -> None) // inventory |> List.pick (fun item -> match item with | Hack h -> (if h.Item.Id = id then Some h else None) | _ -> None)
let findShieldById id inventory = // let findShieldById id inventory =
inventory |> List.pick (fun item -> match item with | Shield s -> (if s.Item.Id = id then Some s else None) | _ -> None) // inventory |> List.pick (fun item -> match item with | Shield s -> (if s.Item.Id = id then Some s else None) | _ -> None)
let findFoodById id inventory = // let findFoodById id inventory =
inventory |> List.pick (fun item -> match item with | Food f -> (if f.Item.Id = id then Some f else None) | _ -> None) // inventory |> List.pick (fun item -> match item with | Food f -> (if f.Item.Id = id then Some f else None) | _ -> None)
let findAccessoryById id inventory = // let findAccessoryById id inventory =
inventory |> List.pick (fun item -> match item with | Accessory a -> (if a.Item.Id = id then Some a else None) | _ -> None) // inventory |> List.pick (fun item -> match item with | Accessory a -> (if a.Item.Id = id then Some a else None) | _ -> None)
//
let getHacks inventory = // let getHacks inventory =
inventory |> List.choose (fun item -> match item with | Hack h -> Some h | _ -> None) // inventory |> List.choose (fun item -> match item with | Hack h -> Some h | _ -> None)
let getShields inventory = // let getShields inventory =
inventory |> List.choose (fun item -> match item with | Shield s -> Some s | _ -> None) // inventory |> List.choose (fun item -> match item with | Shield s -> Some s | _ -> None)
let getFoods inventory = // let getFoods inventory =
inventory |> List.choose (fun item -> match item with | Food f -> Some f | _ -> None) // inventory |> List.choose (fun item -> match item with | Food f -> Some f | _ -> None)
let getAccessories inventory = // let getAccessories inventory =
inventory |> List.choose (fun item -> match item with | Accessory a -> Some a | _ -> None) // inventory |> List.choose (fun item -> match item with | Accessory a -> Some a | _ -> None)
module WeaponClass = module WeaponClass =
let SameTargetAttackCooldown = TimeSpan.FromHours(4) let SameTargetAttackCooldown = TimeSpan.FromHours(4)

View File

@ -88,90 +88,52 @@ type ItemType =
| Shield | Shield
| Food | Food
| Accessory | Accessory
type ItemAttributes = { type Effect =
CanBuy : bool | Min of int
CanSell : bool | Max of int
CanConsume : bool | Booster of int
CanTrade : bool | RateMultiplier of float
CanDrop : bool
type StatEffect = {
TargetStat : StatId
Effect : Effect
} }
type ItemAttribute =
| Buyable of price : int<GBT>
| Sellable of price : int<GBT>
| Expireable of lifetime : int<mins>
| Consumable of effects : StatEffect list
| Passive of effects : StatEffect list
| Droppable of chance : float
| Tradeable of yes : unit
| Attackable of power : int
| Defendable of resistance : int
| Classable of className : string
| Stackable of max : int
let (|CanBuy|_|) itemAttrs = itemAttrs |> List.tryPick (function Buyable p -> Some p | _ -> None)
let (|CanSell|_|) itemAttrs = itemAttrs |> List.tryPick (function Sellable p -> Some p | _ -> None)
let (|CanExpire|_|) itemAttrs = itemAttrs |> List.tryPick (function Expireable l -> Some l | _ -> None)
let (|CanConsume|_|) itemAttrs = itemAttrs |> List.tryPick (function Consumable es -> Some es | _ -> None)
let (|CanPassive|_|) itemAttrs = itemAttrs |> List.tryPick (function Passive es -> Some es | _ -> None)
let (|CanDrop|_|) itemAttrs = itemAttrs |> List.tryPick (function Droppable c -> Some c | _ -> None)
let (|CanTrade|_|) itemAttrs = itemAttrs |> List.tryPick (function Tradeable () -> Some () | _ -> None)
let (|CanAttack|_|) itemAttrs = itemAttrs |> List.tryPick (function Attackable p -> Some p | _ -> None)
let (|CanDefend|_|) itemAttrs = itemAttrs |> List.tryPick (function Defendable r -> Some r | _ -> None)
let (|CanClass|_|) itemAttrs = itemAttrs |> List.tryPick (function Classable c -> Some c | _ -> None)
let (|CanStack|_|) itemAttrs = itemAttrs |> List.tryPick (function Stackable m -> Some m | _ -> None)
type Item = { type Item = {
Id : int Id : int
Name : string Name : string
Price : int<GBT> Type : ItemType
MaxAllowed : int Attributes : ItemAttribute list
Attributes : ItemAttributes
} }
type HackItem = { type Inventory = Item list
Power : int
Class : int
Cooldown : int<mins>
Item : Item
}
type ShieldItem = {
Class : int
Cooldown : int<mins>
Item : Item
}
type FoodItem = {
TargetStat : StatId
BoostAmount : int
Item : Item
}
type AccessoryItem = {
TargetStat : StatId
FloorBoost : int
CeilBoost : int
Item : Item
}
type MeleeWeapon = {
BreakChance : float
Item : Item
}
type ItemDetails =
| Hack of HackItem
| Shield of ShieldItem
| Food of FoodItem
| Accessory of AccessoryItem
member this.Id =
match this with
| Hack i -> i.Item.Id
| Shield i -> i.Item.Id
| Food i -> i.Item.Id
| Accessory i -> i.Item.Id
member this.Name =
match this with
| Hack i -> i.Item.Name
| Shield i -> i.Item.Name
| Food i -> i.Item.Name
| Accessory i -> i.Item.Name
member this.Price =
match this with
| Hack i -> i.Item.Price
| Shield i -> i.Item.Price
| Food i -> i.Item.Price
| Accessory i -> i.Item.Price
member this.getItem =
match this with
| Hack i -> i.Item
| Shield i -> i.Item
| Food i -> i.Item
| Accessory i -> i.Item
static member getClass = function
| Hack i -> i.Class
| Shield i -> i.Class
| Food _ -> -1
| Accessory _ -> -1
type Inventory = ItemDetails list
type PlayerData = { type PlayerData = {
DiscordId : uint64 DiscordId : uint64
@ -195,3 +157,4 @@ with member this.toDiscordPlayer = { Id = this.DiscordId ; Name = this.Name }
// XP = 0 // XP = 0
Bank = 0<GBT> Bank = 0<GBT>
Active = false } Active = false }

View File

@ -15,30 +15,33 @@ let getBuyItemsEmbed (playerInventory : Inventory) (storeInventory : Inventory)
storeInventory storeInventory
|> List.map (fun item -> |> List.map (fun item ->
let embed = DiscordEmbedBuilder() let embed = DiscordEmbedBuilder()
match item with match item.Attributes with
| Hack hack -> | CanBuy price -> embed.AddField("Price 💰", (if price = 0<GBT> then "Free" else $"{price} $GBT"), true) |> ignore
embed.AddField($"$GBT Reward |", string hack.Power, true) | CanAttack power ->
.AddField("Cooldown |", $"{TimeSpan.FromMinutes(int hack.Cooldown).Minutes} minutes", true) let title = match item.Type with ItemType.Hack -> "$GBT Reward" | _ -> "Power"
.WithThumbnail(Embeds.getItemIcon item.Id) embed.AddField($"{title} |", string power, true) |> ignore
|> ignore | CanExpire time ->
| Shield shield -> let title = match item.Type with ItemType.Hack -> "Cooldown" | ItemType.Shield -> "Active For" | _ -> "Expires"
embed.AddField($"Strong against |", WeaponClass.getGoodAgainst shield.Class |> snd |> string, true) let ts = TimeSpan.FromMinutes(int time)
// .AddField($"Defensive Strength |", string item.Power, true) let timeStr = if ts.Hours = 0 then $"{ts.Minutes} mins" else $"{ts.Hours} hours"
.AddField("Active For |", $"{TimeSpan.FromMinutes(int shield.Cooldown).Hours} hours", true) embed.AddField($"{title} |", timeStr, true) |> ignore
.WithThumbnail(Embeds.getItemIcon item.Id) | CanConsume effects | CanPassive effects ->
|> ignore let fx =
| Food food -> effects
embed.AddField($"Stat |", $"{food.TargetStat}", true) |> List.map (fun f ->
.AddField($"Amount |", $"+{food.BoostAmount}", true) |> ignore match f.Effect with
| Accessory accessory -> | Min i -> $"Min - {f.TargetStat}"
embed.AddField($"Stat |", $"{accessory.TargetStat}", true) |> ignore | Max i -> $"Max - {f.TargetStat}"
if accessory.FloorBoost > 0 then | Booster i ->
embed.AddField($"Min Boost |", $"+{accessory.FloorBoost}", true) |> ignore let str = if i > 0 then "Boost" else "Penalty"
if accessory.CeilBoost > 0 then $"{str} - {f.TargetStat}"
embed.AddField($"Max Boost |", $"+{accessory.CeilBoost}", true) |> ignore | RateMultiplier i -> $"Multiplier - {f.TargetStat}")
|> String.concat "\n"
embed.AddField($"Effect - Amount |", $"{fx}", true) |> ignore
| _ -> ()
embed embed
.AddField("Price 💰", (if item.Price = 0<GBT> then "Free" else $"{item.Price} $GBT"), true)
.WithColor(WeaponClass.getClassEmbedColor item) .WithColor(WeaponClass.getClassEmbedColor item)
.WithThumbnail(Embeds.getItemIcon item.Id)
.WithTitle($"{item.Name}") .WithTitle($"{item.Name}")
|> ignore |> ignore
let button = let button =
@ -53,47 +56,36 @@ let getBuyItemsEmbed (playerInventory : Inventory) (storeInventory : Inventory)
.AddComponents(buttons) .AddComponents(buttons)
.AsEphemeral(true) .AsEphemeral(true)
let getSellEmbed (items : ItemDetails list) = let getSellEmbed (items : Inventory) =
let embeds , buttons = let embeds , buttons =
items items
|> List.map (fun item -> |> List.choose (fun item ->
DiscordEmbedBuilder() match item.Attributes with
.AddField("Sell For 💰", $"{item.Price} $GBT", true) | CanSell price ->
.WithTitle($"{item.Name}") let builder =
.WithColor(WeaponClass.getClassEmbedColor item) DiscordEmbedBuilder()
.Build() .AddField("Sell For 💰", $"{price} $GBT", true)
, DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Sell-{item.Id}", $"Sell {item.Name}") :> DiscordComponent) .WithTitle($"{item.Name}")
|> List.unzip .WithColor(WeaponClass.getClassEmbedColor item)
.Build()
DiscordFollowupMessageBuilder() let button = DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Sell-{item.Id}", $"Sell {item.Name}") :> DiscordComponent
.AddEmbeds(embeds) Some ( builder , button )
.AddComponents(buttons) | _ -> None)
.AsEphemeral(true)
let getConsumeEmbed (items : ItemDetails list) =
let embeds , buttons =
items
|> List.groupBy (fun item -> item.Id)
|> List.map (fun (itemId , items ) ->
let item = List.head items
let foodItem = Inventory.findFoodById itemId items
DiscordEmbedBuilder()
.AddField($"{foodItem.Item.Name}", $"Total {items.Length}\nBoosts {foodItem.TargetStat} +{foodItem.BoostAmount}", true)
.WithTitle($"Food Items")
.WithColor(WeaponClass.getClassEmbedColor item)
.Build()
, DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Sell-{id}", $"Sell {item.Name}") :> DiscordComponent)
|> List.unzip |> List.unzip
// TODO: We should alert the user that they have no sellable items
DiscordFollowupMessageBuilder() DiscordFollowupMessageBuilder()
.AddEmbeds(embeds) .AddEmbeds(embeds)
.AddComponents(buttons) .AddComponents(buttons)
.AsEphemeral(true) .AsEphemeral(true)
let checkHasSufficientFunds (item : Item) player = let checkHasSufficientFunds (item : Item) player =
if player.Bank - item.Price >= 0<GBT> match item.Attributes with
then Ok player | CanBuy price ->
else Error $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT" 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 checkAlreadyOwnsItem (item : Item) player = let checkAlreadyOwnsItem (item : Item) player =
if player.Inventory |> List.exists (fun w -> item.Id = w.Id) if player.Inventory |> List.exists (fun w -> item.Id = w.Id)
@ -134,14 +126,15 @@ let handleBuyItem (ctx : IDiscordContext) itemId =
executePlayerAction ctx (fun player -> async { executePlayerAction ctx (fun player -> async {
let item = Armory.weapons |> Inventory.findItemById itemId let item = Armory.weapons |> Inventory.findItemById itemId
do! player do! player
|> checkHasSufficientFunds item.getItem |> checkHasSufficientFunds item
>>= checkAlreadyOwnsItem item.getItem >>= checkAlreadyOwnsItem item
|> handleResultWithResponse ctx (fun player -> async { |> handleResultWithResponse ctx (fun player -> async {
let newBalance = player.Bank - item.Price let price = match item.Attributes with CanBuy price -> price | _ -> 0<GBT>
let newBalance = player.Bank - price
let p = { player with Bank = newBalance ; Inventory = item::player.Inventory } let p = { player with Bank = newBalance ; Inventory = item::player.Inventory }
do! DbService.updatePlayer p |> Async.Ignore do! DbService.updatePlayer p |> Async.Ignore
do! sendFollowUpMessage ctx $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining" do! sendFollowUpMessage ctx $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining"
do! Analytics.buyWeaponButton (ctx.GetDiscordMember()) item do! Analytics.buyWeaponButton (ctx.GetDiscordMember()) item.Name price
}) })
}) })
@ -150,20 +143,23 @@ let handleSell (ctx : IDiscordContext) itemId =
let item = Armory.weapons |> Inventory.findItemById itemId let item = Armory.weapons |> Inventory.findItemById itemId
do! do!
player player
|> checkSoldItemAlready item.getItem |> checkSoldItemAlready item
|> handleResultWithResponse ctx (fun player -> async { |> handleResultWithResponse ctx (fun player -> async {
let updatedPlayer = { match item.Attributes with
player with | CanSell price ->
Bank = player.Bank + item.Price let updatedPlayer = {
Inventory = player.Inventory |> List.filter (fun i -> i.Id <> itemId) player with
} Bank = player.Bank + price
do! Inventory = player.Inventory |> List.filter (fun i -> i.Id <> itemId)
[ DbService.updatePlayer updatedPlayer |> Async.Ignore }
DbService.removeShieldEvent updatedPlayer.DiscordId itemId |> Async.Ignore do!
sendFollowUpMessage ctx $"Sold {item.Name} for {item.Price}! Current Balance: {updatedPlayer.Bank}" [ DbService.updatePlayer updatedPlayer |> Async.Ignore
Analytics.sellWeaponButton (ctx.GetDiscordMember()) item ] DbService.removeShieldEvent updatedPlayer.DiscordId itemId |> Async.Ignore
|> Async.Parallel sendFollowUpMessage ctx $"Sold {item.Name} for {price}! Current Balance: {updatedPlayer.Bank}"
|> Async.Ignore Analytics.sellWeaponButton (ctx.GetDiscordMember()) item price ]
|> Async.Parallel
|> Async.Ignore
| _ -> ()
}) })
}) })
@ -187,33 +183,33 @@ let showInventoryEmbed (ctx : IDiscordContext) = PlayerInteractions.executePlaye
player.Inventory player.Inventory
|> List.map (fun item -> |> List.map (fun item ->
let embed = DiscordEmbedBuilder() let embed = DiscordEmbedBuilder()
match item with match item.Attributes with
| Hack hack -> | CanBuy price -> embed.AddField("Price 💰", (if price = 0<GBT> then "Free" else $"{price} $GBT"), true) |> ignore
embed.AddField($"$GBT Reward |", string hack.Power, true) | CanAttack power ->
.AddField("Cooldown |", $"{TimeSpan.FromMinutes(int hack.Cooldown).Minutes} minutes", true) let title = match item.Type with ItemType.Hack -> "$GBT Reward" | _ -> "Power"
.WithColor(DiscordColor.Red) embed.AddField($"{title} |", string power, true) |> ignore
.WithThumbnail(Embeds.getItemIcon item.Id) | CanExpire time ->
|> ignore let title = match item.Type with ItemType.Hack -> "Cooldown" | ItemType.Shield -> "Active For" | _ -> "Expires"
| Shield shield -> let ts = TimeSpan.FromMinutes(int time)
embed.AddField($"Strong against |", WeaponClass.getGoodAgainst shield.Class |> snd |> string, true) let timeStr = if ts.Hours = 0 then $"{ts.Minutes} mins" else $"{ts.Hours} hours"
// .AddField($"Defensive Strength |", string item.Power, true) embed.AddField($"{title} |", timeStr, true) |> ignore
.AddField("Active For |", $"{TimeSpan.FromMinutes(int shield.Cooldown).Hours} hours", true) | CanConsume effects | CanPassive effects ->
.WithColor(DiscordColor.SapGreen) let fx =
.WithThumbnail(Embeds.getItemIcon item.Id) effects
|> ignore |> List.map (fun f ->
| Food food -> match f.Effect with
embed.AddField($"Stat |", $"{food.TargetStat}", true) | Min i -> $"Min - {f.TargetStat}"
.WithColor(DiscordColor.Azure) | Max i -> $"Max - {f.TargetStat}"
.AddField($"Amount |", $"+{food.BoostAmount}", true) |> ignore | Booster i ->
| Accessory accessory -> let str = if i > 0 then "Boost" else "Penalty"
embed.AddField($"Stat |", $"{accessory.TargetStat}", true) $"{str} - {f.TargetStat}"
.WithColor(DiscordColor.Goldenrod) |> ignore | RateMultiplier i -> $"Multiplier - {f.TargetStat}")
if accessory.FloorBoost > 0 then |> String.concat "\n"
embed.AddField($"Min Boost |", $"+{accessory.FloorBoost}", true) |> ignore embed.AddField($"Effect - Amount |", $"{fx}", true) |> ignore
if accessory.CeilBoost > 0 then | _ -> ()
embed.AddField($"Max Boost |", $"+{accessory.CeilBoost}", true) |> ignore
embed embed
.AddField("Price 💰", (if item.Price = 0<GBT> then "Free" else $"{item.Price} $GBT"), true) .WithColor(WeaponClass.getClassEmbedColor item)
.WithThumbnail(Embeds.getItemIcon item.Id)
.WithTitle($"{item.Name}") .WithTitle($"{item.Name}")
|> ignore |> ignore
let button = let button =