From 8a57d3305ebdaea87e69bc9561217046abd97187 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Tue, 1 Mar 2022 21:48:44 +0700 Subject: [PATCH] Store for food and accessory items, read stats --- Bot/DbService.fs | 8 +-- Bot/Embeds.fs | 4 +- Bot/GameHelpers.fs | 40 +++++++++++---- Bot/GameTypes.fs | 18 +++++-- Bot/Games/HackerBattle.fs | 16 +++--- Bot/Games/Store.fs | 73 +++++++++++++++++++++++++-- Bot/Games/Thief.fs | 4 +- Bot/Games/Trainer.fs | 6 +-- Bot/GuildEnvironment.fs | 4 ++ Bot/Items.json | 102 +++++++++++++++++++++++++++++++++++++- 10 files changed, 237 insertions(+), 38 deletions(-) diff --git a/Bot/DbService.fs b/Bot/DbService.fs index 0402e58..31de54e 100644 --- a/Bot/DbService.fs +++ b/Bot/DbService.fs @@ -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 - 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 diff --git a/Bot/Embeds.fs b/Bot/Embeds.fs index c2a88f2..a27dbfd 100644 --- a/Bot/Embeds.fs +++ b/Bot/Embeds.fs @@ -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) diff --git a/Bot/GameHelpers.fs b/Bot/GameHelpers.fs index 466be95..a80bdea 100644 --- a/Bot/GameHelpers.fs +++ b/Bot/GameHelpers.fs @@ -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 } diff --git a/Bot/GameTypes.fs b/Bot/GameTypes.fs index c3cb71a..8794e15 100644 --- a/Bot/GameTypes.fs +++ b/Bot/GameTypes.fs @@ -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 } +[] type ItemType = + | Hack + | Shield + | Food + | Accessory + +type ItemDetails = | Hack of power : int * hackClass : int * cooldown : int | Shield of shieldClass : int * cooldown : int | Food of targetStat : StatId * boostAmount : int @@ -121,7 +132,8 @@ and Item = { Id : int Name : string Price : int - Type : ItemType +// Type : ItemType + Details : ItemDetails } and Inventory = Item list and PlayerData = { diff --git a/Bot/Games/HackerBattle.fs b/Bot/Games/HackerBattle.fs index 2acbad3..62e983e 100644 --- a/Bot/Games/HackerBattle.fs +++ b/Bot/Games/HackerBattle.fs @@ -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 } + { p with Events = attack::p.Events ; Bank = max (p.Bank + amount) 0 } let event isDefenderEvent = let hackEvent = { HackId = hack.Id diff --git a/Bot/Games/Store.fs b/Bot/Games/Store.fs index 37b37a8..a80b070 100644 --- a/Bot/Games/Store.fs +++ b/Bot/Games/Store.fs @@ -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 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 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 } - [] - 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 + } + + [] + member _.BuyItem (ctx : InteractionContext) = checkChannel (DiscordInteractionContext(ctx)) [] member this.BuyShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy Inventory.filterByShields) + [] + member this.BuyFood (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy Inventory.filterByFood) + [] member this.SellHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell "Hacks" Inventory.filterByHacks) [] member this.SellShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" Inventory.filterByShields) + [] + member this.Consume (ctx : InteractionContext) = + enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" Inventory.filterByShields) + + [] + member this.Inventory (ctx : InteractionContext) = + enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" Inventory.filterByShields) + diff --git a/Bot/Games/Thief.fs b/Bot/Games/Thief.fs index 47990e5..233cc7d 100644 --- a/Bot/Games/Thief.fs +++ b/Bot/Games/Thief.fs @@ -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) diff --git a/Bot/Games/Trainer.fs b/Bot/Games/Trainer.fs index 5a321fc..fe5d53f 100644 --- a/Bot/Games/Trainer.fs +++ b/Bot/Games/Trainer.fs @@ -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 diff --git a/Bot/GuildEnvironment.fs b/Bot/GuildEnvironment.fs index c968e81..0221b09 100644 --- a/Bot/GuildEnvironment.fs +++ b/Bot/GuildEnvironment.fs @@ -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" diff --git a/Bot/Items.json b/Bot/Items.json index 5ba6228..c99fd23 100644 --- a/Bot/Items.json +++ b/Bot/Items.json @@ -14,7 +14,7 @@ }, { "Id": 1, - "Name": "RemoteAccess", + "Name": "Remote Access", "Price": 500, "Type": { "Case": "Hack", @@ -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 + ] + } } ]