Merge branch 'dev' into staging
This commit is contained in:
		
						commit
						3e0e500182
					
				
							
								
								
									
										11
									
								
								Bot/Admin.fs
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								Bot/Admin.fs
									
									
									
									
									
								
							@ -2,6 +2,7 @@ module Degenz.Admin
 | 
			
		||||
 | 
			
		||||
open System
 | 
			
		||||
open System.IO
 | 
			
		||||
open System.Reflection
 | 
			
		||||
open System.Threading.Tasks
 | 
			
		||||
open DSharpPlus
 | 
			
		||||
open DSharpPlus.Entities
 | 
			
		||||
@ -14,6 +15,7 @@ type InitEmbeds =
 | 
			
		||||
    | Dojo = 0
 | 
			
		||||
    | Whitelist = 1
 | 
			
		||||
    | Slots = 2
 | 
			
		||||
    | JpegStore = 3
 | 
			
		||||
 | 
			
		||||
let handleGuildDownloadReady (_ : DiscordClient) (event : GuildDownloadCompletedEventArgs) =
 | 
			
		||||
    task {
 | 
			
		||||
@ -24,6 +26,7 @@ let handleGuildDownloadReady (_ : DiscordClient) (event : GuildDownloadCompleted
 | 
			
		||||
        let permission = DiscordApplicationCommandPermission(adminRole, true)
 | 
			
		||||
        let commands = commands |> Seq.map (fun com -> DiscordGuildApplicationCommandPermissions(com.Id, [ permission ]))
 | 
			
		||||
        do! guild.BatchEditApplicationCommandPermissionsAsync(commands) |> Async.AwaitTask |> Async.Ignore
 | 
			
		||||
        return ()
 | 
			
		||||
    } :> Task
 | 
			
		||||
 | 
			
		||||
let sendEmbed embed (ctx : IDiscordContext) =
 | 
			
		||||
@ -32,6 +35,7 @@ let sendEmbed embed (ctx : IDiscordContext) =
 | 
			
		||||
        | InitEmbeds.Dojo -> Trainer.sendInitialEmbed ctx
 | 
			
		||||
        | InitEmbeds.Whitelist -> InviteTracker.sendInitialEmbed ctx
 | 
			
		||||
        | InitEmbeds.Slots -> SlotMachine.sendInitialEmbedFromSlashCommand ctx
 | 
			
		||||
        | InitEmbeds.JpegStore -> Store.sendInitialEmbed ctx
 | 
			
		||||
        | _ -> ()
 | 
			
		||||
        do! Messaging.sendSimpleResponse ctx "Sent!"
 | 
			
		||||
    } :> Task
 | 
			
		||||
@ -137,24 +141,29 @@ type AdminBot() =
 | 
			
		||||
        else
 | 
			
		||||
            Messaging.sendSimpleResponse ctx $"You are not admin" |> Async.StartAsTask :> Task
 | 
			
		||||
 | 
			
		||||
    [<SlashCommandPermissions(Permissions.Administrator)>]
 | 
			
		||||
    [<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) =
 | 
			
		||||
        enforceAdmin (DiscordInteractionContext ctx) (InviteTracker.getInvitedUsersForId user)
 | 
			
		||||
 | 
			
		||||
    [<SlashCommandPermissions(Permissions.Administrator)>]
 | 
			
		||||
    [<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) =
 | 
			
		||||
        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)>]
 | 
			
		||||
    member this.SendEmbedToChannel (ctx : InteractionContext, [<Option("embed", "Which embed to send")>] embed : InitEmbeds) =
 | 
			
		||||
        enforceAdmin (DiscordInteractionContext ctx) (sendEmbed embed)
 | 
			
		||||
 | 
			
		||||
    [<SlashCommandPermissions(Permissions.Administrator)>]
 | 
			
		||||
    [<SlashCommand("admin-get-msg-reactions", "Set whitelist stock", false)>]
 | 
			
		||||
    member this.GetMessageReactions (ctx : InteractionContext,
 | 
			
		||||
                                     [<Option("channel", "The channel where the message is")>] channel : DiscordChannel,
 | 
			
		||||
                                     [<Option("message-id", "The ID of the message with all the reactions")>] messageId : string) =
 | 
			
		||||
        enforceAdmin (DiscordInteractionContext ctx) (getUsersFromMessageReactions channel messageId)
 | 
			
		||||
 | 
			
		||||
    [<SlashCommandPermissions(Permissions.Administrator)>]
 | 
			
		||||
    [<SlashCommand("admin-get-invites-table", "Invites Table", false)>]
 | 
			
		||||
    member this.GetInvitesFromReactedMessages (ctx : InteractionContext,
 | 
			
		||||
                                     [<Option("channel", "The channel where the message is")>] channel : DiscordChannel,
 | 
			
		||||
 | 
			
		||||
@ -94,33 +94,33 @@ let arsenalCommand (discordMember : DiscordMember) =
 | 
			
		||||
    ]
 | 
			
		||||
    track "Arsenal Command Invoked" discordMember.Id data
 | 
			
		||||
 | 
			
		||||
let buyWeaponCommand (discordMember : DiscordMember) weaponType =
 | 
			
		||||
let buyItemCommand (discordMember : DiscordMember) store =
 | 
			
		||||
    let data = [
 | 
			
		||||
        "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 = [
 | 
			
		||||
        "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 = [
 | 
			
		||||
        "user_display_name" , discordMember.Username
 | 
			
		||||
        "weapon_name" , weapon.Name
 | 
			
		||||
        "weapon_price" , string weapon.Price
 | 
			
		||||
        "weapon_name" , itemName
 | 
			
		||||
        "weapon_price" , string itemPrice
 | 
			
		||||
    ]
 | 
			
		||||
    track "Buy Weapon Button Clicked" discordMember.Id data
 | 
			
		||||
 | 
			
		||||
let sellWeaponButton (discordMember : DiscordMember) (weapon : ItemDetails) =
 | 
			
		||||
