Merge branch 'dev' into staging

This commit is contained in:
Joseph Ferano 2022-05-01 17:33:25 +07:00
commit 3e0e500182
17 changed files with 749 additions and 747 deletions

View File

@ -2,6 +2,7 @@ module Degenz.Admin
open System open System
open System.IO open System.IO
open System.Reflection
open System.Threading.Tasks open System.Threading.Tasks
open DSharpPlus open DSharpPlus
open DSharpPlus.Entities open DSharpPlus.Entities
@ -14,6 +15,7 @@ type InitEmbeds =
| Dojo = 0 | Dojo = 0
| Whitelist = 1 | Whitelist = 1
| Slots = 2 | Slots = 2
| JpegStore = 3
let handleGuildDownloadReady (_ : DiscordClient) (event : GuildDownloadCompletedEventArgs) = let handleGuildDownloadReady (_ : DiscordClient) (event : GuildDownloadCompletedEventArgs) =
task { task {
@ -24,6 +26,7 @@ let handleGuildDownloadReady (_ : DiscordClient) (event : GuildDownloadCompleted
let permission = DiscordApplicationCommandPermission(adminRole, true) let permission = DiscordApplicationCommandPermission(adminRole, true)
let commands = commands |> Seq.map (fun com -> DiscordGuildApplicationCommandPermissions(com.Id, [ permission ])) let commands = commands |> Seq.map (fun com -> DiscordGuildApplicationCommandPermissions(com.Id, [ permission ]))
do! guild.BatchEditApplicationCommandPermissionsAsync(commands) |> Async.AwaitTask |> Async.Ignore do! guild.BatchEditApplicationCommandPermissionsAsync(commands) |> Async.AwaitTask |> Async.Ignore
return ()
} :> Task } :> Task
let sendEmbed embed (ctx : IDiscordContext) = let sendEmbed embed (ctx : IDiscordContext) =
@ -32,6 +35,7 @@ let sendEmbed embed (ctx : IDiscordContext) =
| InitEmbeds.Dojo -> Trainer.sendInitialEmbed ctx | InitEmbeds.Dojo -> Trainer.sendInitialEmbed ctx
| InitEmbeds.Whitelist -> InviteTracker.sendInitialEmbed ctx | InitEmbeds.Whitelist -> InviteTracker.sendInitialEmbed ctx
| InitEmbeds.Slots -> SlotMachine.sendInitialEmbedFromSlashCommand ctx | InitEmbeds.Slots -> SlotMachine.sendInitialEmbedFromSlashCommand ctx
| InitEmbeds.JpegStore -> Store.sendInitialEmbed ctx
| _ -> () | _ -> ()
do! Messaging.sendSimpleResponse ctx "Sent!" do! Messaging.sendSimpleResponse ctx "Sent!"
} :> Task } :> Task
@ -137,24 +141,29 @@ type AdminBot() =
else else
Messaging.sendSimpleResponse ctx $"You are not admin" |> Async.StartAsTask :> Task Messaging.sendSimpleResponse ctx $"You are not admin" |> Async.StartAsTask :> Task
[<SlashCommandPermissions(Permissions.Administrator)>]
[<SlashCommand("admin-invites", "Get total invites from a specific user", false)>] [<SlashCommand("admin-invites", "Get total invites from a specific user", false)>]
member this.GetAttributions (ctx : InteractionContext, [<Option("player", "The player you want to check")>] user : DiscordUser) = member this.GetAttributions (ctx : InteractionContext, [<Option("player", "The player you want to check")>] user : DiscordUser) =
enforceAdmin (DiscordInteractionContext ctx) (InviteTracker.getInvitedUsersForId user) enforceAdmin (DiscordInteractionContext ctx) (InviteTracker.getInvitedUsersForId user)
[<SlashCommandPermissions(Permissions.Administrator)>]
[<SlashCommand("admin-whitelist-stock", "Set whitelist stock", false)>] [<SlashCommand("admin-whitelist-stock", "Set whitelist stock", false)>]
member this.SetStock (ctx : InteractionContext, [<Option("amount", "Set the amount of WL available for purchase")>] amount : int64) = member this.SetStock (ctx : InteractionContext, [<Option("amount", "Set the amount of WL available for purchase")>] amount : int64) =
enforceAdmin (DiscordInteractionContext ctx) (InviteTracker.setWhitelistStock (int amount)) enforceAdmin (DiscordInteractionContext ctx) (InviteTracker.setCurrentWhitelistStock (int amount))
[<SlashCommandPermissions(Permissions.Administrator)>]
[<SlashCommand("admin-send-embed", "Set whitelist stock", false)>] [<SlashCommand("admin-send-embed", "Set whitelist stock", false)>]
member this.SendEmbedToChannel (ctx : InteractionContext, [<Option("embed", "Which embed to send")>] embed : InitEmbeds) = member this.SendEmbedToChannel (ctx : InteractionContext, [<Option("embed", "Which embed to send")>] embed : InitEmbeds) =
enforceAdmin (DiscordInteractionContext ctx) (sendEmbed embed) enforceAdmin (DiscordInteractionContext ctx) (sendEmbed embed)
[<SlashCommandPermissions(Permissions.Administrator)>]
[<SlashCommand("admin-get-msg-reactions", "Set whitelist stock", false)>] [<SlashCommand("admin-get-msg-reactions", "Set whitelist stock", false)>]
member this.GetMessageReactions (ctx : InteractionContext, member this.GetMessageReactions (ctx : InteractionContext,
[<Option("channel", "The channel where the message is")>] channel : DiscordChannel, [<Option("channel", "The channel where the message is")>] channel : DiscordChannel,
[<Option("message-id", "The ID of the message with all the reactions")>] messageId : string) = [<Option("message-id", "The ID of the message with all the reactions")>] messageId : string) =
enforceAdmin (DiscordInteractionContext ctx) (getUsersFromMessageReactions channel messageId) enforceAdmin (DiscordInteractionContext ctx) (getUsersFromMessageReactions channel messageId)
[<SlashCommandPermissions(Permissions.Administrator)>]
[<SlashCommand("admin-get-invites-table", "Invites Table", false)>] [<SlashCommand("admin-get-invites-table", "Invites Table", false)>]
member this.GetInvitesFromReactedMessages (ctx : InteractionContext, member this.GetInvitesFromReactedMessages (ctx : InteractionContext,
[<Option("channel", "The channel where the message is")>] channel : DiscordChannel, [<Option("channel", "The channel where the message is")>] channel : DiscordChannel,

View File

@ -94,33 +94,33 @@ let arsenalCommand (discordMember : DiscordMember) =
] ]
track "Arsenal Command Invoked" discordMember.Id data track "Arsenal Command Invoked" discordMember.Id data
let buyWeaponCommand (discordMember : DiscordMember) weaponType = let buyItemCommand (discordMember : DiscordMember) store =
let data = [ let data = [
"user_display_name" , discordMember.Username "user_display_name" , discordMember.Username
"weapon_type" , string weaponType "store_symbol" , store
] ]
track "Buy Weapon Command Invoked" discordMember.Id data track "Buy Item Command Invoked" discordMember.Id data
let sellWeaponCommand (discordMember : DiscordMember) weaponType = let sellItemCommand (discordMember : DiscordMember) store =
let data = [ let data = [
"user_display_name" , discordMember.Username "user_display_name" , discordMember.Username
"weapon_type" , string weaponType "store_symbol" , store
] ]
track "Sell Weapon Command Invoked" discordMember.Id data track "Sell Item 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

