Add DB interactions for new item/store system. Improvements to attributes

This commit is contained in:
Joseph Ferano 2022-04-28 17:43:50 +07:00
parent bf68d0ae4a
commit f26d51701d
8 changed files with 348 additions and 227 deletions

View File

@ -1,11 +1,94 @@
module Degenz.DbService module Degenz.DbService
open System open System
open Npgsql
open Npgsql.FSharp open Npgsql.FSharp
open Degenz open Degenz
let connStr = GuildEnvironment.connectionString let connStr = GuildEnvironment.connectionString
type StatMod = { mod_type :string ; target_stat : string ; mod_amount : float }
NpgsqlConnection.GlobalTypeMapper.MapComposite<StatMod>("stat_mod") |> ignore
let readItem (reader : RowReader) =
let convertStatMod { mod_type = modType ; target_stat = targetStat; mod_amount = modAmount } =
let fx =
match modType with
| "Min" -> Min (int modAmount)
| "Max" -> Max (int modAmount)
| "RateMultiplier" -> RateMultiplier (modAmount)
| "Booster" -> Add (int modAmount)
| _ -> Add (int modAmount)
let ( _ , stat ) = StatId.TryParse(targetStat)
{ TargetStat = stat ; Effect = fx }
{ Item.Id = reader.int "id"
Item.Name = reader.string "name"
Item.Type =
match reader.string "category" with
| "Hack" -> ItemType.Hack
| "Shield" -> ItemType.Shield
| "Food" -> ItemType.Food
| "Accessory" -> ItemType.Accessory
| _ -> ItemType.Accessory
Item.Attributes = [
reader.intOrNone "buy_price" |> Option.map (fun a -> Buyable (a * 1<GBT>))
reader.intOrNone "sell_price" |> Option.map (fun a -> Sellable (a * 1<GBT>))
reader.intOrNone "expiration" |> Option.map (fun a -> Expireable (a * 1<mins>))
reader.floatOrNone "drop_chance" |> Option.map (float >> Droppable)
reader.intOrNone "attack_power" |> Option.map Attackable
reader.intOrNone "defense_power" |> Option.map Defendable
reader.stringOrNone "class_name" |> Option.map Classable
reader.intOrNone "max_stack" |> Option.map Stackable
if reader.bool "can_trade" then Some Tradeable else None
if reader.bool "can_consume" then Some Consumable else None
(match reader.fieldValue<StatMod array> "mods" with
| [||] -> None
| mods -> mods |> Array.map convertStatMod |> Array.toList |> Modifiable |> Some)
] |> List.choose id
}
let getPlayerInventory (did : uint64) =
connStr
|> Sql.connect
|> Sql.parameters [ "did", Sql.string (string did) ]
|> Sql.query """
SELECT ii.id,name,category,buy_price,sell_price,rate_limit,expiration,drop_chance,can_trade,can_consume,
attack_power,defense_power,class_name,max_stack,mods
FROM inventory_item
JOIN item ii on inventory_item.item_id = ii.id
JOIN "user" usr on inventory_item.user_id = usr.id
WHERE usr.discord_id = @did;
"""
|> Sql.executeAsync readItem
|> Async.AwaitTask
let addToPlayerInventory (did : uint64) (item : Item) =
connStr
|> Sql.connect
|> Sql.parameters [ ( "@did" , Sql.string (string did) ) ; ( "iid" , Sql.int item.Id )]
|> Sql.query """
INSERT INTO inventory_item (item_id, user_id)
VALUES (@iid, (SELECT id FROM "user" WHERE discord_id = @did));
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
let getStoreItems (channelId : uint64) =
connStr
|> Sql.connect
|> Sql.parameters [ "cid", Sql.string (string channelId) ]
|> Sql.query """
SELECT i.id,name,category,buy_price,sell_price,rate_limit,expiration,drop_chance,can_trade,can_consume,
attack_power,defense_power,class_name,max_stack,mods
FROM store_item
JOIN store st on store_item.store_id = st.id
JOIN item i on store_item.item_id = i.id
WHERE channel_id = @cid AND available;
"""
|> Sql.executeAsync readItem
|> Async.AwaitTask
let getPlayerEvents (did : uint64) = let getPlayerEvents (did : uint64) =
connStr connStr
|> Sql.connect |> Sql.connect
@ -59,28 +142,25 @@ let tryFindPlayer (discordId : uint64) = async {
|> Sql.connect |> Sql.connect
|> Sql.parameters [ "did", Sql.string (string discordId) ] |> Sql.parameters [ "did", Sql.string (string discordId) ]
|> Sql.query """ |> Sql.query """
SELECT discord_id, display_name, gbt, in_game, inventory, strength, focus, charisma, luck FROM "user" SELECT discord_id, display_name, gbt, in_game, strength, focus, charisma, luck FROM "user"
WHERE discord_id = @did 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"
DiscordId = read.string "discord_id" |> uint64 Bank = read.intOrNone "gbt" |> Option.map ((*) 1<GBT>) |> Option.defaultValue 0<GBT>
Name = read.string "display_name" Strength = read.intOrNone "strength" |> Option.defaultValue 0
Bank = read.intOrNone "gbt" |> Option.map ((*) 1<GBT>) |> Option.defaultValue 0<GBT> Focus = read.intOrNone "focus" |> Option.defaultValue 0
Inventory = inv |> Array.toList Charisma = read.intOrNone "charisma" |> Option.defaultValue 0
Strength = read.intOrNone "strength" |> Option.defaultValue 0 Luck = read.intOrNone "luck" |> Option.defaultValue 0
Focus = read.intOrNone "focus" |> Option.defaultValue 0 Active = read.bool "in_game"
Charisma = read.intOrNone "charisma" |> Option.defaultValue 0
Luck = read.intOrNone "luck" |> Option.defaultValue 0
Active = read.bool "in_game"
|}) |})
|> Async.AwaitTask |> Async.AwaitTask
match List.tryHead user with match List.tryHead user with
| None -> return None | None -> return None
| Some u -> | Some u ->
let! events = getPlayerEvents u.DiscordId let! events = getPlayerEvents u.DiscordId
let inventory = u.Inventory |> List.choose (fun id -> Armory.weapons |> List.tryFind (fun item -> item.Id = id)) let! inventory = getPlayerInventory discordId
let strength = PlayerStats.calculateActiveStat StatId.Strength u.Strength inventory let strength = PlayerStats.calculateActiveStat StatId.Strength u.Strength inventory
let focus = PlayerStats.calculateActiveStat StatId.Focus u.Focus inventory let focus = PlayerStats.calculateActiveStat StatId.Focus u.Focus inventory
let charisma = PlayerStats.calculateActiveStat StatId.Charisma u.Charisma inventory let charisma = PlayerStats.calculateActiveStat StatId.Charisma u.Charisma inventory
@ -116,14 +196,12 @@ let updatePlayer (player : PlayerData) =
|> Sql.parameters [ |> Sql.parameters [
"did", Sql.string (string player.DiscordId) "did", Sql.string (string player.DiscordId)
"gbt", Sql.int (int player.Bank) "gbt", Sql.int (int player.Bank)
"inv", Sql.intArray (player.Inventory |> Array.ofList |> Array.map (fun item -> item.Id))
"strength", Sql.int player.Stats.Strength.Amount "strength", Sql.int player.Stats.Strength.Amount
"focus", Sql.int player.Stats.Focus.Amount "focus", Sql.int player.Stats.Focus.Amount
"charisma", Sql.int player.Stats.Charisma.Amount "charisma", Sql.int player.Stats.Charisma.Amount
"luck", Sql.int player.Stats.Luck.Amount "luck", Sql.int player.Stats.Luck.Amount
] |> Sql.query """ ] |> Sql.query """
UPDATE "user" SET gbt = @gbt, inventory = @inv, UPDATE "user" SET gbt = @gbt, strength = @strength, focus = @focus, charisma = @charisma, luck = @luck
strength = @strength, focus = @focus, charisma = @charisma, luck = @luck
WHERE discord_id = @did WHERE discord_id = @did
""" """
|> Sql.executeNonQueryAsync |> Sql.executeNonQueryAsync

View File

@ -1,6 +1,7 @@
module Degenz.Embeds module Degenz.Embeds
open System open System
open Degenz
open Degenz.Messaging open Degenz.Messaging
open Degenz.Types open Degenz.Types
open DSharpPlus.Entities open DSharpPlus.Entities
@ -10,13 +11,13 @@ let shieldGif = "https://s10.gifyu.com/images/Defense-Degenz-V2-min.gif"
let getItemIcon id = let getItemIcon id =
match enum<ItemId>(id) with match enum<ItemId>(id) with
| ItemId.Virus -> "https://s10.gifyu.com/images/Virus-icon.jpg" | ItemId.Virus -> Some "https://s10.gifyu.com/images/Virus-icon.jpg"
| ItemId.RemoteAccess -> "https://s10.gifyu.com/images/Mind-Control-Degenz-V2-min.jpg" | ItemId.RemoteAccess -> Some "https://s10.gifyu.com/images/Mind-Control-Degenz-V2-min.jpg"
| ItemId.Worm -> "https://s10.gifyu.com/images/WormBugAttack_Degenz-min.jpg" | ItemId.Worm -> Some "https://s10.gifyu.com/images/WormBugAttack_Degenz-min.jpg"
| ItemId.Firewall -> "https://s10.gifyu.com/images/Defense-GIF-1-Degenz-1.jpg" | ItemId.Firewall -> Some "https://s10.gifyu.com/images/Defense-GIF-1-Degenz-1.jpg"
| ItemId.Encryption -> "https://s10.gifyu.com/images/Encryption-Degenz-V2-1-min.jpg" | ItemId.Encryption -> Some "https://s10.gifyu.com/images/Encryption-Degenz-V2-1-min.jpg"
| ItemId.Cypher -> "https://s10.gifyu.com/images/Cypher-Smaller.jpg" | ItemId.Cypher -> Some "https://s10.gifyu.com/images/Cypher-Smaller.jpg"
| _ -> hackGif | _ -> None
let getItemGif id = let getItemGif id =
match enum<ItemId>(id) with match enum<ItemId>(id) with
@ -61,8 +62,8 @@ let pickDefense actionId player isTrainer =
for shield in Inventory.getShields player.Inventory do for shield in Inventory.getShields player.Inventory do
let hours = TimeSpan.FromMinutes(int shield.Cooldown).TotalHours |> int let hours = TimeSpan.FromMinutes(int shield.Cooldown).TotalHours |> int
let against = WeaponClass.getGoodAgainst(shield.Class) |> snd let against = WeaponClass.getGoodAgainst shield.Class |> snd
embed.AddField(shield.Item.Name, $"Active {hours} hours\nDefeats {against}", true) |> ignore embed.AddField(shield.Name, $"Active {hours} hours\nDefeats {against}", true) |> ignore
DiscordFollowupMessageBuilder() DiscordFollowupMessageBuilder()
.AddComponents(buttons) .AddComponents(buttons)
@ -85,7 +86,7 @@ let pickHack actionId attacker defender isTrainer =
if not isTrainer then if not isTrainer then
for hack in Inventory.getHacks attacker.Inventory do for hack in Inventory.getHacks attacker.Inventory do
let amount = if hack.Power > int defender.Bank then int defender.Bank else hack.Power let amount = if hack.Power > int defender.Bank then int defender.Bank else hack.Power
embed.AddField(hack.Item.Name, $"Cooldown {hack.Cooldown} mins\nExtract {amount} $GBT", true) |> ignore embed.AddField(hack.Name, $"Cooldown {hack.Cooldown} mins\nExtract {amount} $GBT", true) |> ignore
DiscordFollowupMessageBuilder() DiscordFollowupMessageBuilder()
.AddComponents(buttons) .AddComponents(buttons)
@ -95,9 +96,9 @@ let pickHack actionId attacker defender isTrainer =
let responseSuccessfulHack earnedMoney (targetId : uint64) amountTaken (hack : HackItem) = let responseSuccessfulHack earnedMoney (targetId : uint64) amountTaken (hack : HackItem) =
let embed = let embed =
DiscordEmbedBuilder() DiscordEmbedBuilder()
.WithImageUrl(getItemGif hack.Item.Id) .WithImageUrl(getItemGif hack.Id)
.WithTitle("Hack Attack") .WithTitle("Hack Attack")
.WithDescription($"You successfully hacked <@{targetId}> using {hack.Item.Name}" .WithDescription($"You successfully hacked <@{targetId}> using {hack.Name}"
+ (if earnedMoney then $", and took {amountTaken} 💰$GBT from them!" else "!")) + (if earnedMoney then $", and took {amountTaken} 💰$GBT from them!" else "!"))
DiscordFollowupMessageBuilder() DiscordFollowupMessageBuilder()
@ -105,9 +106,9 @@ let responseSuccessfulHack earnedMoney (targetId : uint64) amountTaken (hack : H
.AsEphemeral(true) .AsEphemeral(true)
let responseCreatedShield (shield : ShieldItem) = let responseCreatedShield (shield : ShieldItem) =
let embed = DiscordEmbedBuilder().WithImageUrl(getItemGif shield.Item.Id) let embed = DiscordEmbedBuilder().WithImageUrl(getItemGif shield.Id)
embed.Title <- "Mounted Shield" embed.Title <- "Mounted Shield"
embed.Description <- $"Mounted {shield.Item.Name} shield for {TimeSpan.FromMinutes(int shield.Cooldown).TotalHours} hours" embed.Description <- $"Mounted {shield.Name} shield for {TimeSpan.FromMinutes(int shield.Cooldown).TotalHours} hours"
DiscordFollowupMessageBuilder() DiscordFollowupMessageBuilder()
.AddEmbed(embed) .AddEmbed(embed)

View File

@ -13,15 +13,37 @@ module Armory =
JsonConvert.DeserializeObject<Inventory>(file) JsonConvert.DeserializeObject<Inventory>(file)
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.Type with ItemType.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.Type with ItemType.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.Type with ItemType.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.Type with ItemType.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 getHackItem item =
match item.Type , item.Attributes with
| ItemType.Hack , CanBuy buyPrice & CanSell _ & CanAttack power & CanExpire cooldown & CanClass ``class``->
Some { Id = item.Id
Name = item.Name
Price = buyPrice
Cooldown = cooldown
Power = power
Class = ``class`` }
| _ -> None
let getShieldItem item =
match item.Type , item.Attributes with
| ItemType.Shield , CanBuy buyPrice & CanSell _ & CanDefend resistance & CanExpire cooldown & CanClass ``class`` ->
Some { Id = item.Id
Name = item.Name
Price = buyPrice
Cooldown = cooldown
Resistance = resistance
Class = ``class`` }
| _ -> None
// 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 =
@ -31,36 +53,34 @@ module Inventory =
// 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 getHackItem
// inventory |> List.choose (fun item -> match item with | Hack h -> Some h | _ -> None) let getShields inventory = inventory |> List.choose getShieldItem
// let getShields inventory = let getFoods inventory =
// inventory |> List.choose (fun item -> match item with | Shield s -> Some s | _ -> None) inventory |> List.choose (fun item -> match item.Type with | ItemType.Food -> Some item | _ -> None)
// let getFoods inventory = let getAccessories inventory =
// inventory |> List.choose (fun item -> match item with | Food f -> Some f | _ -> None) inventory |> List.choose (fun item -> match item.Type with | ItemType.Accessory -> Some item | _ -> None)
// let getAccessories inventory =
// 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)
let getClassButtonColor item = let getClassButtonColor item =
match ItemDetails.getClass item with match item.Attributes with
| 0 -> ButtonStyle.Danger | CanClass "0" -> ButtonStyle.Danger
| 1 -> ButtonStyle.Primary | CanClass "1" -> ButtonStyle.Primary
| 2 -> ButtonStyle.Success | CanClass "2" -> ButtonStyle.Success
| _ -> ButtonStyle.Primary | _ -> ButtonStyle.Primary
let getClassEmbedColor item = let getClassEmbedColor item =
match ItemDetails.getClass item with match item.Attributes with
| 0 -> DiscordColor.Red | CanClass "0" -> DiscordColor.Red
| 1 -> DiscordColor.Blurple | CanClass "1" -> DiscordColor.Blurple
| 2 -> DiscordColor.Green | CanClass "2" -> DiscordColor.Green
| _ -> DiscordColor.Blurple | _ -> DiscordColor.Blurple
let getGoodAgainst = function let getGoodAgainst = function
| 0 -> ( ItemId.Firewall , ItemId.Virus ) | "0" -> ( ItemId.Firewall , ItemId.Virus )
| 1 -> ( ItemId.Encryption , ItemId.RemoteAccess ) | "1" -> ( ItemId.Encryption , ItemId.RemoteAccess )
| _ -> ( ItemId.Cypher , ItemId.Worm ) | _ -> ( ItemId.Cypher , ItemId.Worm )
module Player = module Player =
let getHackEvents player = let getHackEvents player =
@ -81,11 +101,11 @@ module Player =
let modifyBank (player : PlayerData) amount = { player with Bank = max (player.Bank + amount) 0<GBT> } let modifyBank (player : PlayerData) amount = { player with Bank = max (player.Bank + amount) 0<GBT> }
module PlayerStats = module PlayerStats =
// 4.17f would go from 100 to 0 in roughly 24 hours // 2.09f would go from 100 to 0 in roughly 48 hours
let Strength = { Id = StatId.Strength ; BaseDecayRate = 4.17 ; BaseRange = Range.normalized } let Strength = { Id = StatId.Strength ; BaseDecayRate = 2.09 ; BaseRange = Range.normalized }
let Focus = { Id = StatId.Focus ; BaseDecayRate = 4.17 ; BaseRange = Range.normalized } let Focus = { Id = StatId.Focus ; BaseDecayRate = 2.09 ; BaseRange = Range.normalized }
let Luck = { Id = StatId.Luck ; BaseDecayRate = 4.17 ; BaseRange = Range.normalized } let Luck = { Id = StatId.Luck ; BaseDecayRate = 2.09 ; BaseRange = Range.normalized }
let Charisma = { Id = StatId.Charisma ; BaseDecayRate = 4.17 ; BaseRange = Range.normalized } let Charisma = { Id = StatId.Charisma ; BaseDecayRate = 2.09 ; BaseRange = Range.normalized }
let stats = [ Strength ; Focus ; Charisma ; Luck ] let stats = [ Strength ; Focus ; Charisma ; Luck ]
@ -104,18 +124,20 @@ module PlayerStats =
let modMinMax = let modMinMax =
let min = let min =
items items
|> List.filter (fun item -> match item with | Accessory a -> a.TargetStat = statId | _ -> false) |> List.choose (fun i -> match i.Attributes with CanModify fx -> Some fx | _ -> None)
|> List.sumBy (fun item -> match item with | Accessory a -> a.FloorBoost | _ -> 0) |> List.concat
|> List.sumBy (fun fx -> match fx.Effect with | Min x -> x | _ -> 0)
let max = let max =
items items
|> List.filter (fun item -> match item with | Accessory a -> a.TargetStat = statId | _ -> false) |> List.choose (fun i -> match i.Attributes with CanModify fx -> Some fx | _ -> None)
|> List.sumBy (fun item -> match item with | Accessory a -> a.CeilBoost | _ -> 0) |> List.concat
|> List.sumBy (fun fx -> match fx.Effect with | Max x -> x | _ -> 0)
Range.create (statConfig.BaseRange.Min + min) (statConfig.BaseRange.Max + max) Range.create (statConfig.BaseRange.Min + min) (statConfig.BaseRange.Max + max)
let amountAfterDecay = modMinMax |> Range.constrain amount let amountAfterDecay = modMinMax |> Range.constrain amount
{ Id = statId ; Amount = amountAfterDecay ; ModRange = modMinMax ; LastRead = DateTime.UtcNow } { Id = statId ; Amount = amountAfterDecay ; ModRange = modMinMax ; LastRead = DateTime.UtcNow }
module Arsenal = module Arsenal =
let battleItemFormat (items : ItemDetails list) = let battleItemFormat (items : Inventory) =
match items with match items with
| [] -> "None" | [] -> "None"
| _ -> items |> List.map (fun item -> item.Name) |> String.concat ", " | _ -> items |> List.map (fun item -> item.Name) |> String.concat ", "
@ -128,13 +150,13 @@ module Arsenal =
|> List.map (fun event -> |> List.map (fun event ->
match event.Type with match event.Type with
| Hacking h -> | Hacking h ->
let item = Armory.weapons |> Inventory.findHackById h.HackId let item = Armory.weapons |> Inventory.findItemById h.HackId
let cooldown = Messaging.getTimeText false WeaponClass.SameTargetAttackCooldown event.Timestamp let cooldown = Messaging.getTimeText false WeaponClass.SameTargetAttackCooldown event.Timestamp
$"Hacked {h.Adversary.Name} with {item.Item.Name} {cooldown} ago" $"Hacked {h.Adversary.Name} with {item.Name} {cooldown} ago"
| Shielding id -> | Shielding id ->
let item = Armory.weapons |> Inventory.findShieldById id let item = Armory.weapons |> Inventory.findItemById id
let cooldown = Messaging.getTimeText true (TimeSpan.FromMinutes(int event.Cooldown)) event.Timestamp let cooldown = Messaging.getTimeText true (TimeSpan.FromMinutes(int event.Cooldown)) event.Timestamp
$"{item.Item.Name} Shield active for {cooldown}" $"{item.Name} Shield active for {cooldown}"
| _ -> "") | _ -> "")
|> List.filter (String.IsNullOrWhiteSpace >> not) |> List.filter (String.IsNullOrWhiteSpace >> not)
|> String.concat "\n" |> String.concat "\n"

View File

@ -92,7 +92,7 @@ type ItemType =
type Effect = type Effect =
| Min of int | Min of int
| Max of int | Max of int
| Booster of int | Add of int
| RateMultiplier of float | RateMultiplier of float
type StatEffect = { type StatEffect = {
@ -103,28 +103,29 @@ type StatEffect = {
type ItemAttribute = type ItemAttribute =
| Buyable of price : int<GBT> | Buyable of price : int<GBT>
| Sellable of price : int<GBT> | Sellable of price : int<GBT>
| RateLimitable of cooldown : int<mins>
| Expireable of lifetime : int<mins> | Expireable of lifetime : int<mins>
| Consumable of effects : StatEffect list | Consumable
| Passive of effects : StatEffect list | Modifiable of effects : StatEffect list
| Droppable of chance : float | Droppable of chance : float
| Tradeable of yes : unit | Tradeable
| Attackable of power : int | Attackable of power : int
| Defendable of resistance : int | Defendable of resistance : int
| Classable of className : string | Classable of className : string
| Stackable of max : int | Stackable of max : int
let (|CanBuy|_|) itemAttrs = itemAttrs |> List.tryPick (function Buyable p -> Some p | _ -> None) 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 (|CanSell|_|) itemAttrs = itemAttrs |> List.tryPick (function Sellable p -> Some p | _ -> None)
let (|CanExpire|_|) itemAttrs = itemAttrs |> List.tryPick (function Expireable l -> Some l | _ -> 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 (|CanRateLimit|_|) itemAttrs = itemAttrs |> List.tryPick (function RateLimitable l -> Some l | _ -> None)
let (|CanPassive|_|) itemAttrs = itemAttrs |> List.tryPick (function Passive es -> Some es | _ -> None) let (|CanConsume|_|) itemAttrs = itemAttrs |> List.tryPick (function Consumable -> Some () | _ -> None)
let (|CanDrop|_|) itemAttrs = itemAttrs |> List.tryPick (function Droppable c -> Some c | _ -> None) let (|CanModify|_|) itemAttrs = itemAttrs |> List.tryPick (function Modifiable es -> Some es | _ -> None)
let (|CanTrade|_|) itemAttrs = itemAttrs |> List.tryPick (function Tradeable () -> Some () | _ -> None) let (|CanDrop|_|) itemAttrs = itemAttrs |> List.tryPick (function Droppable c -> Some c | _ -> None)
let (|CanAttack|_|) itemAttrs = itemAttrs |> List.tryPick (function Attackable p -> Some p | _ -> None) let (|CanTrade|_|) itemAttrs = itemAttrs |> List.tryPick (function Tradeable -> Some () | _ -> None)
let (|CanDefend|_|) itemAttrs = itemAttrs |> List.tryPick (function Defendable r -> Some r | _ -> None) let (|CanAttack|_|) itemAttrs = itemAttrs |> List.tryPick (function Attackable p -> Some p | _ -> None)
let (|CanClass|_|) itemAttrs = itemAttrs |> List.tryPick (function Classable c -> Some c | _ -> None) let (|CanDefend|_|) itemAttrs = itemAttrs |> List.tryPick (function Defendable r -> Some r | _ -> None)
let (|CanStack|_|) itemAttrs = itemAttrs |> List.tryPick (function Stackable m -> Some m | _ -> 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
@ -133,6 +134,24 @@ type Item = {
Attributes : ItemAttribute list Attributes : ItemAttribute list
} }
type HackItem = {
Id : int
Name : string
Price : int<GBT>
Cooldown : int<mins>
Power : int
Class : string
}
type ShieldItem = {
Id : int
Name : string
Price : int<GBT>
Cooldown : int<mins>
Resistance : int
Class : string
}
type Inventory = Item list type Inventory = Item list
type PlayerData = { type PlayerData = {

View File

@ -52,7 +52,7 @@ let checkHasEmptyHacks (attacker : PlayerData) =
let checkPlayerOwnsWeapon (item : Item) player = let checkPlayerOwnsWeapon (item : Item) player =
match player.Inventory |> List.exists (fun i -> i.Id = item.Id) with match player.Inventory |> List.exists (fun i -> i.Id = item.Id) with
| true -> Ok player | true -> Ok player
| false -> Error $"You sold your weapon already, you cheeky bastard..." | false -> Error $"You sold your {item.Name} already, you cheeky bastard..."
let checkPlayerHasShieldSlotsAvailable player = let checkPlayerHasShieldSlotsAvailable player =
let updatedPlayer = player |> Player.removeExpiredActions let updatedPlayer = player |> Player.removeExpiredActions
@ -82,7 +82,11 @@ let runHackerBattle defender (hack : HackItem) =
|> fun p -> p.Events |> fun p -> p.Events
|> List.exists (fun event -> |> List.exists (fun event ->
match event.Type with match event.Type with
| Shielding id -> hack.Class = (Inventory.findShieldById id Armory.weapons).Class | Shielding id ->
let item = Inventory.findItemById id Armory.weapons
match item.Attributes with
| CanClass c -> hack.Class = c
| _ -> false
| _ -> false) | _ -> false)
let updateCombatants successfulHack (attacker : PlayerData) (defender : PlayerData) (hack : HackItem) prize = let updateCombatants successfulHack (attacker : PlayerData) (defender : PlayerData) (hack : HackItem) prize =
@ -90,7 +94,7 @@ let updateCombatants successfulHack (attacker : PlayerData) (defender : PlayerDa
{ p with Events = 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 event isDefenderEvent =
let hackEvent = { let hackEvent = {
HackId = hack.Item.Id HackId = hack.Id
Adversary = if isDefenderEvent then attacker.toDiscordPlayer else defender.toDiscordPlayer Adversary = if isDefenderEvent then attacker.toDiscordPlayer else defender.toDiscordPlayer
IsInstigator = not isDefenderEvent IsInstigator = not isDefenderEvent
Success = successfulHack Success = successfulHack
@ -161,25 +165,26 @@ let handleAttack (ctx : IDiscordContext) =
executePlayerAction ctx (fun attacker -> async { executePlayerAction ctx (fun attacker -> async {
let tokens = ctx.GetInteractionId().Split("-") let tokens = ctx.GetInteractionId().Split("-")
let hackId = int tokens.[1] let hackId = int tokens.[1]
let hack = Armory.weapons |> Inventory.findHackById hackId let item = Armory.weapons |> Inventory.findItemById hackId
let hackItem = (Inventory.getHackItem item).Value
let resultId , targetId = UInt64.TryParse tokens.[2] let resultId , targetId = UInt64.TryParse tokens.[2]
let! resultTarget = DbService.tryFindPlayer targetId let! resultTarget = DbService.tryFindPlayer targetId
match resultTarget , true , resultId with match resultTarget , resultId with
| Some defender , true , true -> | Some defender , true ->
do! attacker do! attacker
|> Player.removeExpiredActions |> Player.removeExpiredActions
|> checkAlreadyHackedTarget defender |> checkAlreadyHackedTarget defender
>>= checkPlayerOwnsWeapon hack.Item >>= checkPlayerOwnsWeapon item
>>= checkTargetHasFunds defender >>= checkTargetHasFunds defender
>>= checkWeaponHasCooldown hack.Item >>= checkWeaponHasCooldown item
|> function |> function
| Ok atkr -> async { | Ok attacker -> async {
let result = runHackerBattle defender hack let result = runHackerBattle defender hackItem
match result with match result with
| false -> do! successfulHack ctx atkr defender hack | false -> do! successfulHack ctx attacker defender hackItem
| true -> do! failedHack ctx attacker defender hack | true -> do! failedHack ctx attacker defender hackItem
do! Analytics.hackedTarget (ctx.GetDiscordMember()) hack.Item.Name (not result) do! Analytics.hackedTarget (ctx.GetDiscordMember()) hackItem.Name (not result)
} }
| Error msg -> Messaging.sendFollowUpMessage ctx msg | Error msg -> Messaging.sendFollowUpMessage ctx msg
| _ -> do! Messaging.sendFollowUpMessage ctx "Error occurred processing attack" | _ -> do! Messaging.sendFollowUpMessage ctx "Error occurred processing attack"
@ -201,18 +206,19 @@ let handleDefense (ctx : IDiscordContext) =
executePlayerAction ctx (fun player -> async { executePlayerAction ctx (fun player -> async {
let tokens = ctx.GetInteractionId().Split("-") let tokens = ctx.GetInteractionId().Split("-")
let shieldId = int tokens.[1] let shieldId = int tokens.[1]
let shield = Armory.weapons |> Inventory.findShieldById shieldId let item = Armory.weapons |> Inventory.findItemById shieldId
let shieldItem = (Inventory.getShieldItem item).Value
do! player do! player
|> checkPlayerOwnsWeapon shield.Item |> checkPlayerOwnsWeapon item
>>= checkPlayerHasShieldSlotsAvailable >>= checkPlayerHasShieldSlotsAvailable
>>= checkWeaponHasCooldown shield.Item >>= checkWeaponHasCooldown item
|> handleResultWithResponse ctx (fun p -> async { |> handleResultWithResponse ctx (fun p -> async {
let embed = Embeds.responseCreatedShield shield let embed = Embeds.responseCreatedShield shieldItem
do! ctx.FollowUp embed |> Async.AwaitTask do! ctx.FollowUp embed |> Async.AwaitTask
let defense = { let defense = {
Type = Shielding shieldId Type = Shielding shieldId
Cooldown = shield.Cooldown Cooldown = shieldItem.Cooldown
Timestamp = DateTime.UtcNow Timestamp = DateTime.UtcNow
} }
do! DbService.updatePlayer p |> Async.Ignore do! DbService.updatePlayer p |> Async.Ignore
@ -223,7 +229,7 @@ let handleDefense (ctx : IDiscordContext) =
do! channel.SendMessageAsync(builder) do! channel.SendMessageAsync(builder)
|> Async.AwaitTask |> Async.AwaitTask
|> Async.Ignore |> Async.Ignore
do! Analytics.shieldActivated (ctx.GetDiscordMember()) shield.Item.Name do! Analytics.shieldActivated (ctx.GetDiscordMember()) shieldItem.Name
}) })
}) })

View File

@ -10,46 +10,61 @@ open Degenz
open Degenz.Messaging open Degenz.Messaging
open Degenz.PlayerInteractions open Degenz.PlayerInteractions
let getBuyItemsEmbed (playerInventory : Inventory) (storeInventory : Inventory) = let getItemEmbeds owned (items : Inventory) =
let embeds , buttons = items
storeInventory |> List.countBy (fun item -> item.Id)
|> List.map (fun item -> |> List.map (fun (id,count) -> items |> List.find (fun i -> i.Id = id) , count )
let embed = DiscordEmbedBuilder() |> List.map (fun (item,count) ->
match item.Attributes with let embed = DiscordEmbedBuilder()
| CanBuy price -> embed.AddField("Price 💰", (if price = 0<GBT> then "Free" else $"{price} $GBT"), true) |> ignore item.Attributes
| CanAttack power -> |> 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.Type with ItemType.Hack -> "$GBT Reward" | _ -> "Power" let title = match item.Type with ItemType.Hack -> "$GBT Reward" | _ -> "Power"
embed.AddField($"{title} |", string power, true) |> ignore embed.AddField($"{title} |", string power, true) |> ignore
| CanExpire time -> | RateLimitable time ->
let title = match item.Type with ItemType.Hack -> "Cooldown" | ItemType.Shield -> "Active For" | _ -> "Expires" let title = match item.Type with ItemType.Hack -> "Cooldown" | ItemType.Shield -> "Active For" | _ -> "Expires"
let ts = TimeSpan.FromMinutes(int time) let ts = TimeSpan.FromMinutes(int time)
let timeStr = if ts.Hours = 0 then $"{ts.Minutes} mins" else $"{ts.Hours} hours" let timeStr = if ts.Hours = 0 then $"{ts.Minutes} mins" else $"{ts.Hours} hours"
embed.AddField($"{title} |", timeStr, true) |> ignore embed.AddField($"{title} |", timeStr, true) |> ignore
| CanConsume effects | CanPassive effects -> | Stackable max ->
if owned then
embed.AddField($"Total Owned |", $"{count}", true) |> ignore
else
embed.AddField($"Max Allowed |", $"{max}", true) |> ignore
| Modifiable effects ->
let fx = let fx =
effects effects
|> List.map (fun f -> |> List.map (fun f ->
match f.Effect with match f.Effect with
| Min i -> $"Min - {f.TargetStat}" | Min i -> $"{f.TargetStat} Min + {i}"
| Max i -> $"Max - {f.TargetStat}" | Max i -> $"{f.TargetStat} Max + {i}"
| Booster i -> | Add i ->
let str = if i > 0 then "Boost" else "Penalty" let str = if i > 0 then "Boost" else "Penalty"
$"{str} - {f.TargetStat}" $"{f.TargetStat} {str} + {i}"
| RateMultiplier i -> $"Multiplier - {f.TargetStat}") | RateMultiplier i -> $"{f.TargetStat} Multiplier - i")
|> String.concat "\n" |> String.concat "\n"
embed.AddField($"Effect - Amount |", $"{fx}", true) |> ignore embed.AddField($"Effect - Amount ", $"{fx}", true) |> ignore
| _ -> () | _ -> ())
embed embed
.WithColor(WeaponClass.getClassEmbedColor item) .WithColor(WeaponClass.getClassEmbedColor item)
.WithThumbnail(Embeds.getItemIcon item.Id) .WithTitle($"{item.Name}")
.WithTitle($"{item.Name}") |> ignore
|> ignore match Embeds.getItemIcon item.Id with
let button = | Some url -> embed.WithThumbnail(url)
if playerInventory |> List.exists (fun i -> i.Id = item.Id) | None -> embed)
then DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Buy-{item.Id}", $"Own {item.Name}", true) |> List.map (fun e -> e.Build())
else DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Buy-{item.Id}", $"Buy {item.Name}") |> Seq.ofList
( embed.Build() , button :> DiscordComponent ))
|> List.unzip let getBuyItemsEmbed (playerInventory : Inventory) (storeInventory : Inventory) =
let embeds = getItemEmbeds false storeInventory
let buttons =
storeInventory
|> List.map (fun item ->
if playerInventory |> List.exists (fun i -> i.Id = item.Id)
then DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Buy-{item.Id}", $"Own {item.Name}", true)
else DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Buy-{item.Id}", $"Buy {item.Name}")
:> DiscordComponent)
DiscordFollowupMessageBuilder() DiscordFollowupMessageBuilder()
.AddEmbeds(embeds) .AddEmbeds(embeds)
@ -87,12 +102,21 @@ let checkHasSufficientFunds (item : Item) player =
else Error $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT" else Error $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT"
| _ -> Error $"{item.Name} item cannot be bought" | _ -> Error $"{item.Name} item cannot be bought"
let checkAlreadyOwnsItem (item : Item) player = let checkDoesntExceedStackCap (item : Item) player =
if player.Inventory |> List.exists (fun w -> item.Id = w.Id) let itemCount =
then Error $"You already own {item.Name}!" player.Inventory
else Ok player |> 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 player = let checkSoldItemAlready (item : Item) player =
if player.Inventory |> List.exists (fun i -> item.Id = i.Id) if player.Inventory |> List.exists (fun i -> item.Id = i.Id)
then Ok player then Ok player
else Error $"{item.Name} not found in your inventory! Looks like you sold it already." else Error $"{item.Name} not found in your inventory! Looks like you sold it already."
@ -105,10 +129,15 @@ let checkHasItemsInArsenal itemType items player =
let buy itemType (ctx : IDiscordContext) = let buy itemType (ctx : IDiscordContext) =
executePlayerAction ctx (fun player -> async { executePlayerAction ctx (fun player -> async {
let playerItems = Inventory.getItemsByType itemType player.Inventory let playerItems = Inventory.getItemsByType itemType player.Inventory
let armoryItems = Inventory.getItemsByType itemType Armory.weapons try
let itemStore = getBuyItemsEmbed playerItems armoryItems let! armoryItems = DbService.getStoreItems (ctx.GetChannel().Id)
do! ctx.FollowUp itemStore |> Async.AwaitTask if armoryItems.Length > 0 then
do! Analytics.buyWeaponCommand (ctx.GetDiscordMember()) itemType let itemStore = getBuyItemsEmbed playerItems armoryItems
do! ctx.FollowUp itemStore |> Async.AwaitTask
do! Analytics.buyWeaponCommand (ctx.GetDiscordMember()) itemType
else
do! Messaging.sendFollowUpMessage ctx "This channel doesn't have anything to sell"
with ex -> printfn $"{ex.Message}"
}) })
let sell itemType getItems (ctx : IDiscordContext) = let sell itemType getItems (ctx : IDiscordContext) =
@ -124,16 +153,16 @@ let sell itemType getItems (ctx : IDiscordContext) =
// TODO: When you buy a shield, prompt the user to activate it // TODO: When you buy a shield, prompt the user to activate it
let handleBuyItem (ctx : IDiscordContext) itemId = let handleBuyItem (ctx : IDiscordContext) itemId =
executePlayerAction ctx (fun player -> async { executePlayerAction ctx (fun player -> async {
let item = Armory.weapons |> Inventory.findItemById itemId let! storeInventory = DbService.getStoreItems (ctx.GetChannel().Id)
let item = storeInventory |> Inventory.findItemById itemId
do! player do! player
|> checkHasSufficientFunds item |> checkHasSufficientFunds item
>>= checkAlreadyOwnsItem item >>= checkDoesntExceedStackCap item
|> handleResultWithResponse ctx (fun player -> async { |> handleResultWithResponse ctx (fun player -> async {
let price = match item.Attributes with CanBuy price -> price | _ -> 0<GBT> let price = match item.Attributes with CanBuy price -> price | _ -> 0<GBT>
let newBalance = player.Bank - price do! DbService.updatePlayerCurrency -price player |> Async.Ignore
let p = { player with Bank = newBalance ; Inventory = item::player.Inventory } do! DbService.addToPlayerInventory player.DiscordId item |> Async.Ignore
do! DbService.updatePlayer p |> Async.Ignore do! sendFollowUpMessage ctx $"Successfully purchased {item.Name}! You now have {player.Bank - price} 💰$GBT remaining"
do! sendFollowUpMessage ctx $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining"
do! Analytics.buyWeaponButton (ctx.GetDiscordMember()) item.Name price do! Analytics.buyWeaponButton (ctx.GetDiscordMember()) item.Name price
}) })
}) })
@ -163,68 +192,18 @@ let handleSell (ctx : IDiscordContext) itemId =
}) })
}) })
let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) = let consume (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction ctx (fun player -> async {
let ctx = DiscordEventContext event :> IDiscordContext match player.Inventory |> Inventory.getFoods with
let id = ctx.GetInteractionId() | [] -> do! Messaging.sendFollowUpMessage ctx "You do not have any items to consume"
let itemId = int <| id.Split("-").[1] | items ->
match id with let embeds = getItemEmbeds true items
| id when id.StartsWith("Buy") -> handleBuyItem ctx itemId let builder = DiscordFollowupMessageBuilder().AddEmbeds(embeds).AsEphemeral(true)
| id when id.StartsWith("Sell") -> handleSell ctx itemId do! ctx.FollowUp builder |> Async.AwaitTask
| _ -> })
task {
let builder = DiscordInteractionResponseBuilder()
builder.IsEphemeral <- true
builder.Content <- $"Incorrect Action identifier {id}"
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
}
let showInventoryEmbed (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction ctx (fun player -> async { let showInventoryEmbed (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction ctx (fun player -> async {
let embeds , buttons = let embeds = getItemEmbeds true player.Inventory
player.Inventory let builder = DiscordFollowupMessageBuilder().AddEmbeds(embeds).AsEphemeral(true)
|> List.map (fun item ->
let embed = DiscordEmbedBuilder()
match item.Attributes with
| CanBuy price -> embed.AddField("Price 💰", (if price = 0<GBT> then "Free" else $"{price} $GBT"), true) |> ignore
| CanAttack power ->
let title = match item.Type with ItemType.Hack -> "$GBT Reward" | _ -> "Power"
embed.AddField($"{title} |", string power, true) |> ignore
| CanExpire time ->
let title = match 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
| CanConsume effects | CanPassive effects ->
let fx =
effects
|> List.map (fun f ->
match f.Effect with
| Min i -> $"Min - {f.TargetStat}"
| Max i -> $"Max - {f.TargetStat}"
| Booster i ->
let str = if i > 0 then "Boost" else "Penalty"
$"{str} - {f.TargetStat}"
| RateMultiplier i -> $"Multiplier - {f.TargetStat}")
|> String.concat "\n"
embed.AddField($"Effect - Amount |", $"{fx}", true) |> ignore
| _ -> ()
embed
.WithColor(WeaponClass.getClassEmbedColor item)
.WithThumbnail(Embeds.getItemIcon item.Id)
.WithTitle($"{item.Name}")
|> ignore
let button =
// if playerInventory |> List.exists (fun i -> i.Id = item.Id)
// then DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Buy-{item.Id}", $"Own {item.Name}", true)
// else DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Buy-{item.Id}", $"Buy {item.Name}")
DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Sell-{item.Id}", $"Sell {item.Name}")
( embed.Build() , button :> DiscordComponent ))
|> List.unzip
let builder =
DiscordFollowupMessageBuilder()
.AddEmbeds(embeds)
// .AddComponents(buttons)
.AsEphemeral(true)
do! ctx.FollowUp builder |> Async.AwaitTask do! ctx.FollowUp builder |> Async.AwaitTask
}) })
@ -233,7 +212,6 @@ let showStats (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction c
PlayerStats.stats PlayerStats.stats
|> List.iter (fun statConfig -> |> List.iter (fun statConfig ->
let playerStat = PlayerStats.getPlayerStat statConfig player let playerStat = PlayerStats.getPlayerStat statConfig player
// let diffSymbol lhs rhs = if lhs < rhs then "-" elif lhs = rhs then "" else "+"
let min = let min =
match statConfig.BaseRange.Min = playerStat.ModRange.Min with match statConfig.BaseRange.Min = playerStat.ModRange.Min with
| true -> $"{statConfig.BaseRange.Min}" | true -> $"{statConfig.BaseRange.Min}"
@ -251,6 +229,21 @@ let showStats (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction c
do! ctx.FollowUp builder |> Async.AwaitTask do! ctx.FollowUp builder |> Async.AwaitTask
}) })
let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) =
let ctx = DiscordEventContext event :> IDiscordContext
let id = ctx.GetInteractionId()
let itemId = int <| id.Split("-").[1]
match id with
| id when id.StartsWith("Buy") -> handleBuyItem ctx itemId
| id when id.StartsWith("Sell") -> handleSell ctx itemId
| _ ->
task {
let builder = DiscordInteractionResponseBuilder()
builder.IsEphemeral <- true
builder.Content <- $"Incorrect Action identifier {id}"
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
}
type Store() = type Store() =
inherit ApplicationCommandModule () inherit ApplicationCommandModule ()
@ -294,8 +287,7 @@ type Store() =
member this.SellShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" (Inventory.getItemsByType ItemType.Shield)) member this.SellShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" (Inventory.getItemsByType ItemType.Shield))
[<SlashCommand("consume", "Consume a food item")>] [<SlashCommand("consume", "Consume a food item")>]
member this.Consume (ctx : InteractionContext) = member this.Consume (ctx : InteractionContext) = consume (DiscordInteractionContext ctx)
enforceChannel (DiscordInteractionContext ctx) (sell "Food" (Inventory.getItemsByType ItemType.Food))
[<SlashCommand("inventory", "Check your inventory")>] [<SlashCommand("inventory", "Check your inventory")>]
member this.Inventory (ctx : InteractionContext) = member this.Inventory (ctx : InteractionContext) =

View File

@ -9,8 +9,10 @@ open Degenz.Messaging
let TrainerAchievement = "FINISHED_TRAINER" let TrainerAchievement = "FINISHED_TRAINER"
let Sensei = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" } let Sensei = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" }
let defaultHack = Armory.weapons |> Inventory.findHackById (int ItemId.Virus) let hackItem = Armory.weapons |> Inventory.findItemById (int ItemId.Virus)
let defaultShield = Armory.weapons |> Inventory.findShieldById (int ItemId.Firewall) let shieldItem = Armory.weapons |> Inventory.findItemById (int ItemId.Firewall)
let defaultHack = (Inventory.getHackItem hackItem).Value
let defaultShield = (Inventory.getShieldItem shieldItem ).Value
let CurrencyGift = 250<GBT> let CurrencyGift = 250<GBT>
let BeginnerProtectionHours = 24 let BeginnerProtectionHours = 24
@ -21,7 +23,7 @@ let HackEvent () = {
Adversary = Sensei Adversary = Sensei
Success = true Success = true
IsInstigator = true IsInstigator = true
HackId = defaultHack.Item.Id HackId = defaultHack.Id
} }
} }
let ShieldEvents () = [ let ShieldEvents () = [
@ -63,7 +65,7 @@ let handleTrainerStep1 (ctx : IDiscordContext) =
|> Async.AwaitTask |> Async.AwaitTask
let msg = "Beautopia© is a dangerous place... quick, put up a SHIELD 🛡 before another Degen hacks you, and takes your 💰$GBT.\n\n" let msg = "Beautopia© is a dangerous place... quick, put up a SHIELD 🛡 before another Degen hacks you, and takes your 💰$GBT.\n\n"
+ "To enable it, you need to run the `/shield` slash command.\n\n" + "To enable it, you need to run the `/shield` slash command.\n\n"
+ $"Type the `/shield` command now, then select - `{defaultShield.Item.Name}`\n" + $"Type the `/shield` command now, then select - `{defaultShield.Name}`\n"
let builder = let builder =
DiscordInteractionResponseBuilder() DiscordInteractionResponseBuilder()
.WithContent(msg) .WithContent(msg)
@ -78,7 +80,7 @@ let defend (ctx : IDiscordContext) =
do! Messaging.defer ctx do! Messaging.defer ctx
let m = ctx.GetDiscordMember() let m = ctx.GetDiscordMember()
let name = if System.String.IsNullOrEmpty m.Nickname then m.DisplayName else m.Nickname let name = if System.String.IsNullOrEmpty m.Nickname then m.DisplayName else m.Nickname
let embed = Embeds.pickDefense "Trainer-2" { PlayerData.empty with Inventory = [ Shield defaultShield ] ; Name = name } true let embed = Embeds.pickDefense "Trainer-2" { PlayerData.empty with Inventory = [ shieldItem ] ; Name = name } true
do! ctx.FollowUp(embed) |> Async.AwaitTask do! ctx.FollowUp(embed) |> Async.AwaitTask
do! Analytics.trainingDojoStep "DefendCommand" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username) do! Analytics.trainingDojoStep "DefendCommand" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username)
} |> Async.StartAsTask :> Task } |> Async.StartAsTask :> Task
@ -101,11 +103,11 @@ let handleDefense (ctx : IDiscordContext) =
let embed = Embeds.responseCreatedShield defaultShield let embed = Embeds.responseCreatedShield defaultShield
do! ctx.FollowUp embed |> Async.AwaitTask do! ctx.FollowUp embed |> Async.AwaitTask
do! Async.Sleep 1000 do! Async.Sleep 1000
do! sendMessage' $"Ok, good, let me make sure that worked.\n\nI'll try to **hack** you now with **{defaultHack.Item.Name}**" do! sendMessage' $"Ok, good, let me make sure that worked.\n\nI'll try to **hack** you now with **{defaultHack.Name}**"
do! Async.Sleep 3000 do! Async.Sleep 3000
do! sendMessage' $"❌ HACKING FAILED!\n\n{playerName} defended hack from <@{Sensei.Id}>!" do! sendMessage' $"❌ HACKING FAILED!\n\n{playerName} defended hack from <@{Sensei.Id}>!"
do! Async.Sleep 1500 do! Async.Sleep 1500
do! sendFollowUpMessageWithButton ctx (handleDefenseMsg defaultHack.Item.Name) do! sendFollowUpMessageWithButton ctx (handleDefenseMsg defaultHack.Name)
do! Analytics.trainingDojoStep "ShieldActivated" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username) do! Analytics.trainingDojoStep "ShieldActivated" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username)
} |> Async.StartAsTask :> Task } |> Async.StartAsTask :> Task
@ -117,7 +119,7 @@ let handleTrainerStep3 (ctx : IDiscordContext) =
.WithContent .WithContent
( "Now lets **HACK** 💻... I want you to **HACK ME**!\n\n" ( "Now lets **HACK** 💻... I want you to **HACK ME**!\n\n"
+ "To **hack**, you need to run the `/hack` slash command.\n" + "To **hack**, you need to run the `/hack` slash command.\n"
+ $"Type the `/hack` command now, then choose me - <@{Sensei.Id}> as your target, and select `{defaultHack.Item.Name}`") + $"Type the `/hack` command now, then choose me - <@{Sensei.Id}> as your target, and select `{defaultHack.Name}`")
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
do! Analytics.trainingDojoStep "LetsHack" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username) do! Analytics.trainingDojoStep "LetsHack" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username)
@ -130,7 +132,7 @@ let hack (target : DiscordUser) (ctx : IDiscordContext) =
let isRightTarget = target.Id = Sensei.Id let isRightTarget = target.Id = Sensei.Id
match isRightTarget with match isRightTarget with
| true -> | true ->
let player = { PlayerData.empty with Inventory = [ Hack defaultHack ] } let player = { PlayerData.empty with Inventory = [ hackItem ] }
let bot = { PlayerData.empty with DiscordId = Sensei.Id ; Name = Sensei.Name } let bot = { PlayerData.empty with DiscordId = Sensei.Id ; Name = Sensei.Name }
let embed = Embeds.pickHack "Trainer-4" player bot true let embed = Embeds.pickHack "Trainer-4" player bot true
@ -166,8 +168,8 @@ let handleHack (ctx : IDiscordContext) =
**🎁 FREE GIFTS:** **🎁 FREE GIFTS:**
Here, I'm going to gift you: Here, I'm going to gift you:
1. A hack - `{defaultHack.Item.Name}`, 1. A hack - `{defaultHack.Name}`,
2. A shield - `{defaultShield.Item.Name}`, 2. A shield - `{defaultShield.Name}`,
3. `{CurrencyGift} 💰 $GBT`, 3. `{CurrencyGift} 💰 $GBT`,
I'm also going to give you some **TEMPORARY SHIELDS** until you buy your own. I'm also going to give you some **TEMPORARY SHIELDS** until you buy your own.
@ -194,7 +196,7 @@ let handleArsenal (ctx : IDiscordContext) = PlayerInteractions.executePlayerActi
let! completed = DbService.checkHasAchievement player.DiscordId TrainerAchievement let! completed = DbService.checkHasAchievement player.DiscordId TrainerAchievement
if not completed then if not completed then
do! DbService.addAchievement player.DiscordId TrainerAchievement |> Async.Ignore do! DbService.addAchievement player.DiscordId TrainerAchievement |> Async.Ignore
let addIfDoesntExist (weapon : ItemDetails) (inventory : Inventory) = let addIfDoesntExist (weapon : Item) (inventory : Inventory) =
if inventory |> List.exists (fun i -> i.Id = weapon.Id) if inventory |> List.exists (fun i -> i.Id = weapon.Id)
then inventory then inventory
else weapon::inventory else weapon::inventory
@ -209,8 +211,8 @@ let handleArsenal (ctx : IDiscordContext) = PlayerInteractions.executePlayerActi
|> List.consTo (HackEvent()) |> List.consTo (HackEvent())
Inventory = Inventory =
player.Inventory player.Inventory
|> addIfDoesntExist (Hack defaultHack) |> addIfDoesntExist hackItem
|> addIfDoesntExist (Shield defaultShield) |> addIfDoesntExist shieldItem
Bank = player.Bank + CurrencyGift Bank = player.Bank + CurrencyGift
} }
do! DbService.updatePlayer updatedPlayer |> Async.Ignore do! DbService.updatePlayer updatedPlayer |> Async.Ignore
@ -227,7 +229,7 @@ let handleArsenal (ctx : IDiscordContext) = PlayerInteractions.executePlayerActi
do! ctx.FollowUp(embed) |> Async.AwaitTask do! ctx.FollowUp(embed) |> Async.AwaitTask
do! Async.Sleep 2000 do! Async.Sleep 2000
let rewards = [ $"{defaultHack.Item.Name} Hack" ; $"{defaultShield.Item.Name} Shield" ; $"{CurrencyGift} 💰$GBT"] let rewards = [ $"{defaultHack.Name} Hack" ; $"{defaultShield.Name} Shield" ; $"{CurrencyGift} 💰$GBT"]
let embed = Embeds.getAchievementEmbed rewards "You completed the Training Dojo and collected some gifts." TrainerAchievement let embed = Embeds.getAchievementEmbed rewards "You completed the Training Dojo and collected some gifts." TrainerAchievement
do! ctx.FollowUp(embed) |> Async.AwaitTask do! ctx.FollowUp(embed) |> Async.AwaitTask

View File

@ -52,3 +52,4 @@ let roleAdmin = getId "ROLE_ADMIN"
let mutable botClientRecruit : DiscordClient option = None let mutable botClientRecruit : DiscordClient option = None
let mutable botClientHacker : DiscordClient option = None let mutable botClientHacker : DiscordClient option = None
let mutable botClientSlots : DiscordClient option = None let mutable botClientSlots : DiscordClient option = None
let mutable botClientStore : DiscordClient option = None