let sellWeaponButton (discordMember : DiscordMember) (weapon : Item) price =
 | 
			
		||||
    let data = [
 | 
			
		||||
        "user_display_name" , discordMember.Username
 | 
			
		||||
        "weapon_name" , weapon.Name
 | 
			
		||||
        "weapon_price" , string weapon.Price
 | 
			
		||||
        "weapon_price" , string price
 | 
			
		||||
    ]
 | 
			
		||||
    track "Sell Weapon Button Clicked" discordMember.Id data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -77,7 +77,6 @@ inviterBot.add_ComponentInteractionCreated(AsyncEventHandler(InviteTracker.handl
 | 
			
		||||
slotsBot.add_ComponentInteractionCreated(AsyncEventHandler(SlotMachine.handleButton))
 | 
			
		||||
slotsBot.add_GuildDownloadCompleted(AsyncEventHandler(SlotMachine.handleGuildDownloadCompleted))
 | 
			
		||||
slotsBot.add_MessageCreated(AsyncEventHandler(SlotMachine.handleMessageCreated))
 | 
			
		||||
adminBot.add_GuildDownloadCompleted(AsyncEventHandler(Admin.handleGuildDownloadReady))
 | 
			
		||||
 | 
			
		||||
let asdf (_ : DiscordClient) (event : DSharpPlus.EventArgs.InteractionCreateEventArgs) =
 | 
			
		||||
    async {
 | 
			
		||||
@ -95,15 +94,15 @@ let asdf (_ : DiscordClient) (event : DSharpPlus.EventArgs.InteractionCreateEven
 | 
			
		||||
      :> Task
 | 
			
		||||
//hackerBattleBot.add_InteractionCreated(AsyncEventHandler(asdf))
 | 
			
		||||
 | 
			
		||||
storeBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
 | 
			
		||||
GuildEnvironment.botClientStore <- Some storeBot
 | 
			
		||||
 | 
			
		||||
slotsBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
 | 
			
		||||
GuildEnvironment.botClientSlots <- Some slotsBot
 | 
			
		||||
 | 
			
		||||
hackerBattleBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
 | 
			
		||||
GuildEnvironment.botClientHacker <- Some hackerBattleBot
 | 
			
		||||
 | 
			
		||||
storeBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
 | 
			
		||||
//GuildEnvironment.botClient <- Some storeBot.CurrentUser
 | 
			
		||||
 | 
			
		||||
inviterBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
 | 
			
		||||
GuildEnvironment.botClientRecruit <- Some inviterBot
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,9 +7,6 @@
 | 
			
		||||
        <RootNamespace>Degenz</RootNamespace>
 | 
			
		||||
    </PropertyGroup>
 | 
			
		||||
    <ItemGroup>
 | 
			
		||||
        <Content Include="Items.json">
 | 
			
		||||
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 | 
			
		||||
        </Content>
 | 
			
		||||
        <Content Include="paket.references" />
 | 
			
		||||
        <Compile Include="Prelude.fs" />
 | 
			
		||||
        <Compile Include="GuildEnvironment.fs" />
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										234
									
								
								Bot/DbService.fs
									
									
									
									
									
								
							
							
						
						
									
										234
									
								
								Bot/DbService.fs
									
									
									
									
									
								
							@ -1,11 +1,149 @@
 | 
			
		||||
module Degenz.DbService
 | 
			
		||||
 | 
			
		||||
open System
 | 
			
		||||
open Npgsql
 | 
			
		||||
open Npgsql.FSharp
 | 
			
		||||
open Degenz
 | 
			
		||||
 | 
			
		||||
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) =
 | 
			
		||||
    connStr
 | 
			
		||||
    |> Sql.connect
 | 
			
		||||
@ -34,36 +172,6 @@ let getPlayerEvents (did : uint64) =
 | 
			
		||||
        )
 | 
			
		||||
    |> 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) =
 | 
			
		||||
    connStr
 | 
			
		||||
    |> Sql.connect
 | 
			
		||||
@ -89,28 +197,25 @@ let tryFindPlayer (discordId : uint64) = async {
 | 
			
		||||
        |> Sql.connect
 | 
			
		||||
        |> Sql.parameters [ "did", Sql.string (string discordId) ]
 | 
			
		||||
        |> 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
 | 
			
		||||
            """
 | 
			
		||||
        |> Sql.executeAsync (fun read ->
 | 
			
		||||
            let inv = read.intArray "inventory"
 | 
			
		||||
            {|
 | 
			
		||||
                DiscordId = read.string "discord_id" |> uint64
 | 
			
		||||
                Name = read.string "display_name"
 | 
			
		||||
                Bank = read.intOrNone "gbt" |> Option.map ((*) 1<GBT>) |> Option.defaultValue 0<GBT>
 | 
			
		||||
                Inventory = inv |> Array.toList
 | 
			
		||||
                Strength = read.intOrNone "strength" |> Option.defaultValue 0
 | 
			
		||||
                Focus = read.intOrNone "focus" |> Option.defaultValue 0
 | 
			
		||||
                Charisma = read.intOrNone "charisma" |> Option.defaultValue 0
 | 
			
		||||
                Luck = read.intOrNone "luck" |> Option.defaultValue 0
 | 
			
		||||
                Active = read.bool "in_game"
 | 
			
		||||
            {| DiscordId = read.string "discord_id" |> uint64
 | 
			
		||||
               Name = read.string "display_name"
 | 
			
		||||
               Bank = read.intOrNone "gbt" |> Option.map ((*) 1<GBT>) |> Option.defaultValue 0<GBT>
 | 
			
		||||
               Strength = read.intOrNone "strength" |> Option.defaultValue 0
 | 
			
		||||
               Focus = read.intOrNone "focus" |> Option.defaultValue 0
 | 
			
		||||
               Charisma = read.intOrNone "charisma" |> Option.defaultValue 0
 | 
			
		||||
               Luck = read.intOrNone "luck" |> Option.defaultValue 0
 | 
			
		||||
               Active = read.bool "in_game"
 | 
			
		||||
            |})
 | 
			
		||||
        |> Async.AwaitTask
 | 
			
		||||
    match List.tryHead user with
 | 
			
		||||
    | None -> return None
 | 
			
		||||
    | Some u ->
 | 
			
		||||
        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 focus = PlayerStats.calculateActiveStat StatId.Focus u.Focus 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)
 | 
			
		||||
        "gbt", Sql.int (int addAmount)
 | 
			
		||||
    ] |> 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
 | 
			
		||||
    |> Async.AwaitTask
 | 
			
		||||
@ -146,14 +251,12 @@ let updatePlayer (player : PlayerData) =
 | 
			
		||||
    |> Sql.parameters [
 | 
			
		||||
        "did", Sql.string (string player.DiscordId)
 | 
			
		||||
        "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
 | 
			
		||||
        "focus", Sql.int player.Stats.Focus.Amount
 | 
			
		||||
        "charisma", Sql.int player.Stats.Charisma.Amount
 | 
			
		||||
        "luck", Sql.int player.Stats.Luck.Amount
 | 
			
		||||
    ] |> Sql.query """
 | 
			
		||||
           UPDATE "user" SET gbt = @gbt, inventory = @inv,
 | 
			
		||||
                             strength = @strength, focus = @focus, charisma = @charisma, luck = @luck
 | 
			
		||||
           UPDATE "user" SET gbt = @gbt, strength = @strength, focus = @focus, charisma = @charisma, luck = @luck
 | 
			
		||||
           WHERE discord_id = @did
 | 
			
		||||
       """
 | 
			
		||||
    |> 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" |})
 | 
			
		||||
    |> 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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
module Degenz.Embeds
 | 
			
		||||
 | 
			
		||||
open System
 | 
			
		||||
open Degenz
 | 
			
		||||
open Degenz.Messaging
 | 
			
		||||
open Degenz.Types
 | 
			
		||||
open DSharpPlus.Entities
 | 
			
		||||
@ -10,13 +11,13 @@ let shieldGif = "https://s10.gifyu.com/images/Defense-Degenz-V2-min.gif"
 | 
			
		||||
 | 
			
		||||
let getItemIcon id =
 | 
			
		||||
    match enum<ItemId>(id) with
 | 
			
		||||
    | ItemId.Virus -> "https://s10.gifyu.com/images/Virus-icon.jpg"
 | 
			
		||||
    | ItemId.RemoteAccess -> "https://s10.gifyu.com/images/Mind-Control-Degenz-V2-min.jpg"
 | 
			
		||||
    | ItemId.Worm -> "https://s10.gifyu.com/images/WormBugAttack_Degenz-min.jpg"
 | 
			
		||||
    | ItemId.Firewall -> "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.Cypher -> "https://s10.gifyu.com/images/Cypher-Smaller.jpg"
 | 
			
		||||
    | _ -> hackGif
 | 
			
		||||
    | ItemId.Virus -> Some "https://s10.gifyu.com/images/Virus-icon.jpg"
 | 
			
		||||
    | ItemId.RemoteAccess -> Some "https://s10.gifyu.com/images/Mind-Control-Degenz-V2-min.jpg"
 | 
			
		||||
    | ItemId.Worm -> Some "https://s10.gifyu.com/images/WormBugAttack_Degenz-min.jpg"
 | 
			
		||||
    | ItemId.Firewall -> Some "https://s10.gifyu.com/images/Defense-GIF-1-Degenz-1.jpg"
 | 
			
		||||
    | ItemId.Encryption -> Some "https://s10.gifyu.com/images/Encryption-Degenz-V2-1-min.jpg"
 | 
			
		||||
    | ItemId.Cypher -> Some "https://s10.gifyu.com/images/Cypher-Smaller.jpg"
 | 
			
		||||
    | _ -> None
 | 
			
		||||
 | 
			
		||||
let getItemGif id =
 | 
			
		||||
    match enum<ItemId>(id) with
 | 
			
		||||
@ -61,8 +62,8 @@ let pickDefense actionId player isTrainer =
 | 
			
		||||
 | 
			
		||||
    for shield in Inventory.getShields player.Inventory do
 | 
			
		||||
        let hours = TimeSpan.FromMinutes(int shield.Cooldown).TotalHours |> int
 | 
			
		||||
        let against = WeaponClass.getGoodAgainst(shield.Class) |> snd
 | 
			
		||||
        embed.AddField(shield.Item.Name, $"Active {hours} hours\nDefeats {against}", true) |> ignore
 | 
			
		||||
        let against = WeaponClass.getGoodAgainst shield.Class |> snd
 | 
			
		||||
        embed.AddField(shield.Name, $"Active {hours} hours\nDefeats {against}", true) |> ignore
 | 
			
		||||
 | 
			
		||||
    DiscordFollowupMessageBuilder()
 | 
			
		||||
        .AddComponents(buttons)
 | 
			
		||||
@ -85,7 +86,7 @@ let pickHack actionId attacker defender isTrainer =
 | 
			
		||||
    if not isTrainer then
 | 
			
		||||
        for hack in Inventory.getHacks attacker.Inventory do
 | 
			
		||||
            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()
 | 
			
		||||
        .AddComponents(buttons)
 | 
			
		||||
@ -95,9 +96,9 @@ let pickHack actionId attacker defender isTrainer =
 | 
			
		||||
let responseSuccessfulHack earnedMoney (targetId : uint64) amountTaken (hack : HackItem) =
 | 
			
		||||
    let embed =
 | 
			
		||||
        DiscordEmbedBuilder()
 | 
			
		||||
          .WithImageUrl(getItemGif hack.Item.Id)
 | 
			
		||||
          .WithImageUrl(getItemGif hack.Id)
 | 
			
		||||
          .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 "!"))
 | 
			
		||||
 | 
			
		||||
    DiscordFollowupMessageBuilder()
 | 
			
		||||
@ -105,9 +106,9 @@ let responseSuccessfulHack earnedMoney (targetId : uint64) amountTaken (hack : H
 | 
			
		||||
        .AsEphemeral(true)
 | 
			
		||||
 | 
			
		||||
let responseCreatedShield (shield : ShieldItem) =
 | 
			
		||||
    let embed = DiscordEmbedBuilder().WithImageUrl(getItemGif shield.Item.Id)
 | 
			
		||||
    let embed = DiscordEmbedBuilder().WithImageUrl(getItemGif shield.Id)
 | 
			
		||||
    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()
 | 
			
		||||
        .AddEmbed(embed)
 | 
			
		||||
 | 
			
		||||
@ -4,64 +4,78 @@ open System
 | 
			
		||||
open DSharpPlus
 | 
			
		||||
open DSharpPlus.Entities
 | 
			
		||||
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 =
 | 
			
		||||
    let getItemsByType itemType inventory =
 | 
			
		||||
        match itemType with
 | 
			
		||||
        | ItemType.Hack -> inventory |> List.filter (fun item -> match item with Hack _ -> true | _ -> false)
 | 
			
		||||
        | ItemType.Shield -> inventory |> List.filter (fun item -> match item with Shield _ -> true | _ -> false)
 | 
			
		||||
        | ItemType.Food -> inventory |> List.filter (fun item -> match item with Food _ -> true | _ -> false)
 | 
			
		||||
        | ItemType.Accessory -> inventory |> List.filter (fun item -> match item with Accessory _ -> true | _ -> false)
 | 
			
		||||
        | ItemType.Hack      -> inventory |> List.filter (fun item -> match item.Type with ItemType.Hack      _ -> 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.Type with ItemType.Food      _ -> 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 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 getHackItem item =
 | 
			
		||||
        match item.Type , item.Attributes with
 | 
			
		||||
        | ItemType.Hack , CanBuy buyPrice & CanSell _ & CanAttack power & CanExpire cooldown & CanClass ``class``->
 | 
			
		||||
            Some { Id = item.Id
 | 
			
		||||
                   Name = item.Name
 | 
			
		||||
                   Price = buyPrice
 | 
			
		||||
                   Cooldown = cooldown
 | 
			
		||||
                   Power = power
 | 
			
		||||
                   Class = ``class`` }
 | 
			
		||||
        | _ -> None
 | 
			
		||||
        
 | 
			
		||||
    let getHacks inventory =
 | 
			
		||||
        inventory |> List.choose (fun item -> match item with | Hack h -> Some h | _ -> None)
 | 
			
		||||
    let getShields inventory =
 | 
			
		||||
        inventory |> List.choose (fun item -> match item with | Shield s -> Some s | _ -> None)
 | 
			
		||||
    let getShieldItem item =
 | 
			
		||||
        match item.Type , item.Attributes with
 | 
			
		||||
        | ItemType.Shield , CanBuy buyPrice & CanSell _ & CanDefend resistance & CanExpire cooldown & CanClass ``class`` ->
 | 
			
		||||
            Some { Id = item.Id
 | 
			
		||||
                   Name = item.Name
 | 
			
		||||
                   Price = buyPrice
 | 
			
		||||
                   Cooldown = cooldown
 | 
			
		||||
                   Resistance = resistance
 | 
			
		||||
                   Class = ``class`` }
 | 
			
		||||
        | _ -> None
 | 
			
		||||
        
 | 
			
		||||
//    let findHackById id inventory =
 | 
			
		||||
//        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 =
 | 
			
		||||
        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 =
 | 
			
		||||
        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 =
 | 
			
		||||
    let SameTargetAttackCooldown = TimeSpan.FromHours(4)
 | 
			
		||||
 | 
			
		||||
    let getClassButtonColor item =
 | 
			
		||||
        match ItemDetails.getClass item with
 | 
			
		||||
        | 0 -> ButtonStyle.Danger
 | 
			
		||||
        | 1 -> ButtonStyle.Primary
 | 
			
		||||
        | 2 -> ButtonStyle.Success
 | 
			
		||||
        | _ -> ButtonStyle.Primary
 | 
			
		||||
        match item.Attributes with
 | 
			
		||||
        | CanClass "0" -> ButtonStyle.Danger
 | 
			
		||||
        | CanClass "1" -> ButtonStyle.Primary
 | 
			
		||||
        | CanClass "2" -> ButtonStyle.Success
 | 
			
		||||
        | _            -> ButtonStyle.Primary
 | 
			
		||||
 | 
			
		||||
    let getClassEmbedColor item =
 | 
			
		||||
        match ItemDetails.getClass item with
 | 
			
		||||
        | 0 -> DiscordColor.Red
 | 
			
		||||
        | 1 -> DiscordColor.Blurple
 | 
			
		||||
        | 2 -> DiscordColor.Green
 | 
			
		||||
        | _ -> DiscordColor.Blurple
 | 
			
		||||
        match item.Attributes with
 | 
			
		||||
        | CanClass "0" -> DiscordColor.Red
 | 
			
		||||
        | CanClass "1" -> DiscordColor.Blurple
 | 
			
		||||
        | CanClass "2" -> DiscordColor.Green
 | 
			
		||||
        | _            -> DiscordColor.Blurple
 | 
			
		||||
 | 
			
		||||
    let getGoodAgainst = function
 | 
			
		||||
        | 0     -> ( ItemId.Firewall   , ItemId.Virus        )
 | 
			
		||||
        | 1     -> ( ItemId.Encryption , ItemId.RemoteAccess )
 | 
			
		||||
        | _     -> ( ItemId.Cypher     , ItemId.Worm         )
 | 
			
		||||
        | "0" -> ( ItemId.Firewall   , ItemId.Virus        )
 | 
			
		||||
        | "1" -> ( ItemId.Encryption , ItemId.RemoteAccess )
 | 
			
		||||
        | _   -> ( ItemId.Cypher     , ItemId.Worm         )
 | 
			
		||||
 | 
			
		||||
module Player =
 | 
			
		||||
    let getHackEvents player =
 | 
			
		||||
@ -82,32 +96,48 @@ module Player =
 | 
			
		||||
    let modifyBank (player : PlayerData) amount = { player with Bank = max (player.Bank + amount) 0<GBT> }
 | 
			
		||||
 | 
			
		||||
module PlayerStats =
 | 
			
		||||
    // 4.17f would go from 100 to 0 in roughly 24 hours
 | 
			
		||||
    let Strength = { Id = StatId.Strength ; BaseDecayRate = 4.17 ; BaseRange = Range.normalized }
 | 
			
		||||
    let Focus    = { Id = StatId.Focus ; BaseDecayRate = 4.17 ; BaseRange = Range.normalized }
 | 
			
		||||
    let Luck     = { Id = StatId.Luck ; BaseDecayRate = 4.17 ; BaseRange = Range.normalized }
 | 
			
		||||
    let Charisma = { Id = StatId.Charisma ; BaseDecayRate = 4.17 ; BaseRange = Range.normalized }
 | 
			
		||||
    // 2.09f would go from 100 to 0 in roughly 48 hours
 | 
			
		||||
    let Strength = { Id = StatId.Strength ; BaseDecayRate = 2.09 ; BaseRange = Range.normalized }
 | 
			
		||||
    let Focus    = { Id = StatId.Focus ; BaseDecayRate = 2.09 ; BaseRange = Range.normalized }
 | 
			
		||||
    let Luck     = { Id = StatId.Luck ; BaseDecayRate = 2.09 ; 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 statConfig = stats |> List.find (fun s -> s.Id = statId)
 | 
			
		||||
//        let hoursElapsed = (DateTime.UtcNow - lastRead).Hours
 | 
			
		||||
//        let totalDecay = float hoursElapsed * statConfig.BaseDecayRate
 | 
			
		||||
        let modMinMax =
 | 
			
		||||
            let min = items |> List.sumBy (fun item -> match item with | Accessory a -> a.FloorBoost | _ -> 0)
 | 
			
		||||
            let max = items |> List.sumBy (fun item -> match item with | Accessory a -> a.CeilBoost  | _ -> 0)
 | 
			
		||||
            let min =
 | 
			
		||||
                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)
 | 
			
		||||
        let amountAfterDecay = modMinMax |> Range.constrain amount
 | 
			
		||||
        { Id = statId ; Amount = amountAfterDecay ; ModRange = modMinMax ; LastRead = DateTime.UtcNow }
 | 
			
		||||
 | 
			
		||||
module Arsenal =
 | 
			
		||||
    let battleItemFormat (items : ItemDetails list) =
 | 
			
		||||
    let battleItemFormat (items : Inventory) =
 | 
			
		||||
        match items with
 | 
			
		||||
        | [] -> "None"
 | 
			
		||||
        | _ -> items |> List.map (fun item -> item.Name) |> String.concat ", "
 | 
			
		||||
 | 
			
		||||
    let actionFormat (actions : PlayerEvent List) =
 | 
			
		||||
    let actionFormat items (actions : PlayerEvent List) =
 | 
			
		||||
        match actions with
 | 
			
		||||
        | [] -> "None"
 | 
			
		||||
        | acts ->
 | 
			
		||||
@ -115,13 +145,13 @@ module Arsenal =
 | 
			
		||||
            |> List.map (fun event ->
 | 
			
		||||
                match event.Type with
 | 
			
		||||
                | 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
 | 
			
		||||
                    $"Hacked {h.Adversary.Name} with {item.Item.Name} {cooldown} ago"
 | 
			
		||||
                    $"Hacked {h.Adversary.Name} with {item.Name} {cooldown} ago"
 | 
			
		||||
                | 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
 | 
			
		||||
                    $"{item.Item.Name} Shield active for {cooldown}"
 | 
			
		||||
                    $"{item.Name} Shield active for {cooldown}"
 | 
			
		||||
                | _ -> "")
 | 
			
		||||
            |> List.filter (String.IsNullOrWhiteSpace >> not)
 | 
			
		||||
            |> String.concat "\n"
 | 
			
		||||
@ -130,5 +160,5 @@ module Arsenal =
 | 
			
		||||
        let hacks = Player.getHackEvents p
 | 
			
		||||
        $"**Hacks:** {Inventory.getItemsByType ItemType.Hack 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
 | 
			
		||||
    **Active Shields:**\n{Player.getShieldEvents p |> actionFormat}"
 | 
			
		||||
    **Hack Attacks:**\n{hacks |> List.take (min hacks.Length 10) |> actionFormat p.Inventory}\n
 | 
			
		||||
    **Active Shields:**\n{Player.getShieldEvents p |> actionFormat p.Inventory}"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										125
									
								
								Bot/GameTypes.fs
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								Bot/GameTypes.fs
									
									
									
									
									
								
							@ -2,7 +2,6 @@
 | 
			
		||||
module Degenz.Types
 | 
			
		||||
 | 
			
		||||
open System
 | 
			
		||||
open Degenz
 | 
			
		||||
 | 
			
		||||
[<Measure>]
 | 
			
		||||
type mins
 | 
			
		||||
@ -89,84 +88,83 @@ type ItemType =
 | 
			
		||||
    | Shield
 | 
			
		||||
    | Food
 | 
			
		||||
    | Accessory
 | 
			
		||||
    | Jpeg
 | 
			
		||||
    | Misc
 | 
			
		||||
    
 | 
			
		||||
type ItemAttributes = {
 | 
			
		||||
    CanBuy : bool
 | 
			
		||||
    CanSell : bool
 | 
			
		||||
    CanConsume : bool
 | 
			
		||||
    CanTrade : bool
 | 
			
		||||
    CanDrop : bool
 | 
			
		||||
type Effect =
 | 
			
		||||
    | Min of int
 | 
			
		||||
    | Max of int
 | 
			
		||||
    | Add of int
 | 
			
		||||
    | RateMultiplier of float
 | 
			
		||||
    
 | 
			
		||||
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 = {
 | 
			
		||||
    Id : int
 | 
			
		||||
    Name : string
 | 
			
		||||
    Price : int<GBT>
 | 
			
		||||
    Attributes : ItemAttributes
 | 
			
		||||
    Description : string
 | 
			
		||||
    Type : ItemType
 | 
			
		||||
    Symbol : string
 | 
			
		||||
    IconUrl : string
 | 
			
		||||
    Attributes : ItemAttribute list
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StoreItem = {
 | 
			
		||||
    Stock : int
 | 
			
		||||
    LimitStock : bool
 | 
			
		||||
    Available : bool
 | 
			
		||||
    Item : Item
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type HackItem = {
 | 
			
		||||
    Power : int
 | 
			
		||||
    Class : int
 | 
			
		||||
    Id : int
 | 
			
		||||
    Name : string
 | 
			
		||||
    Price : int<GBT>
 | 
			
		||||
    Cooldown : int<mins>
 | 
			
		||||
    Item : Item
 | 
			
		||||
    Power : int
 | 
			
		||||
    Class : string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ShieldItem = {
 | 
			
		||||
    Class : int
 | 
			
		||||
    Id : int
 | 
			
		||||
    Name : string
 | 
			
		||||
    Price : int<GBT>
 | 
			
		||||
    Cooldown : int<mins>
 | 
			
		||||
    Item : Item
 | 
			
		||||
    Resistance : int
 | 
			
		||||
    Class : string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FoodItem = {
 | 
			
		||||
    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 Inventory = Item list
 | 
			
		||||
 | 
			
		||||
type PlayerData = {
 | 
			
		||||
    DiscordId : uint64
 | 
			
		||||
@ -190,3 +188,4 @@ with member this.toDiscordPlayer = { Id = this.DiscordId ; Name = this.Name }
 | 
			
		||||
//               XP = 0
 | 
			
		||||
           Bank = 0<GBT>
 | 
			
		||||
           Active = false }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -52,7 +52,7 @@ let checkHasEmptyHacks (attacker : PlayerData) =
 | 
			
		||||
let checkPlayerOwnsWeapon (item : Item) player =
 | 
			
		||||
    match player.Inventory |> List.exists (fun i -> i.Id = item.Id) with
 | 
			
		||||
    | 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 updatedPlayer = player |> Player.removeExpiredActions
 | 
			
		||||
@ -82,7 +82,11 @@ let runHackerBattle defender (hack : HackItem) =
 | 
			
		||||
    |> fun p -> p.Events
 | 
			
		||||
    |> List.exists (fun event ->
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
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> }
 | 
			
		||||
    let event isDefenderEvent =
 | 
			
		||||
        let hackEvent = {
 | 
			
		||||
            HackId = hack.Item.Id
 | 
			
		||||
            HackId = hack.Id
 | 
			
		||||
            Adversary = if isDefenderEvent then attacker.toDiscordPlayer else defender.toDiscordPlayer
 | 
			
		||||
            IsInstigator = not isDefenderEvent
 | 
			
		||||
            Success = successfulHack
 | 
			
		||||
@ -161,25 +165,27 @@ let handleAttack (ctx : IDiscordContext) =
 | 
			
		||||
    executePlayerAction ctx (fun attacker -> async {
 | 
			
		||||
        let tokens = ctx.GetInteractionId().Split("-")
 | 
			
		||||
        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! resultTarget = DbService.tryFindPlayer targetId
 | 
			
		||||
 | 
			
		||||
        match resultTarget , true , resultId with
 | 
			
		||||
        | Some defender , true , true ->
 | 
			
		||||
        match resultTarget , resultId with
 | 
			
		||||
        | Some defender , true ->
 | 
			
		||||
            do! attacker
 | 
			
		||||
                |> Player.removeExpiredActions
 | 
			
		||||
                |> checkAlreadyHackedTarget defender
 | 
			
		||||
                >>= checkPlayerOwnsWeapon hack.Item
 | 
			
		||||
                >>= checkPlayerOwnsWeapon item
 | 
			
		||||
                >>= checkTargetHasFunds defender
 | 
			
		||||
                >>= checkWeaponHasCooldown hack.Item
 | 
			
		||||
                >>= checkWeaponHasCooldown item
 | 
			
		||||
                |> function
 | 
			
		||||
                   | Ok atkr -> async {
 | 
			
		||||
                       let result = runHackerBattle defender hack
 | 
			
		||||
                   | Ok attacker -> async {
 | 
			
		||||
                       let result = runHackerBattle defender hackItem
 | 
			
		||||
                       match result with
 | 
			
		||||
                       | false -> do! successfulHack ctx atkr defender hack
 | 
			
		||||
                       | true -> do! failedHack ctx attacker defender hack
 | 
			
		||||
                       do! Analytics.hackedTarget (ctx.GetDiscordMember()) hack.Item.Name (not result)
 | 
			
		||||
                       | false -> do! successfulHack ctx attacker defender hackItem
 | 
			
		||||
                       | true -> do! failedHack ctx attacker defender hackItem
 | 
			
		||||
                       do! Analytics.hackedTarget (ctx.GetDiscordMember()) hackItem.Name (not result)
 | 
			
		||||
                    }
 | 
			
		||||
                   | Error msg -> Messaging.sendFollowUpMessage ctx msg
 | 
			
		||||
        | _ -> do! Messaging.sendFollowUpMessage ctx "Error occurred processing attack"
 | 
			
		||||
@ -201,18 +207,19 @@ let handleDefense (ctx : IDiscordContext) =
 | 
			
		||||
    executePlayerAction ctx (fun player -> async {
 | 
			
		||||
        let tokens = ctx.GetInteractionId().Split("-")
 | 
			
		||||
        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
 | 
			
		||||
            |> checkPlayerOwnsWeapon shield.Item
 | 
			
		||||
            |> checkPlayerOwnsWeapon item
 | 
			
		||||
            >>= checkPlayerHasShieldSlotsAvailable
 | 
			
		||||
            >>= checkWeaponHasCooldown shield.Item
 | 
			
		||||
            >>= checkWeaponHasCooldown item
 | 
			
		||||
            |> handleResultWithResponse ctx (fun p -> async {
 | 
			
		||||
                let embed = Embeds.responseCreatedShield shield
 | 
			
		||||
                let embed = Embeds.responseCreatedShield shieldItem
 | 
			
		||||
                do! ctx.FollowUp embed |> Async.AwaitTask
 | 
			
		||||
                let defense = {
 | 
			
		||||
                    Type = Shielding shieldId
 | 
			
		||||
                    Cooldown = shield.Cooldown
 | 
			
		||||
                    Cooldown = shieldItem.Cooldown
 | 
			
		||||
                    Timestamp = DateTime.UtcNow
 | 
			
		||||
                }
 | 
			
		||||
                do! DbService.updatePlayer p |> Async.Ignore
 | 
			
		||||
@ -223,7 +230,7 @@ let handleDefense (ctx : IDiscordContext) =
 | 
			
		||||
                do! channel.SendMessageAsync(builder)
 | 
			
		||||
                    |> Async.AwaitTask
 | 
			
		||||
                    |> Async.Ignore
 | 
			
		||||
                do! Analytics.shieldActivated (ctx.GetDiscordMember()) shield.Item.Name
 | 
			
		||||
                do! Analytics.shieldActivated (ctx.GetDiscordMember()) shieldItem.Name
 | 
			
		||||
            })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -103,30 +103,6 @@ let reel1 = getReel (fun s -> s.reel1Count)
 | 
			
		||||
let reel2 = getReel (fun s -> s.reel2Count)
 | 
			
		||||
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 =
 | 
			
		||||
    [| "sushi"
 | 
			
		||||
       "bigbrother"
 | 
			
		||||
@ -152,7 +128,10 @@ let mutable anyEmoji : DiscordEmoji option = None
 | 
			
		||||
let getJackpotAmount () =
 | 
			
		||||
    GuildEnvironment.connectionString
 | 
			
		||||
    |> 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>)
 | 
			
		||||
    |> Async.AwaitTask
 | 
			
		||||
 | 
			
		||||
@ -160,7 +139,10 @@ let incrementJackpot amount =
 | 
			
		||||
    GuildEnvironment.connectionString
 | 
			
		||||
    |> Sql.connect
 | 
			
		||||
    |> 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
 | 
			
		||||
    |> Async.AwaitTask
 | 
			
		||||
 | 
			
		||||
@ -168,10 +150,43 @@ let resetJackpot amount =
 | 
			
		||||
    GuildEnvironment.connectionString
 | 
			
		||||
    |> Sql.connect
 | 
			
		||||
    |> 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
 | 
			
		||||
    |> 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) =
 | 
			
		||||
    task {
 | 
			
		||||
        do! Messaging.defer ctx
 | 
			
		||||
@ -321,10 +336,10 @@ let spin multiplier (ctx : IDiscordContext) =
 | 
			
		||||
    }
 | 
			
		||||
    PlayerInteractions.executePlayerAction ctx (fun player -> async {
 | 
			
		||||
        if player.Bank >= playAmount then
 | 
			
		||||
            match! DbService.getLastPlayedSlotFromPlayer player.DiscordId with
 | 
			
		||||
            match! getLastPlayedSlotFromPlayer player.DiscordId with
 | 
			
		||||
            | Some timestamp ->
 | 
			
		||||
                if DateTime.UtcNow - timestamp > TimeSpan.FromSeconds(8) then
 | 
			
		||||
                    do! DbService.updateSlotPlayedFromPlayer player.DiscordId
 | 
			
		||||
                    do! updateSlotPlayedFromPlayer player.DiscordId
 | 
			
		||||
                    do! execute player
 | 
			
		||||
                else
 | 
			
		||||
                    do! Messaging.sendFollowUpMessage ctx "Wait till you finish the current spin!"
 | 
			
		||||
 | 
			
		||||
@ -10,97 +10,128 @@ open Degenz
 | 
			
		||||
open Degenz.Messaging
 | 
			
		||||
open Degenz.PlayerInteractions
 | 
			
		||||
 | 
			
		||||
let getBuyItemsEmbed (playerInventory : Inventory) (storeInventory : Inventory) =
 | 
			
		||||
    let embeds , buttons =
 | 
			
		||||
let getItemEmbeds owned (items : StoreItem list) =
 | 
			
		||||
    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
 | 
			
		||||
        |> List.map (fun item ->
 | 
			
		||||
            let embed = DiscordEmbedBuilder()
 | 
			
		||||
            match item with
 | 
			
		||||
            | Hack hack ->
 | 
			
		||||
                embed.AddField($"$GBT Reward  |", string hack.Power, true)
 | 
			
		||||
                     .AddField("Cooldown  |", $"{TimeSpan.FromMinutes(int hack.Cooldown).Minutes} minutes", true)
 | 
			
		||||
                     .WithThumbnail(Embeds.getItemIcon item.Id)
 | 
			
		||||
                     |> ignore
 | 
			
		||||
            | 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 owned = playerInventory |> List.exists (fun i -> i.Id = item.Item.Id)
 | 
			
		||||
            let inStock = item.Available && (item.Stock > 0 || item.LimitStock = false)
 | 
			
		||||
            match owned , inStock with
 | 
			
		||||
            | false , true -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}", $"Buy {item.Item.Name}")
 | 
			
		||||
            | false , false -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}", $"{item.Item.Name} (Out of Stock)", true)
 | 
			
		||||
            | true , _ -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}", $"Own {item.Item.Name}", true)
 | 
			
		||||
            :> DiscordComponent)
 | 
			
		||||
 | 
			
		||||
    DiscordFollowupMessageBuilder()
 | 
			
		||||
        .AddEmbeds(embeds)
 | 
			
		||||
        .AddComponents(buttons)
 | 
			
		||||
        .AsEphemeral(true)
 | 
			
		||||
    let builder =
 | 
			
		||||
        DiscordFollowupMessageBuilder()
 | 
			
		||||
            .AddEmbeds(embeds)
 | 
			
		||||
            .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 =
 | 
			
		||||
        items
 | 
			
		||||
        |> List.map (fun item ->
 | 
			
		||||
            DiscordEmbedBuilder()
 | 
			
		||||
              .AddField("Sell For 💰", $"{item.Price} $GBT", true)
 | 
			
		||||
              .WithTitle($"{item.Name}")
 | 
			
		||||
              .WithColor(WeaponClass.getClassEmbedColor item)
 | 
			
		||||
              .Build()
 | 
			
		||||
            , DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Sell-{item.Id}", $"Sell {item.Name}") :> DiscordComponent)
 | 
			
		||||
        |> List.choose (fun item ->
 | 
			
		||||
            match item.Attributes with
 | 
			
		||||
            | CanSell price ->
 | 
			
		||||
                let builder =
 | 
			
		||||
                    DiscordEmbedBuilder()
 | 
			
		||||
                       .AddField("Sell For 💰", $"{price} $GBT", true)
 | 
			
		||||
                       .WithTitle($"{item.Name}")
 | 
			
		||||
                       .WithColor(WeaponClass.getClassEmbedColor item)
 | 
			
		||||
                       .Build()
 | 
			
		||||
                let button = DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Sell-{item.Id}", $"Sell {item.Name}") :> DiscordComponent
 | 
			
		||||
                Some ( builder , button )
 | 
			
		||||
            | _ -> None)
 | 
			
		||||
        |> List.unzip
 | 
			
		||||
 | 
			
		||||
    // TODO: We should alert the user that they have no sellable items
 | 
			
		||||
    DiscordFollowupMessageBuilder()
 | 
			
		||||
        .AddEmbeds(embeds)
 | 
			
		||||
        .AddComponents(buttons)
 | 
			
		||||
        .AsEphemeral(true)
 | 
			
		||||
 | 
			
		||||
let getConsumeEmbed (items : ItemDetails list) =
 | 
			
		||||
    let embeds , buttons =
 | 
			
		||||
        items
 | 
			
		||||
        |> List.groupBy (fun item -> item.Id)
 | 
			
		||||
        |> List.map (fun (itemId , items ) ->
 | 
			
		||||
            let item = List.head items
 | 
			
		||||
            let foodItem = Inventory.findFoodById itemId items
 | 
			
		||||
            DiscordEmbedBuilder()
 | 
			
		||||
              .AddField($"{foodItem.Item.Name}", $"Total {items.Length}\nBoosts {foodItem.TargetStat} +{foodItem.BoostAmount}", true)
 | 
			
		||||
              .WithTitle($"Food Items")
 | 
			
		||||
              .WithColor(WeaponClass.getClassEmbedColor item)
 | 
			
		||||
              .Build()
 | 
			
		||||
            , DiscordButtonComponent(WeaponClass.getClassButtonColor item, $"Sell-{id}", $"Sell {item.Name}") :> DiscordComponent)
 | 
			
		||||
        |> List.unzip
 | 
			
		||||
 | 
			
		||||
    DiscordFollowupMessageBuilder()
 | 
			
		||||
        .AddEmbeds(embeds)
 | 
			
		||||
        .AddComponents(buttons)
 | 
			
		||||
        .AsEphemeral(true)
 | 
			
		||||
let checkHasStock (item : StoreItem) player =
 | 
			
		||||
    if item.Stock > 0 || item.LimitStock = false
 | 
			
		||||
       then Ok player
 | 
			
		||||
       else Error $"{item.Item.Name} is out of stock! Check back later to purchase"
 | 
			
		||||
 | 
			
		||||
let checkHasSufficientFunds (item : Item) player =
 | 
			
		||||
    if player.Bank - item.Price >= 0<GBT>
 | 
			
		||||
       then Ok player
 | 
			
		||||
       else Error $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT"
 | 
			
		||||
    match item.Attributes with
 | 
			
		||||
    | CanBuy price -> 
 | 
			
		||||
        if player.Bank - price >= 0<GBT>
 | 
			
		||||
           then Ok player
 | 
			
		||||
           else Error $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT"
 | 
			
		||||
    | _ -> Error $"{item.Name} item cannot be bought"
 | 
			
		||||
 | 
			
		||||
let checkAlreadyOwnsItem (item : Item) player =
 | 
			
		||||
    if player.Inventory |> List.exists (fun w -> item.Id = w.Id)
 | 
			
		||||
        then Error $"You already own {item.Name}!"
 | 
			
		||||
        else Ok player
 | 
			
		||||
let checkDoesntExceedStackCap (item : Item) player =
 | 
			
		||||
    let itemCount =
 | 
			
		||||
        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
 | 
			
		||||
    | _ , 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)
 | 
			
		||||
        then Ok player
 | 
			
		||||
        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
 | 
			
		||||
        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 {
 | 
			
		||||
        let playerItems = Inventory.getItemsByType itemType player.Inventory
 | 
			
		||||
        let armoryItems = Inventory.getItemsByType itemType Armory.weapons
 | 
			
		||||
        let itemStore = getBuyItemsEmbed playerItems armoryItems
 | 
			
		||||
        do! ctx.FollowUp itemStore |> Async.AwaitTask
 | 
			
		||||
        do! Analytics.buyWeaponCommand (ctx.GetDiscordMember()) itemType
 | 
			
		||||
        try
 | 
			
		||||
        let channelId = ctx.GetChannel().Id
 | 
			
		||||
        let! items = DbService.getStoreItems channelId
 | 
			
		||||
        if items.Length > 0 then
 | 
			
		||||
            let itemStore = getBuyItemsEmbed player.Inventory items
 | 
			
		||||
            do! ctx.FollowUp itemStore |> Async.AwaitTask
 | 
			
		||||
            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) =
 | 
			
		||||
@ -126,47 +163,123 @@ let sell itemType getItems (ctx : IDiscordContext) =
 | 
			
		||||
        | Ok _ -> let itemStore = getSellEmbed items
 | 
			
		||||
                  do! ctx.FollowUp(itemStore) |> Async.AwaitTask
 | 
			
		||||
        | 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
 | 
			
		||||
let handleBuyItem (ctx : IDiscordContext) itemId =
 | 
			
		||||
    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
 | 
			
		||||
            |> checkHasSufficientFunds item.getItem
 | 
			
		||||
            >>= checkAlreadyOwnsItem item.getItem
 | 
			
		||||
            |> checkHasSufficientFunds item
 | 
			
		||||
            >>= checkHasStock storeItem
 | 
			
		||||
            >>= checkDoesntExceedStackCap item
 | 
			
		||||
            |> handleResultWithResponse ctx (fun player -> async {
 | 
			
		||||
                let newBalance = player.Bank - item.Price
 | 
			
		||||
                let p = { player with Bank = newBalance ; Inventory = item::player.Inventory }
 | 
			
		||||
                do! DbService.updatePlayer p |> Async.Ignore
 | 
			
		||||
                do! sendFollowUpMessage ctx $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining"
 | 
			
		||||
                do! Analytics.buyWeaponButton (ctx.GetDiscordMember()) item
 | 
			
		||||
                let price = match item.Attributes with CanBuy price -> price | _ -> 0<GBT>
 | 
			
		||||
                do! DbService.updatePlayerCurrency -price player |> Async.Ignore
 | 
			
		||||
                do! DbService.addToPlayerInventory player.DiscordId item |> Async.Ignore
 | 
			
		||||
                do! DbService.decrementItemStock 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 =
 | 
			
		||||
    executePlayerAction ctx (fun player -> async {
 | 
			
		||||
        let item = Armory.weapons |> Inventory.findItemById itemId
 | 
			
		||||
        let item = player.Inventory |> Inventory.findItemById itemId
 | 
			
		||||
        do!
 | 
			
		||||
        player
 | 
			
		||||
        |> checkSoldItemAlready item.getItem
 | 
			
		||||
        |> checkSoldItemAlready item
 | 
			
		||||
        |> handleResultWithResponse ctx (fun player -> async {
 | 
			
		||||
            let updatedPlayer = {
 | 
			
		||||
                player with
 | 
			
		||||
                    Bank = player.Bank + item.Price
 | 
			
		||||
                    Inventory = player.Inventory |> List.filter (fun i -> i.Id <> itemId)
 | 
			
		||||
            }
 | 
			
		||||
            do!
 | 
			
		||||
            [ DbService.updatePlayer updatedPlayer |> Async.Ignore
 | 
			
		||||
              DbService.removeShieldEvent updatedPlayer.DiscordId itemId |> Async.Ignore
 | 
			
		||||
              sendFollowUpMessage ctx $"Sold {item.Name} for {item.Price}! Current Balance: {updatedPlayer.Bank}"
 | 
			
		||||
              Analytics.sellWeaponButton (ctx.GetDiscordMember()) item ]
 | 
			
		||||
            |> Async.Parallel
 | 
			
		||||
            |> Async.Ignore
 | 
			
		||||
            match item.Attributes with
 | 
			
		||||
            | CanSell price ->
 | 
			
		||||
                let updatedPlayer = {
 | 
			
		||||
                    player with
 | 
			
		||||
                        Bank = player.Bank + price
 | 
			
		||||
                        Inventory = player.Inventory |> List.filter (fun i -> i.Id <> itemId)
 | 
			
		||||
                }
 | 
			
		||||
                do!
 | 
			
		||||
                [ DbService.updatePlayer updatedPlayer |> Async.Ignore
 | 
			
		||||
                  DbService.removeShieldEvent updatedPlayer.DiscordId itemId |> Async.Ignore
 | 
			
		||||
                  sendFollowUpMessage ctx $"Sold {item.Name} for {price}! Current Balance: {updatedPlayer.Bank}"
 | 
			
		||||
                  Analytics.sellWeaponButton (ctx.GetDiscordMember()) item price ]
 | 
			
		||||
                |> Async.Parallel
 | 
			
		||||
                |> 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 ctx = DiscordEventContext event :> IDiscordContext
 | 
			
		||||
    let id = ctx.GetInteractionId()
 | 
			
		||||
@ -174,6 +287,8 @@ let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEve
 | 
			
		||||
    match id with
 | 
			
		||||
    | id when id.StartsWith("Buy") -> handleBuyItem 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 {
 | 
			
		||||
            let builder = DiscordInteractionResponseBuilder()
 | 
			
		||||
@ -182,6 +297,27 @@ let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEve
 | 
			
		||||
            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() =
 | 
			
		||||
    inherit ApplicationCommandModule ()
 | 
			
		||||
 | 
			
		||||
@ -194,32 +330,30 @@ type Store() =
 | 
			
		||||
                do! Messaging.sendSimpleResponse ctx msg
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
    let checkChannel (ctx : IDiscordContext) =
 | 
			
		||||
        match ctx.GetChannel().Id with
 | 
			
		||||
//        | id when id = GuildEnvironment.channelBackAlley -> buy (Inventory.getItemsByType ItemType.Hack) ctx
 | 
			
		||||
        | id when id = GuildEnvironment.channelArmory -> buy ItemType.Shield ctx
 | 
			
		||||
//        | id when id = GuildEnvironment.channelMarket -> buy (Inventory.getItemsByType ItemType.Food) ctx
 | 
			
		||||
//        | id when id = GuildEnvironment.channelAccessoryShop -> buy (Inventory.getItemsByType ItemType.Accessory) ctx
 | 
			
		||||
        | _ ->
 | 
			
		||||
            task {
 | 
			
		||||
                let msg = $"This channel doesn't have any items to sell"
 | 
			
		||||
                do! Messaging.sendSimpleResponse ctx msg
 | 
			
		||||
            }
 | 
			
		||||
//    let checkChannel (ctx : IDiscordContext) (storeFn : IDiscordContext -> Task) =
 | 
			
		||||
//    let checkChannel (ctx : IDiscordContext) =
 | 
			
		||||
//        match ctx.GetChannel().Id with
 | 
			
		||||
//        | id when id = GuildEnvironment.channelBackAlley -> buy ItemType.Hack ctx
 | 
			
		||||
//        | id when id = GuildEnvironment.channelArmory -> buy ItemType.Shield 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. 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")>]
 | 
			
		||||
    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")>]
 | 
			
		||||
    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")>]
 | 
			
		||||
    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))
 | 
			
		||||
 | 
			
		||||
    [<SlashCommand("consume", "Consume a food item")>]
 | 
			
		||||
    member this.Consume (ctx : InteractionContext) =
 | 
			
		||||
        enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" (Inventory.getItemsByType ItemType.Food))
 | 
			
		||||
    member this.Consume (ctx : InteractionContext) = consume (DiscordInteractionContext ctx)
 | 
			
		||||
 | 
			
		||||
//    [<SlashCommand("inventory", "Check your inventory")>]
 | 
			
		||||
//    member this.Inventory (ctx : InteractionContext) =
 | 
			
		||||
//        enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" (Inventory.getItemsByType ItemType))
 | 
			
		||||
    [<SlashCommand("jpegs", "Check your inventory")>]
 | 
			
		||||
    member this.Inventory (ctx : InteractionContext) =
 | 
			
		||||
        showJpegsEmbed (DiscordInteractionContext ctx)
 | 
			
		||||
 | 
			
		||||
    [<SlashCommand("stats", "Check your stats")>]
 | 
			
		||||
    member this.Stats (ctx : InteractionContext) =
 | 
			
		||||
        showStats (DiscordInteractionContext ctx)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,8 +9,11 @@ open Degenz.Messaging
 | 
			
		||||
 | 
			
		||||
let TrainerAchievement = "FINISHED_TRAINER"
 | 
			
		||||
let Sensei = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" }
 | 
			
		||||
let defaultHack = Armory.weapons |> Inventory.findHackById (int ItemId.Virus)
 | 
			
		||||
let defaultShield = Armory.weapons |> Inventory.findShieldById (int ItemId.Firewall)
 | 
			
		||||
let weapons = DbService.getWeapons () |> Async.RunSynchronously
 | 
			
		||||
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 BeginnerProtectionHours = 24
 | 
			
		||||
 | 
			
		||||
@ -21,7 +24,7 @@ let HackEvent () = {
 | 
			
		||||
        Adversary = Sensei
 | 
			
		||||
        Success = true
 | 
			
		||||
        IsInstigator = true
 | 
			
		||||
        HackId = defaultHack.Item.Id
 | 
			
		||||
        HackId = defaultHack.Id
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
let ShieldEvents () = [
 | 
			
		||||
@ -63,7 +66,7 @@ let handleTrainerStep1 (ctx : IDiscordContext) =
 | 
			
		||||
            |> 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"
 | 
			
		||||
                + "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 =
 | 
			
		||||
            DiscordInteractionResponseBuilder()
 | 
			
		||||
               .WithContent(msg)
 | 
			
		||||
@ -78,7 +81,7 @@ let defend (ctx : IDiscordContext) =
 | 
			
		||||
        do! Messaging.defer ctx
 | 
			
		||||
        let m = ctx.GetDiscordMember()
 | 
			
		||||
        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! Analytics.trainingDojoStep "DefendCommand" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username)
 | 
			
		||||
    } |> Async.StartAsTask :> Task
 | 
			
		||||
@ -101,11 +104,11 @@ let handleDefense (ctx : IDiscordContext) =
 | 
			
		||||
        let embed = Embeds.responseCreatedShield defaultShield
 | 
			
		||||
        do! ctx.FollowUp embed |> Async.AwaitTask
 | 
			
		||||
        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! sendMessage' $"❌ HACKING FAILED!\n\n{playerName} defended hack from <@{Sensei.Id}>!"
 | 
			
		||||
        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)
 | 
			
		||||
    } |> Async.StartAsTask :> Task
 | 
			
		||||
 | 
			
		||||
@ -117,7 +120,7 @@ let handleTrainerStep3 (ctx : IDiscordContext) =
 | 
			
		||||
             .WithContent
 | 
			
		||||
                 ( "Now let’s **HACK** 💻... I want you to **HACK ME**!\n\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! 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
 | 
			
		||||
        match isRightTarget with
 | 
			
		||||
        | 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 embed = Embeds.pickHack "Trainer-4" player bot true
 | 
			
		||||
 | 
			
		||||
@ -166,8 +169,8 @@ let handleHack (ctx : IDiscordContext) =
 | 
			
		||||
**🎁 FREE GIFTS:**
 | 
			
		||||
Here, I'm going to gift you:
 | 
			
		||||
 | 
			
		||||
1. A hack - `{defaultHack.Item.Name}`,
 | 
			
		||||
2. A shield - `{defaultShield.Item.Name}`,
 | 
			
		||||
1. A hack - `{defaultHack.Name}`,
 | 
			
		||||
2. A shield - `{defaultShield.Name}`,
 | 
			
		||||
3. `{CurrencyGift} 💰 $GBT`,
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    if not completed then
 | 
			
		||||
        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)
 | 
			
		||||
                then inventory
 | 
			
		||||
                else weapon::inventory
 | 
			
		||||
@ -209,8 +212,8 @@ let handleArsenal (ctx : IDiscordContext) = PlayerInteractions.executePlayerActi
 | 
			
		||||
                    |> List.consTo (HackEvent())
 | 
			
		||||
                Inventory =
 | 
			
		||||
                    player.Inventory
 | 
			
		||||
                    |> addIfDoesntExist (Hack defaultHack)
 | 
			
		||||
                    |> addIfDoesntExist (Shield defaultShield)
 | 
			
		||||
                    |> addIfDoesntExist hackItem
 | 
			
		||||
                    |> addIfDoesntExist shieldItem
 | 
			
		||||
                Bank = player.Bank + CurrencyGift
 | 
			
		||||
        }
 | 
			
		||||
        do! DbService.updatePlayer updatedPlayer |> Async.Ignore
 | 
			
		||||
@ -227,7 +230,7 @@ let handleArsenal (ctx : IDiscordContext) = PlayerInteractions.executePlayerActi
 | 
			
		||||
        do! ctx.FollowUp(embed) |> Async.AwaitTask
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
        do! ctx.FollowUp(embed) |> Async.AwaitTask
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -35,9 +35,9 @@ let channelWhitelist = getId "CHANNEL_WHITELIST"
 | 
			
		||||
let channelShelters = getId "CHANNEL_SHELTERS"
 | 
			
		||||
let channelSlots = getId "CHANNEL_SLOTS"
 | 
			
		||||
//let channelJackpotNum = getId "CHANNEL_JACKPOTNUM"
 | 
			
		||||
//let channelBackAlley = getId "CHANNEL_BACKALLEY"
 | 
			
		||||
//let channelMarket = getId "CHANNEL_MARKET"
 | 
			
		||||
//let channelAccessoryShop = getId "CHANNEL_ACCESSORIES"
 | 
			
		||||
let channelBackAlley = getId "CHANNEL_BACKALLEY"
 | 
			
		||||
let channelMarket = getId "CHANNEL_MARKET"
 | 
			
		||||
let channelAccessoryShop = getId "CHANNEL_ACCESSORIES"
 | 
			
		||||
 | 
			
		||||
//let channelThievery = getId "CHANNEL_THIEVERY"
 | 
			
		||||
let botIdHackerBattle = getId "BOT_HACKER_BATTLE"
 | 
			
		||||
@ -52,3 +52,4 @@ let roleAdmin = getId "ROLE_ADMIN"
 | 
			
		||||
let mutable botClientRecruit : DiscordClient option = None
 | 
			
		||||
let mutable botClientHacker : DiscordClient option = None
 | 
			
		||||
let mutable botClientSlots : DiscordClient option = None
 | 
			
		||||
let mutable botClientStore : DiscordClient option = None
 | 
			
		||||
 | 
			
		||||
@ -178,6 +178,48 @@ let getInvitedUserCount userId =
 | 
			
		||||
    |> Sql.executeRowAsync (fun read -> read.int "count")
 | 
			
		||||
    |> 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 rewardMsg =
 | 
			
		||||
        $"**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! wlItem = DbService.getWhitelistItem ()
 | 
			
		||||
        let! wlItem = getWhitelistItem ()
 | 
			
		||||
        let! availability = tryGrantWhitelist ctx wlItem.Stock wlItem.Price
 | 
			
		||||
        match availability with
 | 
			
		||||
        | NotAHacker -> whitelistEmbed.Description <- notAHackerMsg
 | 
			
		||||
@ -480,7 +522,7 @@ let handleBuyWhitelist (ctx : IDiscordContext) =
 | 
			
		||||
        let builder = DiscordInteractionResponseBuilder().AsEphemeral(true)
 | 
			
		||||
        do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder)
 | 
			
		||||
 | 
			
		||||
        let! wlItem = DbService.getWhitelistItem ()
 | 
			
		||||
        let! wlItem = getWhitelistItem ()
 | 
			
		||||
        let builder = DiscordFollowupMessageBuilder().AsEphemeral(true)
 | 
			
		||||
        match! tryGrantWhitelist ctx wlItem.Stock wlItem.Price with
 | 
			
		||||
        | NotAHacker ->
 | 
			
		||||
@ -499,7 +541,7 @@ let handleBuyWhitelist (ctx : IDiscordContext) =
 | 
			
		||||
            builder.Content <- $"We just ran out of stock, tough shit"
 | 
			
		||||
            do! ctx.FollowUp(builder)
 | 
			
		||||
        | Granted player ->
 | 
			
		||||
            match! DbService.updateWhitelistStock () with
 | 
			
		||||
            match! updateWhitelistStock () with
 | 
			
		||||
            | true ->
 | 
			
		||||
                let embed = DiscordEmbedBuilder()
 | 
			
		||||
                embed.Description <- buyWhitelistMsg
 | 
			
		||||
@ -588,10 +630,10 @@ let handleGuildMemberAdded _ (eventArgs : GuildMemberAddEventArgs) =
 | 
			
		||||
            do! processNewUser eventArgs
 | 
			
		||||
    } :> Task
 | 
			
		||||
 | 
			
		||||
let rec setWhitelistStock amount (ctx : IDiscordContext) =
 | 
			
		||||
let setCurrentWhitelistStock amount (ctx : IDiscordContext) =
 | 
			
		||||
    task {
 | 
			
		||||
        do! Messaging.defer ctx
 | 
			
		||||
        let! result = DbService.setWhitelistStock amount
 | 
			
		||||
        let! result = setWhitelistStock amount
 | 
			
		||||
        if result then
 | 
			
		||||
            do! Messaging.sendFollowUpMessage ctx $"Set Whitelist stock to {amount}"
 | 
			
		||||
        else
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										304
									
								
								Bot/Items.json
									
									
									
									
									
								
							
							
						
						
									
										304
									
								
								Bot/Items.json
									
									
									
									
									
								
							@ -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
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -5,9 +5,9 @@ framework: net6.0, netstandard2.0, netstandard2.1
 | 
			
		||||
 | 
			
		||||
nuget FSharp.Core >= 6.0.0
 | 
			
		||||
 | 
			
		||||
nuget DSharpPlus >= 4.2.0-nightly-01105
 | 
			
		||||
nuget DSharpPlus.Interactivity >= 4.2.0-nightly-01105
 | 
			
		||||
nuget DSharpPlus.SlashCommands >= 4.2.0-nightly-01105
 | 
			
		||||
nuget DSharpPlus >= 4.2.0-nightly-01125
 | 
			
		||||
nuget DSharpPlus.Interactivity >= 4.2.0-nightly-01125
 | 
			
		||||
nuget DSharpPlus.SlashCommands >= 4.2.0-nightly-01125
 | 
			
		||||
 | 
			
		||||
nuget MongoDB.Driver
 | 
			
		||||
nuget dotenv.net 3.1.1
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										10
									
								
								paket.lock
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								paket.lock
									
									
									
									
									
								
							@ -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))
 | 
			
		||||
    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))
 | 
			
		||||
    DSharpPlus (4.2.0-nightly-01105)
 | 
			
		||||
    DSharpPlus (4.3.0-nightly-01125)
 | 
			
		||||
      Emzi0767.Common (>= 2.6.2)
 | 
			
		||||
      Microsoft.Extensions.Logging.Abstractions (>= 5.0)
 | 
			
		||||
      Newtonsoft.Json (>= 13.0.1)
 | 
			
		||||
@ -18,11 +18,11 @@ NUGET
 | 
			
		||||
      System.Net.WebSockets.Client (>= 4.3.2)
 | 
			
		||||
      System.Runtime.InteropServices.RuntimeInformation (>= 4.3)
 | 
			
		||||
      System.Threading.Channels (>= 5.0)
 | 
			
		||||
    DSharpPlus.Interactivity (4.2.0-nightly-01105)
 | 
			
		||||
    DSharpPlus.Interactivity (4.3.0-nightly-01125)
 | 
			
		||||
      ConcurrentHashSet (>= 1.1)
 | 
			
		||||
      DSharpPlus (>= 4.2.0-nightly-01105)
 | 
			
		||||
    DSharpPlus.SlashCommands (4.2.0-nightly-01105)
 | 
			
		||||
      DSharpPlus (>= 4.2.0-nightly-01105)
 | 
			
		||||
      DSharpPlus (>= 4.3.0-nightly-01125)
 | 
			
		||||
    DSharpPlus.SlashCommands (4.3.0-nightly-01125)
 | 
			
		||||
      DSharpPlus (>= 4.3.0-nightly-01125)
 | 
			
		||||
      Microsoft.Extensions.DependencyInjection (>= 5.0.1)
 | 
			
		||||
    Emzi0767.Common (2.6.2)
 | 
			
		||||
      System.Collections.Immutable (>= 5.0)
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user