@ -77,7 +77,6 @@ inviterBot.add_ComponentInteractionCreated(AsyncEventHandler(InviteTracker.handl
slotsBot.add_ComponentInteractionCreated(AsyncEventHandler(SlotMachine.handleButton)) slotsBot.add_ComponentInteractionCreated(AsyncEventHandler(SlotMachine.handleButton))
slotsBot.add_GuildDownloadCompleted(AsyncEventHandler(SlotMachine.handleGuildDownloadCompleted)) slotsBot.add_GuildDownloadCompleted(AsyncEventHandler(SlotMachine.handleGuildDownloadCompleted))
slotsBot.add_MessageCreated(AsyncEventHandler(SlotMachine.handleMessageCreated)) slotsBot.add_MessageCreated(AsyncEventHandler(SlotMachine.handleMessageCreated))
adminBot.add_GuildDownloadCompleted(AsyncEventHandler(Admin.handleGuildDownloadReady))
let asdf (_ : DiscordClient) (event : DSharpPlus.EventArgs.InteractionCreateEventArgs) = let asdf (_ : DiscordClient) (event : DSharpPlus.EventArgs.InteractionCreateEventArgs) =
async { async {
@ -95,15 +94,15 @@ let asdf (_ : DiscordClient) (event : DSharpPlus.EventArgs.InteractionCreateEven
:> Task :> Task
//hackerBattleBot.add_InteractionCreated(AsyncEventHandler(asdf)) //hackerBattleBot.add_InteractionCreated(AsyncEventHandler(asdf))
storeBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
GuildEnvironment.botClientStore <- Some storeBot
slotsBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously slotsBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
GuildEnvironment.botClientSlots <- Some slotsBot GuildEnvironment.botClientSlots <- Some slotsBot
hackerBattleBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously hackerBattleBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
GuildEnvironment.botClientHacker <- Some hackerBattleBot GuildEnvironment.botClientHacker <- Some hackerBattleBot
storeBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
//GuildEnvironment.botClient <- Some storeBot.CurrentUser
inviterBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously inviterBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
GuildEnvironment.botClientRecruit <- Some inviterBot GuildEnvironment.botClientRecruit <- Some inviterBot

View File

@ -7,9 +7,6 @@
<RootNamespace>Degenz</RootNamespace> <RootNamespace>Degenz</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="Items.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="paket.references" /> <Content Include="paket.references" />
<Compile Include="Prelude.fs" /> <Compile Include="Prelude.fs" />
<Compile Include="GuildEnvironment.fs" /> <Compile Include="GuildEnvironment.fs" />

View File

@ -1,11 +1,149 @@
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.Description = reader.string "description"
Item.IconUrl = reader.string "icon"
Item.Symbol = reader.string "symbol"
Item.Type =
match reader.string "category" with
| "Hack" -> ItemType.Hack
| "Shield" -> ItemType.Shield
| "Food" -> ItemType.Food
| "Accessory" -> ItemType.Accessory
| "Jpeg" -> ItemType.Jpeg
| _ -> ItemType.Misc
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,ii.symbol,name,description,icon,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 getStoreSymbol (channelId : uint64) =
connStr
|> Sql.connect
|> Sql.parameters [ "cid", Sql.string (string channelId) ]
|> Sql.query """
SELECT symbol FROM store WHERE channel_id = @cid
"""
|> Sql.executeRowAsync (fun read -> read.string "symbol")
|> Async.AwaitTask
let getStoreItems (channelId : uint64) =
connStr
|> Sql.connect
|> Sql.parameters [ "cid", Sql.string (string channelId) ]
|> Sql.query """
SELECT stock,available,limit_stock,i.id,i.symbol,name,description,icon,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 (fun reader -> {
Stock = reader.int "stock"
LimitStock = reader.bool "limit_stock"
Available = reader.bool "available"
StoreItem.Item = readItem reader
})
|> Async.AwaitTask
let decrementItemStock (item : Item) =
connStr
|> Sql.connect
|> Sql.parameters [ ( "iid" , Sql.int item.Id) ]
|> Sql.query """
UPDATE store_item SET stock = GREATEST(stock - 1, 0)
WHERE store_item.item_id = @iid
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
|> Async.Ignore
let getWeapons () =
connStr
|> Sql.connect
|> Sql.query """
SELECT i.id,i.symbol,name,description,icon,category,buy_price,sell_price,rate_limit,expiration,drop_chance,can_trade,can_consume,
attack_power,defense_power,class_name,max_stack,mods
FROM item WHERE category = 'Hack' OR symbol = 'Shield'
"""
|> Sql.executeAsync readItem
|> Async.AwaitTask
let consumeItem (did : uint64) (item : Item) =
connStr
|> Sql.connect
|> Sql.parameters [ ( "@did" , Sql.string (string did) ) ; ( "iid" , Sql.int item.Id )]
|> Sql.query """
DELETE FROM inventory_item
WHERE id IN (SELECT id FROM inventory_item
WHERE user_id = (SELECT id FROM "user" WHERE discord_id = @did)
AND item_id = @iid LIMIT 1)
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
let getPlayerEvents (did : uint64) = let getPlayerEvents (did : uint64) =
connStr connStr
|> Sql.connect |> Sql.connect
@ -34,36 +172,6 @@ let getPlayerEvents (did : uint64) =
) )
|> Async.AwaitTask |> Async.AwaitTask
let getLastPlayedSlotFromPlayer (did : uint64) = async {
let! events =
connStr
|> Sql.connect
|> Sql.parameters [ "did", Sql.string (string did) ]
|> Sql.query """
SELECT player_event.updated_at FROM player_event
JOIN "user" u on u.id = player_event.user_id
WHERE u.discord_id = @did AND event_type = 'PlayingSlot'
"""
|> Sql.executeAsync (fun read -> read.dateTime "updated_at" )
|> Async.AwaitTask
match events with
| [] -> return None
| es -> return Some (List.head es)
}
let updateSlotPlayedFromPlayer (did : uint64) =
connStr
|> Sql.connect
|> Sql.parameters [ "did", Sql.string (string did) ]
|> Sql.query """
WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did)
UPDATE player_event SET updated_at = now() at time zone 'utc'
FROM usr WHERE usr.id = user_id AND player_event.event_type = 'PlayingSlot';
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
|> Async.Ignore
let updatePlayerStats (player : PlayerData) = let updatePlayerStats (player : PlayerData) =
connStr connStr
|> Sql.connect |> Sql.connect
@ -89,16 +197,13 @@ 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
{|
DiscordId = read.string "discord_id" |> uint64
Name = read.string "display_name" Name = read.string "display_name"
Bank = read.intOrNone "gbt" |> Option.map ((*) 1<GBT>) |> Option.defaultValue 0<GBT> Bank = read.intOrNone "gbt" |> Option.map ((*) 1<GBT>) |> Option.defaultValue 0<GBT>
Inventory = inv |> Array.toList
Strength = read.intOrNone "strength" |> Option.defaultValue 0 Strength = read.intOrNone "strength" |> Option.defaultValue 0
Focus = read.intOrNone "focus" |> Option.defaultValue 0 Focus = read.intOrNone "focus" |> Option.defaultValue 0
Charisma = read.intOrNone "charisma" |> Option.defaultValue 0 Charisma = read.intOrNone "charisma" |> Option.defaultValue 0
@ -110,7 +215,7 @@ let tryFindPlayer (discordId : uint64) = async {
| 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
@ -135,7 +240,7 @@ let updatePlayerCurrency (addAmount : int<GBT>) (player : PlayerData) =
"did", Sql.string (string player.DiscordId) "did", Sql.string (string player.DiscordId)
"gbt", Sql.int (int addAmount) "gbt", Sql.int (int addAmount)
] |> Sql.query """ ] |> Sql.query """
UPDATE "user" SET gbt = gbt + @gbt WHERE discord_id = @did; UPDATE "user" SET gbt = gbt + GREATEST(gbt + @gbt, 0) WHERE discord_id = @did;
""" """
|> Sql.executeNonQueryAsync |> Sql.executeNonQueryAsync
|> Async.AwaitTask |> Async.AwaitTask
@ -146,14 +251,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
@ -283,40 +386,3 @@ let getRandomHackablePlayers (did : uint64) =
|> Sql.executeAsync (fun read -> {| Id = read.string "discord_id" |> uint64 ; Name = read.string "display_name" |}) |> Sql.executeAsync (fun read -> {| Id = read.string "discord_id" |> uint64 ; Name = read.string "display_name" |})
|> Async.AwaitTask |> Async.AwaitTask
let getWhitelistItem () =
connStr
|> Sql.connect
|> Sql.query """
SELECT stock, price FROM item WHERE symbol = 'WHITELIST'
"""
|> Sql.executeRowAsync (fun read -> {| Stock = read.int "stock" ; Price = (read.int "price") * 1<GBT> |})
|> Async.AwaitTask
let updateWhitelistStock () = async {
try
do! connStr
|> Sql.connect
|> Sql.query """
UPDATE item SET stock = stock - 1 WHERE symbol = 'WHITELIST'
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
|> Async.Ignore
return true
with _ -> return false
}
let setWhitelistStock amount = async {
try
do! connStr
|> Sql.connect
|> Sql.parameters [ ( "amount" , Sql.int amount ) ]
|> Sql.query """
UPDATE item SET stock = @amount WHERE symbol = 'WHITELIST'
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
|> Async.Ignore
return true
with _ -> return false
}

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

@ -4,63 +4,77 @@ open System
open DSharpPlus open DSharpPlus
open DSharpPlus.Entities open DSharpPlus.Entities
open Degenz open Degenz
open Newtonsoft.Json
module Armory =
let weapons : ItemDetails list =
let file = System.IO.File.ReadAllText("Items.json")
// let file = System.IO.File.ReadAllText("Bot/Items.json")
JsonConvert.DeserializeObject<ItemDetails array>(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.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)
| ItemType.Jpeg -> inventory |> List.filter (fun item -> match item.Type with ItemType.Jpeg _ -> true | _ -> false)
| ItemType.Misc -> inventory |> List.filter (fun item -> match item.Type with ItemType.Misc _ -> 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 getHackItem item =
inventory |> List.pick (fun item -> match item with | Hack h -> (if h.Item.Id = id then Some h else None) | _ -> None) match item.Type , item.Attributes with
let findShieldById id inventory = | ItemType.Hack , CanBuy buyPrice & CanSell _ & CanAttack power & CanExpire cooldown & CanClass ``class``->
inventory |> List.pick (fun item -> match item with | Shield s -> (if s.Item.Id = id then Some s else None) | _ -> None) Some { Id = item.Id
let findFoodById id inventory = Name = item.Name
inventory |> List.pick (fun item -> match item with | Food f -> (if f.Item.Id = id then Some f else None) | _ -> None) Price = buyPrice
let findAccessoryById id inventory = Cooldown = cooldown
inventory |> List.pick (fun item -> match item with | Accessory a -> (if a.Item.Id = id then Some a else None) | _ -> None) Power = power
Class = ``class`` }
| _ -> None
let getHacks inventory = let getShieldItem item =
inventory |> List.choose (fun item -> match item with | Hack h -> Some h | _ -> None) match item.Type , item.Attributes with
let getShields inventory = | ItemType.Shield , CanBuy buyPrice & CanSell _ & CanDefend resistance & CanExpire cooldown & CanClass ``class`` ->
inventory |> List.choose (fun item -> match item with | Shield s -> Some s | _ -> None) Some { Id = item.Id
Name = item.Name
Price = buyPrice
Cooldown = cooldown
Resistance = resistance
Class = ``class`` }
| _ -> None
// 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)
// 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)
// 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)
// 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)
//
let getHacks inventory = inventory |> List.choose getHackItem
let getShields inventory = inventory |> List.choose getShieldItem
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.Type with | ItemType.Food -> Some item | _ -> 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.Type with | ItemType.Accessory -> Some item | _ -> 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 =
@ -82,32 +96,48 @@ 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 ; Luck ; Charisma ] let stats = [ Strength ; Focus ; Charisma ; Luck ]
let getPlayerStat (statConfig : StatConfig) player =
match statConfig.Id with
| StatId.Strength -> player.Stats.Strength
| StatId.Focus -> player.Stats.Focus
| StatId.Charisma -> player.Stats.Charisma
| StatId.Luck -> player.Stats.Luck
| _ -> player.Stats.Luck
let calculateActiveStat statId amount items = let calculateActiveStat statId amount items =
let statConfig = stats |> List.find (fun s -> s.Id = statId) let statConfig = stats |> List.find (fun s -> s.Id = statId)
// let hoursElapsed = (DateTime.UtcNow - lastRead).Hours // let hoursElapsed = (DateTime.UtcNow - lastRead).Hours
// let totalDecay = float hoursElapsed * statConfig.BaseDecayRate // let totalDecay = float hoursElapsed * statConfig.BaseDecayRate
let modMinMax = let modMinMax =
let min = items |> List.sumBy (fun item -> match item with | Accessory a -> a.FloorBoost | _ -> 0) let min =
let max = items |> List.sumBy (fun item -> match item with | Accessory a -> a.CeilBoost | _ -> 0) items
|> List.choose (fun i -> match i.Attributes with CanModify fx -> Some fx | _ -> None)
|> List.concat
|> List.sumBy (fun fx -> match fx.Effect with | Min x -> x | _ -> 0)
let max =
items
|> List.choose (fun i -> match i.Attributes with CanModify fx -> Some fx | _ -> None)
|> 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 ", "
let actionFormat (actions : PlayerEvent List) = let actionFormat items (actions : PlayerEvent List) =
match actions with match actions with
| [] -> "None" | [] -> "None"
| acts -> | acts ->
@ -115,13 +145,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 = items |> 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 = items |> 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"
@ -130,5 +160,5 @@ module Arsenal =
let hacks = Player.getHackEvents p let hacks = Player.getHackEvents p
$"**Hacks:** {Inventory.getItemsByType ItemType.Hack p.Inventory |> battleItemFormat}\n $"**Hacks:** {Inventory.getItemsByType ItemType.Hack p.Inventory |> battleItemFormat}\n
**Shields:** {Inventory.getItemsByType ItemType.Shield p.Inventory |> battleItemFormat}\n **Shields:** {Inventory.getItemsByType ItemType.Shield p.Inventory |> battleItemFormat}\n
**Hack Attacks:**\n{hacks |> List.take (min hacks.Length 10) |> actionFormat}\n **Hack Attacks:**\n{hacks |> List.take (min hacks.Length 10) |> actionFormat p.Inventory}\n
**Active Shields:**\n{Player.getShieldEvents p |> actionFormat}" **Active Shields:**\n{Player.getShieldEvents p |> actionFormat p.Inventory}"

View File

@ -2,7 +2,6 @@
module Degenz.Types module Degenz.Types
open System open System
open Degenz
[<Measure>] [<Measure>]
type mins type mins
@ -89,84 +88,83 @@ type ItemType =
| Shield | Shield
| Food | Food
| Accessory | Accessory
| Jpeg
| Misc
type ItemAttributes = { type Effect =
CanBuy : bool | Min of int
CanSell : bool | Max of int
CanConsume : bool | Add 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>
| RateLimitable of cooldown : int<mins>
| Expireable of lifetime : int<mins>
| Consumable
| Modifiable of effects : StatEffect list
| Droppable of chance : float
| Tradeable
| 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 (|CanRateLimit|_|) itemAttrs = itemAttrs |> List.tryPick (function RateLimitable l -> Some l | _ -> None)
let (|CanConsume|_|) itemAttrs = itemAttrs |> List.tryPick (function Consumable -> Some () | _ -> None)
let (|CanModify|_|) itemAttrs = itemAttrs |> List.tryPick (function Modifiable 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> Description : string
Attributes : ItemAttributes Type : ItemType
Symbol : string
IconUrl : string
Attributes : ItemAttribute list
}
type StoreItem = {
Stock : int
LimitStock : bool
Available : bool
Item : Item
} }
type HackItem = { type HackItem = {
Power : int Id : int
Class : int Name : string
Price : int<GBT>
Cooldown : int<mins> Cooldown : int<mins>
Item : Item Power : int
Class : string
} }
type ShieldItem = { type ShieldItem = {
Class : int Id : int
Name : string
Price : int<GBT>
Cooldown : int<mins> Cooldown : int<mins>
Item : Item Resistance : int
Class : string
} }
type FoodItem = { type Inventory = Item list
TargetStat : StatId
BoostAmount : int
Item : Item
}
type AccessoryItem = {
TargetStat : StatId
FloorBoost : int
CeilBoost : int
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
@ -190,3 +188,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

@ -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 Trainer.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,27 @@ 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 // TODO: This sucks
let item = Trainer.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 +207,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 = Trainer.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 +230,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

@ -103,30 +103,6 @@ let reel1 = getReel (fun s -> s.reel1Count)
let reel2 = getReel (fun s -> s.reel2Count) let reel2 = getReel (fun s -> s.reel2Count)
let reel3 = getReel (fun s -> s.reel3Count) let reel3 = getReel (fun s -> s.reel3Count)
let slots =
[| "https://s7.gifyu.com/images/aneye.png"
"https://s7.gifyu.com/images/anonmask.png"
"https://s7.gifyu.com/images/circuitboard.png"
"https://s7.gifyu.com/images/obey.png"
"https://s7.gifyu.com/images/oldtv.png"
"https://s7.gifyu.com/images/pills.png"
"https://s7.gifyu.com/images/pizza0d47578733961746.png"
"https://s7.gifyu.com/images/ramen0515f00869e1f4eb.png"
"https://s7.gifyu.com/images/rat69609f842a0eb9f5.png"
"https://s7.gifyu.com/images/alcohol.png"
"https://s7.gifyu.com/images/bigbrother.png"
"https://s7.gifyu.com/images/sushi.png" |]
// [| "https://s7.gifyu.com/images/A-bottle-of-pills0a3006d0170e08df.png"
// "https://s7.gifyu.com/images/an-eyec362d8152ae2382b.png"
// "https://s7.gifyu.com/images/anon-face-mask6c7624821c89fc08.png"
// "https://s7.gifyu.com/images/a-piece-of-sushi77071d30f60a89c6.png"
// "https://s7.gifyu.com/images/Circuit-board89056017b80f1d13.png"
// "https://s7.gifyu.com/images/OBEYf2a8234109836c03.png"
// "https://s7.gifyu.com/images/old-tv-screendc6bc9d4b6c1fd65.png"
// "https://s7.gifyu.com/images/pizza030ffc00ff50da0e.png"
// "https://s7.gifyu.com/images/ramen08336d448018c98f.png"
// "https://s7.gifyu.com/images/rat14f65f54f0d75036.png" |]
let slotEmojiNames = let slotEmojiNames =
[| "sushi" [| "sushi"
"bigbrother" "bigbrother"
@ -152,7 +128,10 @@ let mutable anyEmoji : DiscordEmoji option = None
let getJackpotAmount () = let getJackpotAmount () =
GuildEnvironment.connectionString GuildEnvironment.connectionString
|> Sql.connect |> Sql.connect
|> Sql.query "SELECT stock FROM item WHERE symbol = 'JACKPOT'" |> Sql.query """
SELECT stock FROM store_item
WHERE store_item.item_id = (SELECT id FROM item WHERE symbol = 'JACKPOT')
"""
|> Sql.executeRowAsync (fun read -> (read.int "stock") * 1<GBT>) |> Sql.executeRowAsync (fun read -> (read.int "stock") * 1<GBT>)
|> Async.AwaitTask |> Async.AwaitTask
@ -160,7 +139,10 @@ let incrementJackpot amount =
GuildEnvironment.connectionString GuildEnvironment.connectionString
|> Sql.connect |> Sql.connect
|> Sql.parameters [ ( "amount" , Sql.int (int amount) ) ] |> Sql.parameters [ ( "amount" , Sql.int (int amount) ) ]
|> Sql.query "UPDATE item SET stock = stock + @amount WHERE symbol = 'JACKPOT'" |> Sql.query """
UPDATE store_item SET stock = stock + @amount
WHERE store_item.item_id = (SELECT id FROM item WHERE symbol = 'JACKPOT')
"""
|> Sql.executeNonQueryAsync |> Sql.executeNonQueryAsync
|> Async.AwaitTask |> Async.AwaitTask
@ -168,10 +150,43 @@ let resetJackpot amount =
GuildEnvironment.connectionString GuildEnvironment.connectionString
|> Sql.connect |> Sql.connect
|> Sql.parameters [ ( "amount" , Sql.int (int amount) ) ] |> Sql.parameters [ ( "amount" , Sql.int (int amount) ) ]
|> Sql.query "UPDATE item SET stock = @amount WHERE symbol = 'JACKPOT'" |> Sql.query """
UPDATE store_item SET stock = @amount
WHERE store_item.item_id = (SELECT id FROM item WHERE symbol = 'JACKPOT')
"""
|> Sql.executeNonQueryAsync |> Sql.executeNonQueryAsync
|> Async.AwaitTask |> Async.AwaitTask
let getLastPlayedSlotFromPlayer (did : uint64) = async {
let! events =
GuildEnvironment.connectionString
|> Sql.connect
|> Sql.parameters [ "did", Sql.string (string did) ]
|> Sql.query """
SELECT player_event.updated_at FROM player_event
JOIN "user" u on u.id = player_event.user_id
WHERE u.discord_id = @did AND event_type = 'PlayingSlot'
"""
|> Sql.executeAsync (fun read -> read.dateTime "updated_at" )
|> Async.AwaitTask
match events with
| [] -> return None
| es -> return Some (List.head es)
}
let updateSlotPlayedFromPlayer (did : uint64) =
GuildEnvironment.connectionString
|> Sql.connect
|> Sql.parameters [ "did", Sql.string (string did) ]
|> Sql.query """
WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did)
UPDATE player_event SET updated_at = now() at time zone 'utc'
FROM usr WHERE usr.id = user_id AND player_event.event_type = 'PlayingSlot';
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
|> Async.Ignore
let handlePrizeTable (ctx : IDiscordContext) = let handlePrizeTable (ctx : IDiscordContext) =
task { task {
do! Messaging.defer ctx do! Messaging.defer ctx
@ -321,10 +336,10 @@ let spin multiplier (ctx : IDiscordContext) =
} }
PlayerInteractions.executePlayerAction ctx (fun player -> async { PlayerInteractions.executePlayerAction ctx (fun player -> async {
if player.Bank >= playAmount then if player.Bank >= playAmount then
match! DbService.getLastPlayedSlotFromPlayer player.DiscordId with match! getLastPlayedSlotFromPlayer player.DiscordId with
| Some timestamp -> | Some timestamp ->
if DateTime.UtcNow - timestamp > TimeSpan.FromSeconds(8) then if DateTime.UtcNow - timestamp > TimeSpan.FromSeconds(8) then
do! DbService.updateSlotPlayedFromPlayer player.DiscordId do! updateSlotPlayedFromPlayer player.DiscordId
do! execute player do! execute player
else else
do! Messaging.sendFollowUpMessage ctx "Wait till you finish the current spin!" do! Messaging.sendFollowUpMessage ctx "Wait till you finish the current spin!"

View File

@ -10,97 +10,128 @@ open Degenz
open Degenz.Messaging open Degenz.Messaging
open Degenz.PlayerInteractions open Degenz.PlayerInteractions
let getBuyItemsEmbed (playerInventory : Inventory) (storeInventory : Inventory) = let getItemEmbeds owned (items : StoreItem list) =
let embeds , buttons = items
|> List.countBy (fun item -> item.Item.Id)
|> List.map (fun (id,count) -> items |> List.find (fun i -> i.Item.Id = id) , count )
|> List.map (fun (item,count) ->
let embed = DiscordEmbedBuilder()
if not owned && item.LimitStock then
embed.AddField("Stock", $"{item.Stock}", true) |> ignore
item.Item.Attributes
|> List.iter (function
| Buyable price -> embed.AddField("Price 💰", (if price = 0<GBT> then "Free" else $"{price} $GBT"), true) |> ignore
| Attackable power ->
let title = match item.Item.Type with ItemType.Hack -> "$GBT Reward" | _ -> "Power"
embed.AddField($"{title} |", string power, true) |> ignore
| RateLimitable time ->
let title = match item.Item.Type with ItemType.Hack -> "Cooldown" | ItemType.Shield -> "Active For" | _ -> "Expires"
let ts = TimeSpan.FromMinutes(int time)
let timeStr = if ts.Hours = 0 then $"{ts.Minutes} mins" else $"{ts.Hours} hours"
embed.AddField($"{title} |", timeStr, true) |> ignore
| Stackable max ->
if owned then
embed.AddField($"Total Owned |", $"{count}", true) |> ignore
else
embed.AddField($"Max Allowed |", $"{max}", true) |> ignore
| Modifiable effects ->
let fx =
effects
|> List.map (fun f ->
match f.Effect with
| Min i -> $"{f.TargetStat} Min + {i}"
| Max i -> $"{f.TargetStat} Max + {i}"
| Add i ->
let str = if i > 0 then "Boost" else "Penalty"
$"{f.TargetStat} {str} + {i}"
| RateMultiplier i -> $"{f.TargetStat} Multiplier - i")
|> String.concat "\n"
embed.AddField($"Effect - Amount ", $"{fx}", true) |> ignore
| _ -> ())
embed
.WithColor(WeaponClass.getClassEmbedColor item.Item)
.WithTitle($"{item.Item.Name}")
|> ignore
match Embeds.getItemIcon item.Item.Id with
| Some url -> embed.WithThumbnail(url)
| None -> if String.IsNullOrWhiteSpace(item.Item.IconUrl) then embed else embed.WithThumbnail(item.Item.IconUrl))
|> List.map (fun e -> e.Build())
|> Seq.ofList
let getBuyItemsEmbed (playerInventory : Inventory) (storeInventory : StoreItem list) =
let embeds = getItemEmbeds false storeInventory
let buttons =
storeInventory storeInventory
|> List.map (fun item -> |> List.map (fun item ->
let embed = DiscordEmbedBuilder() let owned = playerInventory |> List.exists (fun i -> i.Id = item.Item.Id)
match item with let inStock = item.Available && (item.Stock > 0 || item.LimitStock = false)
| Hack hack -> match owned , inStock with
embed.AddField($"$GBT Reward |", string hack.Power, true) | false , true -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}", $"Buy {item.Item.Name}")
.AddField("Cooldown |", $"{TimeSpan.FromMinutes(int hack.Cooldown).Minutes} minutes", true) | false , false -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}", $"{item.Item.Name} (Out of Stock)", true)
.WithThumbnail(Embeds.getItemIcon item.Id) | true , _ -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}", $"Own {item.Item.Name}", true)
|> ignore :> DiscordComponent)
| Shield shield ->
embed.AddField($"Strong against |", WeaponClass.getGoodAgainst shield.Class |> snd |> string, true)
// .AddField($"Defensive Strength |", string item.Power, true)
.AddField("Active For |", $"{TimeSpan.FromMinutes(int shield.Cooldown).Hours} hours", true)
.WithThumbnail(Embeds.getItemIcon item.Id)
|> ignore
| Food food ->
embed.AddField($"Stat |", $"{food.TargetStat}", true)
.AddField($"Amount |", $"+{food.BoostAmount}", true) |> ignore
| Accessory accessory ->
embed.AddField($"Stat |", $"{accessory.TargetStat}", true) |> ignore
if accessory.FloorBoost > 0 then
embed.AddField($"Min Boost |", $"+{accessory.FloorBoost}", true) |> ignore
if accessory.CeilBoost > 0 then
embed.AddField($"Max Boost |", $"+{accessory.CeilBoost}", true) |> ignore
embed
.AddField("Price 💰", (if item.Price = 0<GBT> then "Free" else $"{item.Price} $GBT"), true)
.WithColor(WeaponClass.getClassEmbedColor item)
.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}")
( embed.Build() , button :> DiscordComponent ))
|> List.unzip
let builder =
DiscordFollowupMessageBuilder() DiscordFollowupMessageBuilder()
.AddEmbeds(embeds) .AddEmbeds(embeds)
.AddComponents(buttons)
.AsEphemeral(true) .AsEphemeral(true)
buttons
|> List.chunkBySize 5
|> List.iter (fun btns -> builder.AddComponents(btns) |> ignore)
builder
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 ->
match item.Attributes with
| CanSell price ->
let builder =
DiscordEmbedBuilder() DiscordEmbedBuilder()
.AddField("Sell For 💰", $"{item.Price} $GBT", true) .AddField("Sell For 💰", $"{price} $GBT", true)
.WithTitle($"{item.Name}") .WithTitle($"{item.Name}")
.WithColor(WeaponClass.getClassEmbedColor item) .WithColor(WeaponClass.getClassEmbedColor item)
.Build() .Build()
, DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Sell-{item.Id}", $"Sell {item.Name}") :> DiscordComponent) let button = DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Sell-{item.Id}", $"Sell {item.Name}") :> DiscordComponent
Some ( builder , button )
| _ -> None)
|> 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 getConsumeEmbed (items : ItemDetails list) = let checkHasStock (item : StoreItem) player =
let embeds , buttons = if item.Stock > 0 || item.LimitStock = false
items then Ok player
|> List.groupBy (fun item -> item.Id) else Error $"{item.Item.Name} is out of stock! Check back later to purchase"
|> 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
DiscordFollowupMessageBuilder()
.AddEmbeds(embeds)
.AddComponents(buttons)
.AsEphemeral(true)
let checkHasSufficientFunds (item : Item) player = let checkHasSufficientFunds (item : Item) player =
if player.Bank - item.Price >= 0<GBT> match item.Attributes with
| CanBuy price ->
if player.Bank - price >= 0<GBT>
then Ok player then Ok 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"
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
|> 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 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."
@ -110,13 +141,19 @@ let checkHasItemsInArsenal itemType items player =
then Ok player then Ok player
else Error $"You currently have no {itemType} in your arsenal to sell!" else Error $"You currently have no {itemType} in your arsenal to sell!"
let buy itemType (ctx : IDiscordContext) = let buy (ctx : IDiscordContext) =
executePlayerAction ctx (fun player -> async { executePlayerAction ctx (fun player -> async {
let playerItems = Inventory.getItemsByType itemType player.Inventory try
let armoryItems = Inventory.getItemsByType itemType Armory.weapons let channelId = ctx.GetChannel().Id
let itemStore = getBuyItemsEmbed playerItems armoryItems let! items = DbService.getStoreItems channelId
if items.Length > 0 then
let itemStore = getBuyItemsEmbed player.Inventory items
do! ctx.FollowUp itemStore |> Async.AwaitTask do! ctx.FollowUp itemStore |> Async.AwaitTask
do! Analytics.buyWeaponCommand (ctx.GetDiscordMember()) itemType let! storeSymbol = DbService.getStoreSymbol channelId
do! Analytics.buyItemCommand (ctx.GetDiscordMember()) storeSymbol
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) =
@ -126,47 +163,123 @@ let sell itemType getItems (ctx : IDiscordContext) =
| Ok _ -> let itemStore = getSellEmbed items | Ok _ -> let itemStore = getSellEmbed items
do! ctx.FollowUp(itemStore) |> Async.AwaitTask do! ctx.FollowUp(itemStore) |> Async.AwaitTask
| Error e -> do! sendFollowUpMessage ctx e | Error e -> do! sendFollowUpMessage ctx e
do! Analytics.sellWeaponCommand (ctx.GetDiscordMember()) itemType do! Analytics.buyItemCommand (ctx.GetDiscordMember()) itemType
}) })
let purchaseItemEmbed (item : Item) =
let embed = DiscordEmbedBuilder()
embed.ImageUrl <- item.IconUrl
embed.Title <- $"Purchased {item.Name}"
match item.Type with
| ItemType.Jpeg ->
if item.Symbol.Contains "RAFFLE" then
embed.Description <- $"Congratulations! You are in the draw for the {item.Name}. The winner will be announced shortly"
embed.ImageUrl <- item.Description
else
embed.Description <- $"Congratulations! You own the rights to the {item.Name} NFT. Please create a ticket in the support channel and we will transfer to your wallet"
| _ -> embed.Description <- $"Purchased {item.Name}"
embed
// 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 storeItem = storeInventory |> List.find (fun si -> si.Item.Id = itemId)
let item = storeInventory |> List.map (fun i -> i.Item) |> Inventory.findItemById itemId
do! player do! player
|> checkHasSufficientFunds item.getItem |> checkHasSufficientFunds item
>>= checkAlreadyOwnsItem item.getItem >>= checkHasStock storeItem
>>= checkDoesntExceedStackCap 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 p = { player with Bank = newBalance ; Inventory = item::player.Inventory } do! DbService.updatePlayerCurrency -price player |> Async.Ignore
do! DbService.updatePlayer p |> Async.Ignore do! DbService.addToPlayerInventory player.DiscordId item |> Async.Ignore
do! sendFollowUpMessage ctx $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining" do! DbService.decrementItemStock item
do! Analytics.buyWeaponButton (ctx.GetDiscordMember()) item let builder = DiscordFollowupMessageBuilder().AsEphemeral(true)
builder.AddEmbed(purchaseItemEmbed (item)) |> ignore
do! ctx.FollowUp builder |> Async.AwaitTask
do! Analytics.buyWeaponButton (ctx.GetDiscordMember()) item.Name price
}) })
}) })
let handleSell (ctx : IDiscordContext) itemId = let handleSell (ctx : IDiscordContext) itemId =
executePlayerAction ctx (fun player -> async { executePlayerAction ctx (fun player -> async {
let item = Armory.weapons |> Inventory.findItemById itemId let item = player.Inventory |> Inventory.findItemById itemId
do! do!
player player
|> checkSoldItemAlready item.getItem |> checkSoldItemAlready item
|> handleResultWithResponse ctx (fun player -> async { |> handleResultWithResponse ctx (fun player -> async {
match item.Attributes with
| CanSell price ->
let updatedPlayer = { let updatedPlayer = {
player with player with
Bank = player.Bank + item.Price Bank = player.Bank + price
Inventory = player.Inventory |> List.filter (fun i -> i.Id <> itemId) Inventory = player.Inventory |> List.filter (fun i -> i.Id <> itemId)
} }
do! do!
[ DbService.updatePlayer updatedPlayer |> Async.Ignore [ DbService.updatePlayer updatedPlayer |> Async.Ignore
DbService.removeShieldEvent updatedPlayer.DiscordId itemId |> Async.Ignore DbService.removeShieldEvent updatedPlayer.DiscordId itemId |> Async.Ignore
sendFollowUpMessage ctx $"Sold {item.Name} for {item.Price}! Current Balance: {updatedPlayer.Bank}" sendFollowUpMessage ctx $"Sold {item.Name} for {price}! Current Balance: {updatedPlayer.Bank}"
Analytics.sellWeaponButton (ctx.GetDiscordMember()) item ] Analytics.sellWeaponButton (ctx.GetDiscordMember()) item price ]
|> Async.Parallel |> Async.Parallel
|> Async.Ignore |> Async.Ignore
| _ -> ()
}) })
}) })
let consume (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction ctx (fun player -> async {
match player.Inventory |> Inventory.getFoods with
| [] -> do! Messaging.sendFollowUpMessage ctx "You do not have any items to consume"
| items ->
let items' = items |> List.map (fun i -> { Item = i ; Stock = 1 ; LimitStock = false ; Available = true })
let embeds = getItemEmbeds true items'
let builder = DiscordFollowupMessageBuilder().AddEmbeds(embeds).AsEphemeral(true)
do! ctx.FollowUp builder |> Async.AwaitTask
})
let handleConsume (ctx : IDiscordContext) itemId = PlayerInteractions.executePlayerAction ctx (fun player -> async {
let item = player.Inventory |> Inventory.findItemById itemId
match player.Inventory |> Inventory.getFoods with
| [] -> do! Messaging.sendFollowUpMessage ctx "You do not have any items to consume"
| items ->
let items' = items |> List.map (fun i -> { Item = i ; Stock = 1 ; LimitStock = false ; Available = true })
let embeds = getItemEmbeds true items'
let builder = DiscordFollowupMessageBuilder().AddEmbeds(embeds).AsEphemeral(true)
do! ctx.FollowUp builder |> Async.AwaitTask
})
let showJpegsEmbed (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction ctx (fun player -> async {
let jpegs =
player.Inventory
|> Inventory.getItemsByType ItemType.Jpeg
|> List.map (fun i -> { Item = i ; Stock = 1 ; LimitStock = false ; Available = true })
let embeds = getItemEmbeds true jpegs
let builder = DiscordFollowupMessageBuilder().AddEmbeds(embeds).AsEphemeral(true)
do! ctx.FollowUp builder |> Async.AwaitTask
})
let showStats (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction ctx (fun player -> async {
let embed = DiscordEmbedBuilder()
PlayerStats.stats
|> List.iter (fun statConfig ->
let playerStat = PlayerStats.getPlayerStat statConfig player
let min =
match statConfig.BaseRange.Min = playerStat.ModRange.Min with
| true -> $"{statConfig.BaseRange.Min}"
| false -> $"{statConfig.BaseRange.Min} (+{playerStat.ModRange.Min}) "
let max =
match statConfig.BaseRange.Max = playerStat.ModRange.Max with
| true -> $"{statConfig.BaseRange.Max}"
| false -> $"{statConfig.BaseRange.Max} (+{playerStat.ModRange.Max}) "
let field = $"{min} |---------------| {max}"
embed.AddField(string statConfig.Id , field) |> ignore)
let builder =
DiscordFollowupMessageBuilder()
.AddEmbed(embed)
.AsEphemeral(true)
do! ctx.FollowUp builder |> Async.AwaitTask
})
let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) = let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) =
let ctx = DiscordEventContext event :> IDiscordContext let ctx = DiscordEventContext event :> IDiscordContext
let id = ctx.GetInteractionId() let id = ctx.GetInteractionId()
@ -174,6 +287,8 @@ let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEve
match id with match id with
| id when id.StartsWith("Buy") -> handleBuyItem ctx itemId | id when id.StartsWith("Buy") -> handleBuyItem ctx itemId
| id when id.StartsWith("Sell") -> handleSell ctx itemId | id when id.StartsWith("Sell") -> handleSell ctx itemId
| id when id.StartsWith("Consume") -> handleConsume ctx itemId
| id when id.StartsWith("ShowJpegInventory") -> buy ctx
| _ -> | _ ->
task { task {
let builder = DiscordInteractionResponseBuilder() let builder = DiscordInteractionResponseBuilder()
@ -182,6 +297,27 @@ let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEve
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
} }
let sendInitialEmbed (ctx : IDiscordContext) =
async {
try
let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelBackAlley)
let builder = DiscordMessageBuilder()
let embed = DiscordEmbedBuilder()
embed.ImageUrl <- "https://s7.gifyu.com/images/ezgif.com-gif-maker-23203b9dca779ba7cf.gif"
embed.Title <- "Degenz Game"
embed.Color <- DiscordColor.Black
embed.Description <- "Hey, what do you want kid? Did you come alone?"
builder.AddEmbed embed |> ignore
let button = DiscordButtonComponent(ButtonStyle.Success, $"ShowJpegInventory-0", $"Show me your stash") :> DiscordComponent
builder.AddComponents [| button |] |> ignore
do! GuildEnvironment.botClientStore.Value.SendMessageAsync(channel, builder)
|> Async.AwaitTask
|> Async.Ignore
with e ->
printfn $"Error trying to get channel Jpeg Alley\n\n{e.Message}"
} |> Async.RunSynchronously
type Store() = type Store() =
inherit ApplicationCommandModule () inherit ApplicationCommandModule ()
@ -194,32 +330,30 @@ type Store() =
do! Messaging.sendSimpleResponse ctx msg do! Messaging.sendSimpleResponse ctx msg
} }
let checkChannel (ctx : IDiscordContext) = // let checkChannel (ctx : IDiscordContext) (storeFn : IDiscordContext -> Task) =
match ctx.GetChannel().Id with // let checkChannel (ctx : IDiscordContext) =
// | id when id = GuildEnvironment.channelBackAlley -> buy (Inventory.getItemsByType ItemType.Hack) ctx // match ctx.GetChannel().Id with
| id when id = GuildEnvironment.channelArmory -> buy ItemType.Shield ctx // | id when id = GuildEnvironment.channelBackAlley -> buy ItemType.Hack ctx
// | id when id = GuildEnvironment.channelMarket -> buy (Inventory.getItemsByType ItemType.Food) ctx // | id when id = GuildEnvironment.channelArmory -> buy ItemType.Shield ctx
// | id when id = GuildEnvironment.channelAccessoryShop -> buy (Inventory.getItemsByType ItemType.Accessory) ctx // | id when id = GuildEnvironment.channelMarket -> buy ItemType.Food ctx
| _ -> // | id when id = GuildEnvironment.channelAccessoryShop -> buy ItemType.Accessory ctx
task { // | _ ->
let msg = $"This channel doesn't have any items to sell" // task {
do! Messaging.sendSimpleResponse ctx msg // let msg = $"This channel doesn't have any items to sell. Try <#{GuildEnvironment.channelArmory}>"
} // do! Messaging.sendSimpleResponse ctx msg
// }
[<SlashCommand("buy-item", "Purchase an item")>]
member _.BuyItem (ctx : InteractionContext) = buy (DiscordInteractionContext ctx)
// [<SlashCommand("buy-item", "Purchase an item")>]
// member _.BuyItem (ctx : InteractionContext) = checkChannel (DiscordInteractionContext(ctx))
//
[<SlashCommand("buy-hack", "Purchase a hack so you can take money from other Degenz")>] [<SlashCommand("buy-hack", "Purchase a hack so you can take money from other Degenz")>]
member _.BuyHack (ctx : InteractionContext) = member _.BuyHack (ctx : InteractionContext) =
enforceChannel (DiscordInteractionContext(ctx)) (buy ItemType.Hack) enforceChannel (DiscordInteractionContext(ctx)) buy
[<SlashCommand("buy-shield", "Purchase a hack shield so you can protect your GBT")>] [<SlashCommand("buy-shield", "Purchase a hack shield so you can protect your GBT")>]
member this.BuyShield (ctx : InteractionContext) = member this.BuyShield (ctx : InteractionContext) =
enforceChannel (DiscordInteractionContext(ctx)) (buy ItemType.Shield) enforceChannel (DiscordInteractionContext(ctx)) buy
// [<SlashCommand("buy-food", "Purchase a food item to help boost your stats")>]
// member this.BuyFood (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy (Inventory.getItemsByType ItemType.Food))
//
[<SlashCommand("sell-hack", "Sell a hack for GoodBoyTokenz")>] [<SlashCommand("sell-hack", "Sell a hack for GoodBoyTokenz")>]
member this.SellHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell "Hacks" (Inventory.getItemsByType ItemType.Hack)) member this.SellHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell "Hacks" (Inventory.getItemsByType ItemType.Hack))
@ -227,10 +361,13 @@ 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 "Shields" (Inventory.getItemsByType ItemType.Food))
// [<SlashCommand("inventory", "Check your inventory")>] [<SlashCommand("jpegs", "Check your inventory")>]
// member this.Inventory (ctx : InteractionContext) = member this.Inventory (ctx : InteractionContext) =
// enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" (Inventory.getItemsByType ItemType)) showJpegsEmbed (DiscordInteractionContext ctx)
[<SlashCommand("stats", "Check your stats")>]
member this.Stats (ctx : InteractionContext) =
showStats (DiscordInteractionContext ctx)

View File

@ -9,8 +9,11 @@ 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 weapons = DbService.getWeapons () |> Async.RunSynchronously
let defaultShield = Armory.weapons |> Inventory.findShieldById (int ItemId.Firewall) let hackItem = weapons |> Inventory.findItemById (int ItemId.Virus)
let shieldItem = 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 +24,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 +66,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 +81,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 +104,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 +120,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 +133,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 +169,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 +197,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 +212,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 +230,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

@ -35,9 +35,9 @@ let channelWhitelist = getId "CHANNEL_WHITELIST"
let channelShelters = getId "CHANNEL_SHELTERS" let channelShelters = getId "CHANNEL_SHELTERS"
let channelSlots = getId "CHANNEL_SLOTS" let channelSlots = getId "CHANNEL_SLOTS"
//let channelJackpotNum = getId "CHANNEL_JACKPOTNUM" //let channelJackpotNum = getId "CHANNEL_JACKPOTNUM"
//let channelBackAlley = getId "CHANNEL_BACKALLEY" let channelBackAlley = getId "CHANNEL_BACKALLEY"
//let channelMarket = getId "CHANNEL_MARKET" let channelMarket = getId "CHANNEL_MARKET"
//let channelAccessoryShop = getId "CHANNEL_ACCESSORIES" let channelAccessoryShop = getId "CHANNEL_ACCESSORIES"
//let channelThievery = getId "CHANNEL_THIEVERY" //let channelThievery = getId "CHANNEL_THIEVERY"
let botIdHackerBattle = getId "BOT_HACKER_BATTLE" let botIdHackerBattle = getId "BOT_HACKER_BATTLE"
@ -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

View File

@ -178,6 +178,48 @@ let getInvitedUserCount userId =
|> Sql.executeRowAsync (fun read -> read.int "count") |> Sql.executeRowAsync (fun read -> read.int "count")
|> Async.AwaitTask |> Async.AwaitTask
let getWhitelistItem () =
connStr
|> Sql.connect
|> Sql.query """
SELECT stock, buy_price FROM store_item
JOIN item i on store_item.item_id = i.id
WHERE i.symbol = 'WHITELIST'
"""
|> Sql.executeRowAsync (fun read -> {| Stock = read.int "stock" ; Price = (read.int "buy_price") * 1<GBT> |})
|> Async.AwaitTask
let updateWhitelistStock () = async {
try
do! connStr
|> Sql.connect
|> Sql.query """
UPDATE store_item SET stock = GREATEST(stock - 1, 0)
WHERE store_item.item_id = (SELECT id FROM item WHERE symbol = 'WHITELIST')
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
|> Async.Ignore
return true
with _ -> return false
}
let setWhitelistStock amount = async {
try
do! connStr
|> Sql.connect
|> Sql.parameters [ ( "amount" , Sql.int amount ) ]
|> Sql.query """
UPDATE store_item SET stock = @amount
WHERE store_item.item_id = (SELECT id FROM item WHERE symbol = 'WHITELIST')
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
|> Async.Ignore
return true
with _ -> return false
}
let guildInviteEmbed = let guildInviteEmbed =
let rewardMsg = let rewardMsg =
$"**Your Mission:**\nCLICK THE BUTTON below, then share your **UNIQUE LINK** with any Degenz you want to invite into the Server.\n\n" + $"**Your Mission:**\nCLICK THE BUTTON below, then share your **UNIQUE LINK** with any Degenz you want to invite into the Server.\n\n" +
@ -431,7 +473,7 @@ let handleGimmeWhitelist (ctx : IDiscordContext) =
let builder = DiscordFollowupMessageBuilder().AsEphemeral(true) let builder = DiscordFollowupMessageBuilder().AsEphemeral(true)
let! wlItem = DbService.getWhitelistItem () let! wlItem = getWhitelistItem ()
let! availability = tryGrantWhitelist ctx wlItem.Stock wlItem.Price let! availability = tryGrantWhitelist ctx wlItem.Stock wlItem.Price
match availability with match availability with
| NotAHacker -> whitelistEmbed.Description <- notAHackerMsg | NotAHacker -> whitelistEmbed.Description <- notAHackerMsg
@ -480,7 +522,7 @@ let handleBuyWhitelist (ctx : IDiscordContext) =
let builder = DiscordInteractionResponseBuilder().AsEphemeral(true) let builder = DiscordInteractionResponseBuilder().AsEphemeral(true)
do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder)
let! wlItem = DbService.getWhitelistItem () let! wlItem = getWhitelistItem ()
let builder = DiscordFollowupMessageBuilder().AsEphemeral(true) let builder = DiscordFollowupMessageBuilder().AsEphemeral(true)
match! tryGrantWhitelist ctx wlItem.Stock wlItem.Price with match! tryGrantWhitelist ctx wlItem.Stock wlItem.Price with
| NotAHacker -> | NotAHacker ->
@ -499,7 +541,7 @@ let handleBuyWhitelist (ctx : IDiscordContext) =
builder.Content <- $"We just ran out of stock, tough shit" builder.Content <- $"We just ran out of stock, tough shit"
do! ctx.FollowUp(builder) do! ctx.FollowUp(builder)
| Granted player -> | Granted player ->
match! DbService.updateWhitelistStock () with match! updateWhitelistStock () with
| true -> | true ->
let embed = DiscordEmbedBuilder() let embed = DiscordEmbedBuilder()
embed.Description <- buyWhitelistMsg embed.Description <- buyWhitelistMsg
@ -588,10 +630,10 @@ let handleGuildMemberAdded _ (eventArgs : GuildMemberAddEventArgs) =
do! processNewUser eventArgs do! processNewUser eventArgs
} :> Task } :> Task
let rec setWhitelistStock amount (ctx : IDiscordContext) = let setCurrentWhitelistStock amount (ctx : IDiscordContext) =
task { task {
do! Messaging.defer ctx do! Messaging.defer ctx
let! result = DbService.setWhitelistStock amount let! result = setWhitelistStock amount
if result then if result then
do! Messaging.sendFollowUpMessage ctx $"Set Whitelist stock to {amount}" do! Messaging.sendFollowUpMessage ctx $"Set Whitelist stock to {amount}"
else else

View File

@ -1,304 +0,0 @@
[
{
"Case": "Hack",
"Fields": [
{
"Power": 20,
"Class": 0,
"Cooldown": 2,
"Item": {
"Id": 0,
"Name": "Virus",
"Price": 250,
"Attributes": {
"CanBuy" : true,
"CanSell" : true,
"CanConsume" : false,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
},
{
"Case": "Hack",
"Fields": [
{
"Power": 20,
"Class": 1,
"Cooldown": 2,
"Item": {
"Id": 1,
"Name": "Remote Access",
"Price": 250,
"Attributes": {
"CanBuy" : true,
"CanSell" : true,
"CanConsume" : false,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
},
{
"Case": "Hack",
"Fields": [
{
"Power": 20,
"Class": 2,
"Cooldown": 2,
"Item": {
"Id": 2,
"Name": "Worm",
"Price": 250,
"Attributes": {
"CanBuy" : true,
"CanSell" : true,
"CanConsume" : false,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
},
{
"Case": "Shield",
"Fields": [
{
"Class": 0,
"Cooldown": 300,
"Item": {
"Id": 6,
"Name": "Firewall",
"Price": 100,
"Attributes": {
"CanBuy" : true,
"CanSell" : true,
"CanConsume" : false,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
},
{
"Case": "Shield",
"Fields": [
{
"Class": 1,
"Cooldown": 300,
"Item": {
"Id": 7,
"Name": "Encryption",
"Price": 100,
"Attributes": {
"CanBuy" : true,
"CanSell" : true,
"CanConsume" : false,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
},
{
"Case": "Shield",
"Fields": [
{
"Class": 2,
"Cooldown": 300,
"Item": {
"Id": 8,
"Name": "Cypher",
"Price": 100,
"Attributes": {
"CanBuy" : true,
"CanSell" : true,
"CanConsume" : false,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
},
{
"Case": "Food",
"Fields": [
{
"TargetStat" : 0,
"BoostAmount" : 30,
"Item": {
"Id": 12,
"Name": "Protein Powder",
"Price": 50,
"Attributes": {
"CanBuy" : true,
"CanSell" : false,
"CanConsume" : true,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
},
{
"Case": "Food",
"Fields": [
{
"TargetStat" : 1,
"BoostAmount" : 30,
"Item": {
"Id": 13,
"Name": "Toro Loco",
"Price": 50,
"Attributes": {
"CanBuy" : true,
"CanSell" : false,
"CanConsume" : true,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
},
{
"Case": "Food",
"Fields": [
{
"TargetStat" : 2,
"BoostAmount" : 30,
"Item": {
"Id": 14,
"Name": "Oldports Cigs",
"Price": 50,
"Attributes": {
"CanBuy" : true,
"CanSell" : false,
"CanConsume" : true,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
},
{
"Case": "Food",
"Fields": [
{
"TargetStat" : 3,
"BoostAmount" : 30,
"Item": {
"Id": 15,
"Name": "Moon Pie",
"Price": 50,
"Attributes": {
"CanBuy" : true,
"CanSell" : false,
"CanConsume" : true,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
},
{
"Case": "Accessory",
"Fields": [
{
"TargetStat" : 0,
"FloorBoost" : 25,
"CeilBoost" : 0,
"Item": {
"Id": 20,
"Name": "Kettlebell",
"Price": 250,
"Attributes": {
"CanBuy" : true,
"CanSell" : true,
"CanConsume" : false,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
},
{
"Case": "Accessory",
"Fields": [
{
"TargetStat" : 1,
"FloorBoost" : 25,
"CeilBoost" : 0,
"Item": {
"Id": 21,
"Name": "Headphones",
"Price": 250,
"Attributes": {
"CanBuy" : true,
"CanSell" : true,
"CanConsume" : false,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
},
{
"Case": "Accessory",
"Fields": [
{
"TargetStat" : 2,
"FloorBoost" : 0,
"CeilBoost" : 25,
"Item": {
"Id": 22,
"Name": "Rolox Watch",
"Price": 250,
"Attributes": {
"CanBuy" : true,
"CanSell" : true,
"CanConsume" : false,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
},
{
"Case": "Accessory",
"Fields": [
{
"TargetStat" : 3,
"FloorBoost" : 0,
"CeilBoost" : 25,
"Item": {
"Id": 23,
"Name": "Buddha Keychain",
"Price": 250,
"Attributes": {
"CanBuy" : true,
"CanSell" : true,
"CanConsume" : false,
"CanTrade" : false,
"CanDrop" : false
}
}
}
]
}
]

View File

@ -5,9 +5,9 @@ framework: net6.0, netstandard2.0, netstandard2.1
nuget FSharp.Core >= 6.0.0 nuget FSharp.Core >= 6.0.0
nuget DSharpPlus >= 4.2.0-nightly-01105 nuget DSharpPlus >= 4.2.0-nightly-01125
nuget DSharpPlus.Interactivity >= 4.2.0-nightly-01105 nuget DSharpPlus.Interactivity >= 4.2.0-nightly-01125
nuget DSharpPlus.SlashCommands >= 4.2.0-nightly-01105 nuget DSharpPlus.SlashCommands >= 4.2.0-nightly-01125
nuget MongoDB.Driver nuget MongoDB.Driver
nuget dotenv.net 3.1.1 nuget dotenv.net 3.1.1

View File

@ -8,7 +8,7 @@ NUGET
System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= net471)) (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (>= net471)) (&& (== netstandard2.1) (< netstandard2.0)) System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= net471)) (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (>= net471)) (&& (== netstandard2.1) (< netstandard2.0))
dotenv.net (3.1.1) dotenv.net (3.1.1)
System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (< netstandard2.0)) System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (< netstandard2.0))
DSharpPlus (4.2.0-nightly-01105) DSharpPlus (4.3.0-nightly-01125)
Emzi0767.Common (>= 2.6.2) Emzi0767.Common (>= 2.6.2)
Microsoft.Extensions.Logging.Abstractions (>= 5.0) Microsoft.Extensions.Logging.Abstractions (>= 5.0)
Newtonsoft.Json (>= 13.0.1) Newtonsoft.Json (>= 13.0.1)
@ -18,11 +18,11 @@ NUGET
System.Net.WebSockets.Client (>= 4.3.2) System.Net.WebSockets.Client (>= 4.3.2)
System.Runtime.InteropServices.RuntimeInformation (>= 4.3) System.Runtime.InteropServices.RuntimeInformation (>= 4.3)
System.Threading.Channels (>= 5.0) System.Threading.Channels (>= 5.0)
DSharpPlus.Interactivity (4.2.0-nightly-01105) DSharpPlus.Interactivity (4.3.0-nightly-01125)
ConcurrentHashSet (>= 1.1) ConcurrentHashSet (>= 1.1)
DSharpPlus (>= 4.2.0-nightly-01105) DSharpPlus (>= 4.3.0-nightly-01125)
DSharpPlus.SlashCommands (4.2.0-nightly-01105) DSharpPlus.SlashCommands (4.3.0-nightly-01125)
DSharpPlus (>= 4.2.0-nightly-01105) DSharpPlus (>= 4.3.0-nightly-01125)
Microsoft.Extensions.DependencyInjection (>= 5.0.1) Microsoft.Extensions.DependencyInjection (>= 5.0.1)
Emzi0767.Common (2.6.2) Emzi0767.Common (2.6.2)
System.Collections.Immutable (>= 5.0) System.Collections.Immutable (>= 5.0)