Store for food and accessory items, read stats

This commit is contained in:
Joseph Ferano 2022-03-01 21:48:44 +07:00
parent 05cdb98bee
commit 8a57d3305e
10 changed files with 237 additions and 38 deletions

View File

@ -78,14 +78,16 @@ let tryFindPlayer (discordId : uint64) = async {
SELECT discord_id, display_name, gbt, inventory, strength, focus, charisma, luck FROM "user"
WHERE discord_id = @did
"""
|> Sql.executeAsync (fun read -> {
|> Sql.executeAsync (fun read ->
let inv = read.intArray "inventory"
{
DiscordId = read.string "discord_id" |> uint64
Name = read.string "display_name"
Bank = read.int "gbt" * 1<GBT>
Inventory = read.intArray "inventory" |> Array.toList
Inventory = inv |> Array.toList
Strength = read.int "strength"
Focus = read.int "focus"
Charisma = read.int "charm"
Charisma = read.int "charisma"
Luck = read.int "luck"
})
|> Async.AwaitTask

View File

@ -33,7 +33,7 @@ let constructButtons (actionId: string) (buttonInfo : string) (player: PlayerDat
|> List.map (fun item ->
let action =
player.Events
|> Array.tryFind (fun i ->
|> List.tryFind (fun i ->
match i.Type with
| Hacking h -> h.HackId = item.Id && h.IsInstigator
| Shielding id -> id = item.Id
@ -101,7 +101,7 @@ let responseSuccessfulHack earnedMoney (targetId : uint64) amountTaken (hack : H
let responseCreatedShield (shield : ShieldItem) =
let embed = DiscordEmbedBuilder().WithImageUrl(getItemGif shield.Id)
embed.Title <- "Mounted Shield"
embed.Description <- $"Mounted {shield.Name} shield for {TimeSpan.FromMinutes(int shield.Cooldown).Hours} hours"
embed.Description <- $"Mounted {shield.Name} shield for {TimeSpan.FromMinutes(int shield.Cooldown).TotalHours} hours"
DiscordFollowupMessageBuilder()
.AddEmbed(embed)

View File

@ -21,7 +21,6 @@ module Inventory =
Class = hackClass
Cooldown = cooldown
}
let itemToShield item hackClass cooldown = {
Id = item.Id
Name = item.Name
@ -29,31 +28,48 @@ module Inventory =
Class = hackClass
Cooldown = cooldown
}
let itemToFood item targetStat boostAmount = {
Id = item.Id
Name = item.Name
Price = item.Price
TargetStat = targetStat
BoostAmount = boostAmount
}
let hackToItem (hack : HackItem) = {
Id = hack.Id
Name = hack.Name
Price = hack.Price
Type = Hack (hack.Power, hack.Class, hack.Cooldown)
Details = Hack (hack.Power, hack.Class, hack.Cooldown)
}
let shieldToItem (shield : ShieldItem) = {
Id = shield.Id
Name = shield.Name
Price = shield.Price
Type = Shield (shield.Class, shield.Cooldown)
Details = Shield (shield.Class, shield.Cooldown)
}
let filterByHacks inventory =
inventory |> List.filter (fun item -> match item.Type with Hack _ -> true | _ -> false)
inventory |> List.filter (fun item -> match item.Details with Hack _ -> true | _ -> false)
let filterByShields inventory =
inventory |> List.filter (fun item -> match item.Type with Shield _ -> true | _ -> false)
inventory |> List.filter (fun item -> match item.Details with Shield _ -> true | _ -> false)
let filterByWeapons inventory =
inventory |> List.filter (fun item -> match item.Details with Hack _ | Shield _ -> true | _ -> false)
let filterByFood inventory =
inventory |> List.filter (fun item -> match item.Details with Food _ -> true | _ -> false)
let filterByAccessories inventory =
inventory |> List.filter (fun item -> match item.Details with Accessory _ -> true | _ -> false)
let getHackItems inventory =
inventory
|> List.choose (fun item -> match item.Type with Hack (p,cl,co) -> Some (itemToHack item p cl co) | _ -> None)
|> List.choose (fun item -> match item.Details with Hack (p,cl,co) -> Some (itemToHack item p cl co) | _ -> None)
|> List.sortBy (fun item -> item.Id)
let getShieldItems inventory =
inventory
|> List.choose (fun item -> match item.Type with Shield (cl,co) -> Some (itemToShield item cl co) | _ -> None)
|> List.choose (fun item -> match item.Details with Shield (cl,co) -> Some (itemToShield item cl co) | _ -> None)
|> List.sortBy (fun item -> item.Id)
let getFoodItems inventory =
inventory
|> List.choose (fun item -> match item.Details with Food(t,b) -> Some (itemToFood item t b) | _ -> None)
|> List.sortBy (fun item -> item.Id)
let findItemById id inventory = inventory |> List.find (fun item -> item.Id = id)
let tryFindItemById id inventory = inventory |> List.tryFind (fun item -> item.Id = id)
@ -61,6 +77,8 @@ module Inventory =
inventory |> getHackItems |> List.pick (fun item -> if item.Id = id then Some item else None)
let findShieldById id inventory =
inventory |> getShieldItems |> List.pick (fun item -> if item.Id = id then Some item else None)
let findFoodById id inventory =
inventory |> getFoodItems |> List.pick (fun item -> if item.Id = id then Some item else None)
let tryFindHackById id inventory =
inventory |> getHackItems |> List.tryFind (fun item -> item.Id = id)
let tryFindShieldById id inventory =
@ -70,14 +88,14 @@ module WeaponClass =
let SameTargetAttackCooldown = System.TimeSpan.FromHours(1)
let getClassButtonColor item =
match item.Type with
match item.Details with
| Hack (_,0,_) | Shield (0,_) -> ButtonStyle.Danger
| Hack (_,1,_) | Shield (1,_) -> ButtonStyle.Primary
| Hack (_,2,_) | Shield (2,_) -> ButtonStyle.Success
| _ -> ButtonStyle.Primary
let getClassEmbedColor item =
match item.Type with
match item.Details with
| Hack (_,0,_) | Shield (0,_) -> DiscordColor.Red
| Hack (_,1,_) | Shield (1,_) -> DiscordColor.Blurple
| Hack (_,2,_) | Shield (2,_) -> DiscordColor.Green
@ -120,8 +138,8 @@ module PlayerStats =
// let hoursElapsed = (DateTime.UtcNow - lastRead).Hours
// let totalDecay = float hoursElapsed * statConfig.BaseDecayRate
let modMinMax =
let min = items |> List.sumBy (fun item -> match item.Type with | Accessory(_,floorBoost,_) -> floorBoost | _ -> 0)
let max = items |> List.sumBy (fun item -> match item.Type with | Accessory(_,_,ceilBoost) -> ceilBoost | _ -> 0)
let min = items |> List.sumBy (fun item -> match item.Details with | Accessory(_,floorBoost,_) -> floorBoost | _ -> 0)
let max = items |> List.sumBy (fun item -> match item.Details with | Accessory(_,_,ceilBoost) -> ceilBoost | _ -> 0)
Range.create (statConfig.BaseRange.Min + min) (statConfig.BaseRange.Max + max)
let amountAfterDecay = modMinMax |> Range.constrain amount
{ Id = statId ; Amount = amountAfterDecay ; ModRange = modMinMax ; LastRead = DateTime.UtcNow }

View File

@ -24,12 +24,16 @@ type ItemId =
| Firewall = 6
| Encryption = 7
| Cypher = 8
| ProteinPowder = 12
| ToroLoco = 13
| Cigs = 14
| MoonPie = 15
type StatId =
| Strength = 0
| Focus = 1
| Luck = 2
| Charisma = 3
| Charisma = 2
| Luck = 3
type StatConfig = {
Id : StatId
@ -112,7 +116,14 @@ type AccessoryItem = {
CeilBoost : int
}
[<RequireQualifiedAccess>]
type ItemType =
| Hack
| Shield
| Food
| Accessory
type ItemDetails =
| Hack of power : int * hackClass : int * cooldown : int<mins>
| Shield of shieldClass : int * cooldown : int<mins>
| Food of targetStat : StatId * boostAmount : int
@ -121,7 +132,8 @@ and Item = {
Id : int
Name : string
Price : int<GBT>
Type : ItemType
// Type : ItemType
Details : ItemDetails
}
and Inventory = Item list
and PlayerData = {

View File

@ -19,7 +19,7 @@ let checkAlreadyHackedTarget defender attacker =
defender
|> Player.removeExpiredActions
|> fun d -> d.Events
|> Array.tryFind (fun event ->
|> List.tryFind (fun event ->
match event.Type with
| Hacking h -> h.Adversary.Id = attacker.DiscordId && h.IsInstigator = false
| _ -> false)
@ -32,7 +32,7 @@ let checkAlreadyHackedTarget defender attacker =
let checkWeaponHasCooldown (weapon : Item) attacker =
attacker.Events
|> Array.tryFind (fun a ->
|> List.tryFind (fun a ->
match a.Type with
| Hacking h -> h.HackId = weapon.Id && h.IsInstigator
| Shielding id -> id = weapon.Id
@ -56,9 +56,9 @@ let checkPlayerOwnsWeapon (item : Item) player =
let checkPlayerHasShieldSlotsAvailable player =
let updatedPlayer = player |> Player.removeExpiredActions
let defenses = Player.getShieldEvents updatedPlayer
match defenses |> Array.length >= 3 with
match defenses |> List.length >= 3 with
| true ->
let event = defenses |> Array.rev |> Array.head // This should be the next expiring timestamp
let event = defenses |> List.rev |> List.head // This should be the next expiring timestamp
let cooldown = getTimeText true (TimeSpan.FromMinutes(int event.Cooldown)) event.Timestamp
Error $"You are only allowed three shields at a time. Wait {cooldown} to add another shield"
| false -> Ok updatedPlayer
@ -72,16 +72,16 @@ let runHackerBattle defender (hack : HackItem) =
defender
|> Player.removeExpiredActions
|> fun p -> p.Events
|> Array.choose (fun event ->
|> List.choose (fun event ->
match event.Type with
| Shielding id -> defender.Inventory |> Inventory.getShieldItems |> List.find (fun item -> item.Id = id) |> Some
| _ -> None)
|> Array.map (fun shield -> if hack.Class = shield.Class then Weak else Strong)
|> Array.contains Weak
|> List.map (fun shield -> if hack.Class = shield.Class then Weak else Strong)
|> List.contains Weak
let updateCombatants successfulHack (attacker : PlayerData) (defender : PlayerData) (hack : HackItem) prize =
let updatePlayer amount attack p =
{ p with Events = Array.append [| attack |] p.Events ; Bank = max (p.Bank + amount) 0<GBT> }
{ p with Events = attack::p.Events ; Bank = max (p.Bank + amount) 0<GBT> }
let event isDefenderEvent =
let hackEvent = {
HackId = hack.Id

View File

@ -15,7 +15,7 @@ let getBuyItemsEmbed (playerInventory : Inventory) (storeInventory : Inventory)
storeInventory
|> List.map (fun item ->
let embed = DiscordEmbedBuilder()
match item.Type with
match item.Details with
| Hack(power,_,cooldown) ->
embed.AddField($"$GBT Reward |", string power, true)
.AddField("Cooldown |", $"{TimeSpan.FromMinutes(int cooldown).Minutes} minutes", true)
@ -27,7 +27,15 @@ let getBuyItemsEmbed (playerInventory : Inventory) (storeInventory : Inventory)
.AddField("Active For |", $"{TimeSpan.FromMinutes(int cooldown).Hours} hours", true)
.WithThumbnail(Embeds.getItemIcon item.Id)
|> ignore
| _ -> ()
| Food(targetStat, boostAmount) ->
embed.AddField($"Stat |", $"{targetStat}", true)
.AddField($"Amount |", $"+{boostAmount}", true) |> ignore
| Accessory(targetStat, floorBoost, ceilBoost) ->
embed.AddField($"Stat |", $"{targetStat}", true) |> ignore
if floorBoost > 0 then
embed.AddField($"Min Boost |", $"+{floorBoost}", true) |> ignore
if ceilBoost > 0 then
embed.AddField($"Max Boost |", $"+{ceilBoost}", true) |> ignore
embed
.AddField("Price 💰", (if item.Price = 0<GBT> then "Free" else $"{item.Price} $GBT"), true)
.WithColor(WeaponClass.getClassEmbedColor item)
@ -62,6 +70,26 @@ let getSellEmbed (items : Item list) =
.AddComponents(buttons)
.AsEphemeral(true)
let getConsumeEmbed (items : Item 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.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
DiscordFollowupMessageBuilder()
.AddEmbeds(embeds)
.AddComponents(buttons)
.AsEphemeral(true)
let checkHasSufficientFunds (item : Item) player =
if player.Bank - item.Price >= 0<GBT>
then Ok player
@ -127,12 +155,24 @@ let handleSell (ctx : IDiscordContext) itemId =
do!
[ DbService.updatePlayer updatedPlayer |> Async.Ignore
DbService.removeShieldEvent updatedPlayer.DiscordId itemId |> Async.Ignore
sendFollowUpMessage ctx $"Sold {item.Type} {item.Name} for {item.Price}! Current Balance: {updatedPlayer.Bank}" ]
sendFollowUpMessage ctx $"Sold {item.Details} {item.Name} for {item.Price}! Current Balance: {updatedPlayer.Bank}" ]
|> Async.Parallel
|> Async.Ignore
})
})
//let inventory (ctx : IDiscordContext) =
// executePlayerAction ctx (fun player -> async {
// player.Inventory
// |> List.groupBy (fun item -> item.Details)
// })
//
//
//let consume (ctx : IDiscordContext) =
// executePlayerAction ctx (fun player -> async {
//
// })
let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) =
let ctx = DiscordEventContext event :> IDiscordContext
let id = ctx.GetInteractionId()
@ -160,15 +200,38 @@ type Store() =
do! Messaging.sendSimpleResponse ctx msg
}
[<SlashCommand("buy-hack", "Purchase a hack attack you can use to earn GoodBoyTokenz")>]
member _.BuyHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy Inventory.filterByHacks)
let checkChannel (ctx : IDiscordContext) =
match ctx.GetChannel().Id with
| id when id = GuildEnvironment.channelBackAlley -> buy Inventory.filterByHacks ctx
| id when id = GuildEnvironment.channelArmory -> buy Inventory.filterByShields ctx
| id when id = GuildEnvironment.channelMarket -> buy Inventory.filterByFood ctx
| id when id = GuildEnvironment.channelAccessoryShop -> buy Inventory.filterByAccessories ctx
| _ ->
task {
let msg = $"This channel doesn't have any items to sell"
do! Messaging.sendSimpleResponse ctx msg
}
[<SlashCommand("buy-item", "Purchase an item")>]
member _.BuyItem (ctx : InteractionContext) = checkChannel (DiscordInteractionContext(ctx))
[<SlashCommand("buy-shield", "Purchase a hack shield so you can protect your GoodBoyTokenz")>]
member this.BuyShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy Inventory.filterByShields)
[<SlashCommand("buy-food", "Purchase a food item to help boost your stats")>]
member this.BuyFood (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy Inventory.filterByFood)
[<SlashCommand("sell-hack", "Sell a hack for GoodBoyTokenz")>]
member this.SellHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell "Hacks" Inventory.filterByHacks)
[<SlashCommand("sell-shield", "Sell a shield for GoodBoyTokenz")>]
member this.SellShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" Inventory.filterByShields)
[<SlashCommand("consume", "Consume a food item")>]
member this.Consume (ctx : InteractionContext) =
enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" Inventory.filterByShields)
[<SlashCommand("inventory", "Check your inventory")>]
member this.Inventory (ctx : InteractionContext) =
enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" Inventory.filterByShields)

View File

@ -72,7 +72,7 @@ let checkVictimStealingCooldown defender attacker =
defender
|> Player.removeExpiredActions
|> fun p -> p.Events
|> Array.tryFind (fun e ->
|> List.tryFind (fun e ->
match e.Type with Stealing _ -> true | _ -> false)
|> function
| Some act ->
@ -95,7 +95,7 @@ let checkThiefCooldown attacker =
attacker
|> Player.removeExpiredActions
|> fun p -> p.Events
|> Array.tryFind (fun pe -> match pe.Type with Stealing(instigator, _) -> instigator | _ -> false)
|> List.tryFind (fun pe -> match pe.Type with Stealing(instigator, _) -> instigator | _ -> false)
|> function
| Some act ->
let cooldown = ThiefCooldown - (DateTime.UtcNow - act.Timestamp)

View File

@ -12,7 +12,7 @@ let Sensei = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" }
let defaultHack = Armory.weapons |> Inventory.findHackById (int ItemId.Virus)
let defaultShield = Armory.weapons |> Inventory.findShieldById (int ItemId.Firewall)
let TrainerEvents = [|
let TrainerEvents = [
{ Timestamp = System.DateTime.UtcNow
Cooldown = defaultHack.Cooldown
Type = Hacking {
@ -23,7 +23,7 @@ let TrainerEvents = [|
{ Timestamp = System.DateTime.UtcNow
Cooldown = defaultShield.Cooldown
Type = Shielding defaultShield.Id }
|]
]
let sendInitialEmbed (client : DiscordClient) =
async {
@ -173,7 +173,7 @@ let handleArsenal (ctx : IDiscordContext) =
let updatedPlayer =
if not hasStockWeapons then {
Player.removeExpiredActions player with
Events = TrainerEvents |> Array.append player.Events
Events = TrainerEvents @ player.Events
Inventory = Inventory.hackToItem defaultHack::Inventory.shieldToItem defaultShield::player.Inventory
}
else

View File

@ -21,7 +21,11 @@ let tokenStore = getVar "TOKEN_STORE"
let channelEventsHackerBattle = getId "CHANNEL_EVENTS_HACKER_BATTLE"
let channelTraining = getId "CHANNEL_TRAINING"
let channelArmory = getId "CHANNEL_ARMORY"
let channelBackAlley = getId "CHANNEL_BACKALLEY"
let channelBattle = getId "CHANNEL_BATTLE"
let channelMarket = getId "CHANNEL_MARKET"
let channelAccessoryShop = getId "CHANNEL_ACCESSORIES"
//let channelThievery = getId "CHANNEL_THIEVERY"
let botIdHackerBattle = getId "BOT_HACKER_BATTLE"
let botIdArmory = getId "BOT_ARMORY"

View File

@ -73,5 +73,105 @@
380
]
}
},
{
"Id": 12,
"Name": "Protein Powder",
"Price": 50,
"Type": {
"Case": "Food",
"Fields": [
0,
30
]
}
},
{
"Id": 13,
"Name": "Toro Loco",
"Price": 50,
"Type": {
"Case": "Food",
"Fields": [
1,
30
]
}
},
{
"Id": 14,
"Name": "Cigarettes",
"Price": 50,
"Type": {
"Case": "Food",
"Fields": [
2,
30
]
}
},
{
"Id": 15,
"Name": "Moon Pie",
"Price": 50,
"Type": {
"Case": "Food",
"Fields": [
3,
30
]
}
},
{
"Id": 20,
"Name": "Kettle Bell",
"Price": 250,
"Type": {
"Case": "Accessory",
"Fields": [
0,
25,
0
]
}
},
{
"Id": 21,
"Name": "Headphones",
"Price": 250,
"Type": {
"Case": "Accessory",
"Fields": [
1,
25,
0
]
}
},
{
"Id": 22,
"Name": "Silk Shirt",
"Price": 250,
"Type": {
"Case": "Accessory",
"Fields": [
2,
0,
25
]
}
},
{
"Id": 23,
"Name": "Buddha Keychain",
"Price": 250,
"Type": {
"Case": "Accessory",
"Fields": [
3,
0,
25
]
}
}
]