Merge branch 'staging'
This commit is contained in:
commit
e7bb53006f
77
Bot/Bot.fs
77
Bot/Bot.fs
@ -1,12 +1,11 @@
|
|||||||
module Degenz.Bot
|
module Degenz.Bot
|
||||||
|
|
||||||
|
open System.IO
|
||||||
|
open System.IO.Pipes
|
||||||
open System.Threading.Tasks
|
open System.Threading.Tasks
|
||||||
open DSharpPlus
|
open DSharpPlus
|
||||||
open DSharpPlus.SlashCommands
|
open DSharpPlus.SlashCommands
|
||||||
open Degenz
|
open Degenz
|
||||||
open Degenz.HackerBattle
|
|
||||||
open Degenz.Store
|
|
||||||
open Degenz.Thief
|
|
||||||
open Emzi0767.Utilities
|
open Emzi0767.Utilities
|
||||||
//open Degenz.SlotMachine
|
//open Degenz.SlotMachine
|
||||||
|
|
||||||
@ -17,11 +16,10 @@ let guild = GuildEnvironment.guildId
|
|||||||
let hackerBattleConfig = DiscordConfiguration()
|
let hackerBattleConfig = DiscordConfiguration()
|
||||||
let storeConfig = DiscordConfiguration()
|
let storeConfig = DiscordConfiguration()
|
||||||
let stealConfig = DiscordConfiguration()
|
let stealConfig = DiscordConfiguration()
|
||||||
|
let inviterConfig = DiscordConfiguration()
|
||||||
//let slotMachineConfig = DiscordConfiguration()
|
//let slotMachineConfig = DiscordConfiguration()
|
||||||
//hackerBattleConfig.MinimumLogLevel <- Microsoft.Extensions.Logging.LogLevel.Trace
|
//hackerBattleConfig.MinimumLogLevel <- Microsoft.Extensions.Logging.LogLevel.Trace
|
||||||
|
//storeConfig.MinimumLogLevel <- Microsoft.Extensions.Logging.LogLevel.Trace
|
||||||
//let configs = [| hackerBattleConfig ; storeConfig ; slotMachineConfig ; |]
|
|
||||||
let configs = [ hackerBattleConfig ; storeConfig ; stealConfig ]
|
|
||||||
|
|
||||||
hackerBattleConfig.TokenType <- TokenType.Bot
|
hackerBattleConfig.TokenType <- TokenType.Bot
|
||||||
hackerBattleConfig.Intents <- DiscordIntents.All
|
hackerBattleConfig.Intents <- DiscordIntents.All
|
||||||
@ -32,31 +30,40 @@ storeConfig.Intents <- DiscordIntents.All
|
|||||||
stealConfig.TokenType <- TokenType.Bot
|
stealConfig.TokenType <- TokenType.Bot
|
||||||
stealConfig.Intents <- DiscordIntents.All
|
stealConfig.Intents <- DiscordIntents.All
|
||||||
|
|
||||||
|
inviterConfig.TokenType <- TokenType.Bot
|
||||||
|
inviterConfig.Intents <- DiscordIntents.All
|
||||||
|
|
||||||
hackerBattleConfig.Token <- GuildEnvironment.tokenHackerBattle
|
hackerBattleConfig.Token <- GuildEnvironment.tokenHackerBattle
|
||||||
storeConfig.Token <- GuildEnvironment.tokenStore
|
storeConfig.Token <- GuildEnvironment.tokenStore
|
||||||
stealConfig.Token <- GuildEnvironment.tokenSteal
|
stealConfig.Token <- GuildEnvironment.tokenSteal
|
||||||
|
inviterConfig.Token <- GuildEnvironment.tokenInviter
|
||||||
//slotMachineConfig.Token <- Environment.GetEnvironmentVariable("BOT_SLOT_MACHINE")
|
//slotMachineConfig.Token <- Environment.GetEnvironmentVariable("BOT_SLOT_MACHINE")
|
||||||
|
|
||||||
let hackerBattleBot = new DiscordClient(hackerBattleConfig)
|
let hackerBattleBot = new DiscordClient(hackerBattleConfig)
|
||||||
let storeBot = new DiscordClient(storeConfig)
|
let storeBot = new DiscordClient(storeConfig)
|
||||||
let stealBot = new DiscordClient(stealConfig)
|
let stealBot = new DiscordClient(stealConfig)
|
||||||
|
let inviterBot = new DiscordClient(inviterConfig)
|
||||||
//let slotMachineBot = new DiscordClient(slotMachineConfig)
|
//let slotMachineBot = new DiscordClient(slotMachineConfig)
|
||||||
|
|
||||||
//let clients = [| hackerBattleBot ; storeBot ; slotMachineBot |]
|
//let clients = [| hackerBattleBot ; storeBot ; slotMachineBot |]
|
||||||
let hackerCommands = hackerBattleBot.UseSlashCommands()
|
let hackerCommands = hackerBattleBot.UseSlashCommands()
|
||||||
let storeCommands = storeBot.UseSlashCommands()
|
let storeCommands = storeBot.UseSlashCommands()
|
||||||
let stealCommands = stealBot.UseSlashCommands()
|
let stealCommands = stealBot.UseSlashCommands()
|
||||||
|
let inviterCommands = inviterBot.UseSlashCommands()
|
||||||
//let sc3 = slotMachineBot.UseSlashCommands()
|
//let sc3 = slotMachineBot.UseSlashCommands()
|
||||||
|
|
||||||
hackerCommands.RegisterCommands<HackerGame>(guild);
|
hackerCommands.RegisterCommands<HackerBattle.HackerGame>(guild);
|
||||||
storeCommands.RegisterCommands<Store>(guild);
|
storeCommands.RegisterCommands<Store.Store>(guild);
|
||||||
stealCommands.RegisterCommands<StealGame>(guild);
|
stealCommands.RegisterCommands<Thief.StealGame>(guild);
|
||||||
|
inviterCommands.RegisterCommands<InviteTracker.Inviter>(guild);
|
||||||
//hackerCommands.RegisterCommands<RPSGame>(guild);
|
//hackerCommands.RegisterCommands<RPSGame>(guild);
|
||||||
//sc3.RegisterCommands<SlotMachine>(guild);
|
//sc3.RegisterCommands<SlotMachine>(guild);
|
||||||
|
|
||||||
hackerBattleBot.add_ComponentInteractionCreated(AsyncEventHandler(HackerBattle.handleButtonEvent))
|
hackerBattleBot.add_ComponentInteractionCreated(AsyncEventHandler(HackerBattle.handleButtonEvent))
|
||||||
storeBot.add_ComponentInteractionCreated(AsyncEventHandler(Store.handleStoreEvents))
|
storeBot.add_ComponentInteractionCreated(AsyncEventHandler(Store.handleStoreEvents))
|
||||||
stealBot.add_ComponentInteractionCreated(AsyncEventHandler(Thief.handleStealButton))
|
stealBot.add_ComponentInteractionCreated(AsyncEventHandler(Thief.handleStealButton))
|
||||||
|
inviterBot.add_GuildMemberAdded(AsyncEventHandler(InviteTracker.handleGuildMemberAdded))
|
||||||
|
inviterBot.add_GuildMemberRemoved(AsyncEventHandler(InviteTracker.handleGuildMemberRemoved))
|
||||||
|
|
||||||
let asdf (_ : DiscordClient) (event : DSharpPlus.EventArgs.InteractionCreateEventArgs) =
|
let asdf (_ : DiscordClient) (event : DSharpPlus.EventArgs.InteractionCreateEventArgs) =
|
||||||
async {
|
async {
|
||||||
@ -74,8 +81,8 @@ let asdf (_ : DiscordClient) (event : DSharpPlus.EventArgs.InteractionCreateEven
|
|||||||
:> Task
|
:> Task
|
||||||
//hackerBattleBot.add_InteractionCreated(AsyncEventHandler(asdf))
|
//hackerBattleBot.add_InteractionCreated(AsyncEventHandler(asdf))
|
||||||
|
|
||||||
if guild <> 922419263275425832uL then
|
//if guild <> 922419263275425832uL then
|
||||||
Trainer.sendInitialEmbed hackerBattleBot
|
// Trainer.sendInitialEmbed hackerBattleBot
|
||||||
|
|
||||||
hackerBattleBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
|
hackerBattleBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
|
||||||
GuildEnvironment.botUserHackerBattle <- Some hackerBattleBot.CurrentUser
|
GuildEnvironment.botUserHackerBattle <- Some hackerBattleBot.CurrentUser
|
||||||
@ -85,32 +92,38 @@ GuildEnvironment.botUserArmory <- Some storeBot.CurrentUser
|
|||||||
|
|
||||||
//stealBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
|
//stealBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
|
||||||
|
|
||||||
//async {
|
//inviterBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
|
||||||
// let! user = hackerBattleBot.GetUserAsync(GuildEnvironment.botIdHackerBattle) |> Async.AwaitTask
|
|
||||||
// if user <> null then
|
|
||||||
// GuildEnvironment.botUserHackerBattle <- Some user
|
|
||||||
// return ()
|
|
||||||
//} |> Async.RunSynchronously
|
|
||||||
|
|
||||||
//async {
|
|
||||||
// let! user = storeBot.GetUserAsync(GuildEnvironment.botIdHackerBattle) |> Async.AwaitTask
|
|
||||||
// if user <> null then
|
|
||||||
// GuildEnvironment.botUserHackerBattle <- Some user
|
|
||||||
// return ()
|
|
||||||
//} |> Async.RunSynchronously
|
|
||||||
|
|
||||||
if guild = 922419263275425832uL then
|
let rec loop areBotsRunning =
|
||||||
let interactionsConfig = DiscordConfiguration()
|
async {
|
||||||
interactionsConfig.TokenType <- TokenType.Bot
|
if not (File.Exists "fsharp-bots") then
|
||||||
interactionsConfig.Intents <- DiscordIntents.All
|
use file = File.Create "fsharp-bots"
|
||||||
interactionsConfig.Token <- GuildEnvironment.tokenPlayerInteractions
|
file.Flush()
|
||||||
|
|
||||||
let interactionsBot = new DiscordClient(interactionsConfig)
|
let! file = File.ReadAllTextAsync("fsharp-bots") |> Async.AwaitTask
|
||||||
|
|
||||||
let commands = interactionsBot.UseSlashCommands()
|
let! ran =
|
||||||
commands.RegisterCommands<PlayerInteractions.PlayerInteractions>(guild)
|
async {
|
||||||
|
if areBotsRunning && file.StartsWith "kill" then
|
||||||
|
printfn "Disconnecting bots"
|
||||||
|
do! hackerBattleBot.DisconnectAsync() |> Async.AwaitTask
|
||||||
|
do! storeBot.DisconnectAsync() |> Async.AwaitTask
|
||||||
|
return false
|
||||||
|
elif not areBotsRunning && not (file.StartsWith "kill") then
|
||||||
|
printfn "Reconnecting bots"
|
||||||
|
do! hackerBattleBot.ConnectAsync() |> Async.AwaitTask
|
||||||
|
do! storeBot.ConnectAsync() |> Async.AwaitTask
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return areBotsRunning
|
||||||
|
}
|
||||||
|
|
||||||
interactionsBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
|
do! Async.Sleep 3000
|
||||||
|
return! loop (ran)
|
||||||
|
}
|
||||||
|
|
||||||
|
Async.Start (loop true)
|
||||||
|
|
||||||
|
|
||||||
Task.Delay(-1)
|
Task.Delay(-1)
|
||||||
|
@ -11,21 +11,23 @@
|
|||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="paket.references" />
|
<Content Include="paket.references" />
|
||||||
|
<Compile Include="Prelude.fs" />
|
||||||
<Compile Include="GuildEnvironment.fs" />
|
<Compile Include="GuildEnvironment.fs" />
|
||||||
<Compile Include="Game.fs" />
|
<Compile Include="Messaging.fs" />
|
||||||
<Compile Include="XP.fs" />
|
<Compile Include="GameTypes.fs" />
|
||||||
|
<Compile Include="GameHelpers.fs" />
|
||||||
|
<Compile Include="DbService.fs" />
|
||||||
<Compile Include="PlayerInteractions.fs" />
|
<Compile Include="PlayerInteractions.fs" />
|
||||||
|
<Compile Include="InviteTracker.fs" />
|
||||||
|
<Compile Include="XP.fs" />
|
||||||
<Compile Include="Embeds.fs" />
|
<Compile Include="Embeds.fs" />
|
||||||
<Compile Include="SlotMachine.fs" />
|
<Compile Include="Games\SlotMachine.fs" />
|
||||||
<Compile Include="Thief.fs" />
|
<Compile Include="Games\Thief.fs" />
|
||||||
<Compile Include="RockPaperScissors.fs" />
|
<Compile Include="Games\RockPaperScissors.fs" />
|
||||||
<Compile Include="Store.fs" />
|
<Compile Include="Games\Store.fs" />
|
||||||
<Compile Include="Trainer.fs" />
|
<Compile Include="Games\Trainer.fs" />
|
||||||
<Compile Include="HackerBattle.fs" />
|
<Compile Include="Games\HackerBattle.fs" />
|
||||||
<Compile Include="Bot.fs" />
|
<Compile Include="Bot.fs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\DbService\DbService.fsproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="..\.paket\Paket.Restore.targets" />
|
<Import Project="..\.paket\Paket.Restore.targets" />
|
||||||
</Project>
|
</Project>
|
@ -1,32 +1,26 @@
|
|||||||
module Degenz.DbService
|
module Degenz.DbService
|
||||||
|
|
||||||
open System.Security.Cryptography.X509Certificates
|
|
||||||
open Degenz.Types
|
|
||||||
|
|
||||||
open System
|
open System
|
||||||
open Npgsql.FSharp
|
open Npgsql.FSharp
|
||||||
|
open Degenz
|
||||||
|
|
||||||
|
let connStr = GuildEnvironment.connectionString
|
||||||
|
|
||||||
type User = {
|
type User = {
|
||||||
Name : string
|
Name : string
|
||||||
DiscordId : uint64
|
DiscordId : uint64
|
||||||
Bank : int<GBT>
|
Bank : int<GBT>
|
||||||
|
Inventory : int list
|
||||||
Strength : int
|
Strength : int
|
||||||
Inventory : int array
|
Focus : int
|
||||||
|
Charisma : int
|
||||||
|
Luck : int
|
||||||
}
|
}
|
||||||
|
|
||||||
let mapBack user : PlayerData =
|
let getPlayerEvents (did : uint64) =
|
||||||
{ DiscordId = user.DiscordId
|
|
||||||
Name = user.Name
|
|
||||||
Inventory = user.Inventory |> Array.choose (fun id -> Armory.battleItems |> Array.tryFind (fun i -> i.Id = id))
|
|
||||||
Events = [||]
|
|
||||||
Traits = { PlayerTraits.empty with Strength = user.Strength }
|
|
||||||
Bank = user.Bank
|
|
||||||
}
|
|
||||||
|
|
||||||
let getPlayerEvents connStr (player : PlayerData) =
|
|
||||||
connStr
|
connStr
|
||||||
|> Sql.connect
|
|> Sql.connect
|
||||||
|> Sql.parameters [ "did", Sql.string (string player.DiscordId) ]
|
|> Sql.parameters [ "did", Sql.string (string did) ]
|
||||||
|> Sql.query """
|
|> Sql.query """
|
||||||
WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did)
|
WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did)
|
||||||
SELECT event_type, success, is_instigator, item_id, cooldown, adversary_id, adversary_name, created_at
|
SELECT event_type, success, is_instigator, item_id, cooldown, adversary_id, adversary_name, created_at
|
||||||
@ -50,59 +44,94 @@ let getPlayerEvents connStr (player : PlayerData) =
|
|||||||
)
|
)
|
||||||
|> Async.AwaitTask
|
|> Async.AwaitTask
|
||||||
|
|
||||||
let tryFindPlayer connStr (discordId : uint64) =
|
let updatePlayerStats (player : PlayerData) =
|
||||||
async {
|
connStr
|
||||||
try
|
|> Sql.connect
|
||||||
let! user =
|
|> Sql.parameters
|
||||||
// use cert = new X509Certificate2("~/Downloads/ca-certificate.crt")
|
[ ( "did" , Sql.string (string player.DiscordId) )
|
||||||
// (Uri connStr)
|
( "strength", Sql.int player.Stats.Strength.Amount )
|
||||||
// |> Sql.fromUriToConfig
|
( "focus", Sql.int player.Stats.Focus.Amount )
|
||||||
// |> Sql.requireSslMode
|
( "charisma", Sql.int player.Stats.Charisma.Amount )
|
||||||
// |> Sql.formatConnectionString
|
( "luck", Sql.int player.Stats.Luck.Amount ) ]
|
||||||
// |> Sql.clientCertificate cert
|
|> Sql.query """
|
||||||
connStr
|
WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did)
|
||||||
|> Sql.connect
|
UPDATE player_stat SET strength = @strength, focus = @focus, charisma = @charisma, luck = @luck,
|
||||||
|> Sql.parameters [ "did", Sql.string (string discordId) ]
|
updated_at = now() at time zone 'utc'
|
||||||
|> Sql.query """
|
FROM usr WHERE usr.id = user_id;
|
||||||
SELECT discord_id, display_name, gbt, strength, inventory FROM "user" WHERE discord_id = @did
|
"""
|
||||||
"""
|
|> Sql.executeNonQueryAsync
|
||||||
|> Sql.executeAsync (fun read ->
|
|> Async.AwaitTask
|
||||||
{
|
|
||||||
DiscordId = read.string "discord_id" |> uint64
|
|
||||||
Name = read.string "display_name"
|
|
||||||
Bank = read.int "gbt" * 1<GBT>
|
|
||||||
Strength = read.int "strength"
|
|
||||||
Inventory = read.intArray "inventory"
|
|
||||||
})
|
|
||||||
|> Async.AwaitTask
|
|
||||||
match List.tryHead user with
|
|
||||||
| None -> return None
|
|
||||||
| Some u ->
|
|
||||||
let player = mapBack u
|
|
||||||
let! events = getPlayerEvents connStr player
|
|
||||||
return Some { player with Events = events |> List.toArray }
|
|
||||||
with e ->
|
|
||||||
printfn $"Got an error{e.Message}"
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
let updatePlayer connStr (player : PlayerData) =
|
let tryFindPlayer (discordId : uint64) = async {
|
||||||
|
try
|
||||||
|
let! user =
|
||||||
|
// use cert = new X509Certificate2("~/Downloads/ca-certificate.crt")
|
||||||
|
// (Uri connStr)
|
||||||
|
// |> Sql.fromUriToConfig
|
||||||
|
// |> Sql.requireSslMode
|
||||||
|
// |> Sql.formatConnectionString
|
||||||
|
// |> Sql.clientCertificate cert
|
||||||
|
connStr
|
||||||
|
|> Sql.connect
|
||||||
|
|> Sql.parameters [ "did", Sql.string (string discordId) ]
|
||||||
|
|> Sql.query """
|
||||||
|
SELECT discord_id, display_name, gbt, inventory, 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.int "gbt" * 1<GBT>
|
||||||
|
Inventory = inv |> Array.toList
|
||||||
|
Strength = read.int "strength"
|
||||||
|
Focus = read.int "focus"
|
||||||
|
Charisma = read.int "charisma"
|
||||||
|
Luck = read.int "luck"
|
||||||
|
})
|
||||||
|
|> 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 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
|
||||||
|
let luck = PlayerStats.calculateActiveStat StatId.Luck u.Luck inventory
|
||||||
|
return Some
|
||||||
|
{ DiscordId = u.DiscordId
|
||||||
|
Name = u.Name
|
||||||
|
Inventory = inventory
|
||||||
|
Events = events
|
||||||
|
Stats = { Strength = strength ; Focus = focus ; Charisma = charisma ; Luck = luck }
|
||||||
|
Bank = u.Bank }
|
||||||
|
with e ->
|
||||||
|
printfn $"Got an error{e.Message}"
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
let updatePlayer (player : PlayerData) =
|
||||||
connStr
|
connStr
|
||||||
|> Sql.connect
|
|> Sql.connect
|
||||||
|> Sql.parameters [
|
|> Sql.parameters [
|
||||||
"did", Sql.string (string player.DiscordId)
|
"did", Sql.string (string player.DiscordId)
|
||||||
"gbt", Sql.int (int player.Bank)
|
"gbt", Sql.int (int player.Bank)
|
||||||
"str", Sql.int (int player.Traits.Strength)
|
"inv", Sql.intArray (player.Inventory |> Array.ofList |> Array.map (fun item -> item.Id))
|
||||||
"inv", Sql.intArray (player.Inventory |> Array.map (fun i -> i.Id))
|
"strength", Sql.int player.Stats.Strength.Amount
|
||||||
]
|
"focus", Sql.int player.Stats.Focus.Amount
|
||||||
|> Sql.query """
|
"charisma", Sql.int player.Stats.Charisma.Amount
|
||||||
UPDATE "user" SET gbt = @gbt, strength = @str, inventory = @inv
|
"luck", Sql.int player.Stats.Luck.Amount
|
||||||
|
] |> Sql.query """
|
||||||
|
UPDATE "user" SET gbt = @gbt, inventory = @inv,
|
||||||
|
strength = @strength, focus = @focus, charisma = @charisma, luck = @luck
|
||||||
WHERE discord_id = @did
|
WHERE discord_id = @did
|
||||||
"""
|
"""
|
||||||
|> Sql.executeNonQueryAsync
|
|> Sql.executeNonQueryAsync
|
||||||
|> Async.AwaitTask
|
|> Async.AwaitTask
|
||||||
|
|
||||||
let addAchievement connStr (did : uint64) (achievement : string) =
|
let addAchievement (did : uint64) (achievement : string) =
|
||||||
connStr
|
connStr
|
||||||
|> Sql.connect
|
|> Sql.connect
|
||||||
|> Sql.parameters
|
|> Sql.parameters
|
||||||
@ -116,7 +145,7 @@ let addAchievement connStr (did : uint64) (achievement : string) =
|
|||||||
|> Sql.executeNonQueryAsync
|
|> Sql.executeNonQueryAsync
|
||||||
|> Async.AwaitTask
|
|> Async.AwaitTask
|
||||||
|
|
||||||
let checkHasAchievement connStr (did : uint64) (achievement : string) = async {
|
let checkHasAchievement (did : uint64) (achievement : string) = async {
|
||||||
let! result =
|
let! result =
|
||||||
connStr
|
connStr
|
||||||
|> Sql.connect
|
|> Sql.connect
|
||||||
@ -133,7 +162,7 @@ let checkHasAchievement connStr (did : uint64) (achievement : string) = async {
|
|||||||
return List.isEmpty result |> not
|
return List.isEmpty result |> not
|
||||||
}
|
}
|
||||||
|
|
||||||
let removeShieldEvent connStr (did : uint64) shieldId =
|
let removeShieldEvent (did : uint64) shieldId =
|
||||||
connStr
|
connStr
|
||||||
|> Sql.connect
|
|> Sql.connect
|
||||||
|> Sql.parameters
|
|> Sql.parameters
|
||||||
@ -146,7 +175,7 @@ let removeShieldEvent connStr (did : uint64) shieldId =
|
|||||||
|> Sql.executeNonQueryAsync
|
|> Sql.executeNonQueryAsync
|
||||||
|> Async.AwaitTask
|
|> Async.AwaitTask
|
||||||
|
|
||||||
let addPlayerEvent connStr (did : uint64) (playerEvent : PlayerEvent) =
|
let addPlayerEvent (did : uint64) (playerEvent : PlayerEvent) =
|
||||||
let sqlParams , query =
|
let sqlParams , query =
|
||||||
match playerEvent.Type with
|
match playerEvent.Type with
|
||||||
| Hacking h ->
|
| Hacking h ->
|
143
Bot/Embeds.fs
143
Bot/Embeds.fs
@ -1,7 +1,6 @@
|
|||||||
module Degenz.Embeds
|
module Degenz.Embeds
|
||||||
|
|
||||||
open System
|
open System
|
||||||
open DSharpPlus
|
|
||||||
open Degenz.Messaging
|
open Degenz.Messaging
|
||||||
open Degenz.Types
|
open Degenz.Types
|
||||||
open DSharpPlus.Entities
|
open DSharpPlus.Entities
|
||||||
@ -9,43 +8,37 @@ open DSharpPlus.Entities
|
|||||||
let hackGif = "https://s10.gifyu.com/images/Hacker-Degenz-V20ce8eb832734aa62-min.gif"
|
let hackGif = "https://s10.gifyu.com/images/Hacker-Degenz-V20ce8eb832734aa62-min.gif"
|
||||||
let shieldGif = "https://s10.gifyu.com/images/Defense-Degenz-V2-min.gif"
|
let shieldGif = "https://s10.gifyu.com/images/Defense-Degenz-V2-min.gif"
|
||||||
|
|
||||||
let getHackIcon = function
|
let getItemIcon id =
|
||||||
| HackId.Virus -> "https://s10.gifyu.com/images/Virus-icon.jpg"
|
match enum<ItemId>(id) with
|
||||||
| HackId.RemoteAccess -> "https://s10.gifyu.com/images/Mind-Control-Degenz-V2-min.jpg"
|
| ItemId.Virus -> "https://s10.gifyu.com/images/Virus-icon.jpg"
|
||||||
| HackId.Worm -> "https://s10.gifyu.com/images/WormBugAttack_Degenz-min.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
|
| _ -> hackGif
|
||||||
|
|
||||||
let getShieldIcon = function
|
let getItemGif id =
|
||||||
| ShieldId.Firewall -> "https://s10.gifyu.com/images/Defense-GIF-1-Degenz-1.jpg"
|
match enum<ItemId>(id) with
|
||||||
| ShieldId.Encryption -> "https://s10.gifyu.com/images/Encryption-Degenz-V2-1-min.jpg"
|
| ItemId.Virus -> "https://s10.gifyu.com/images/Attack-DegenZ-1.gif"
|
||||||
| ShieldId.Cypher -> "https://s10.gifyu.com/images/Cypher-Smaller.jpg"
|
| ItemId.RemoteAccess -> "https://s10.gifyu.com/images/Mind-Control-Degenz-V2-min.gif"
|
||||||
| _ -> shieldGif
|
| ItemId.Worm -> "https://s10.gifyu.com/images/WormBugAttack_Degenz-min.gif"
|
||||||
|
| ItemId.Firewall -> "https://s10.gifyu.com/images/Defense-GIF-1-Degenz-min.gif"
|
||||||
let getHackGif = function
|
| ItemId.Encryption -> "https://s10.gifyu.com/images/Encryption-Degenz-V2-1-min.gif"
|
||||||
| HackId.Virus -> "https://s10.gifyu.com/images/Attack-DegenZ-1.gif"
|
| ItemId.Cypher -> "https://s10.gifyu.com/images/Cypher-Smaller.gif"
|
||||||
| HackId.RemoteAccess -> "https://s10.gifyu.com/images/Mind-Control-Degenz-V2-min.gif"
|
|
||||||
| HackId.Worm -> "https://s10.gifyu.com/images/WormBugAttack_Degenz-min.gif"
|
|
||||||
| _ -> hackGif
|
| _ -> hackGif
|
||||||
|
|
||||||
let getShieldGif = function
|
let constructButtons (actionId: string) (buttonInfo : string) (player: PlayerData) (items : Inventory) ignoreCooldown =
|
||||||
| ShieldId.Firewall -> "https://s10.gifyu.com/images/Defense-GIF-1-Degenz-min.gif"
|
items
|
||||||
| ShieldId.Encryption -> "https://s10.gifyu.com/images/Encryption-Degenz-V2-1-min.gif"
|
|> List.map (fun item ->
|
||||||
| ShieldId.Cypher -> "https://s10.gifyu.com/images/Cypher-Smaller.gif"
|
|
||||||
| _ -> shieldGif
|
|
||||||
|
|
||||||
let constructButtons (actionId: string) (buttonInfo : string) (player: PlayerData) itemType ignoreCooldown =
|
|
||||||
player
|
|
||||||
|> Player.getItems itemType
|
|
||||||
|> Array.sortBy (fun i -> i.Power)
|
|
||||||
|> Array.map (fun item ->
|
|
||||||
let action =
|
let action =
|
||||||
player.Events
|
player.Events
|
||||||
|> Array.tryFind (fun i ->
|
|> List.tryFind (fun event ->
|
||||||
match i.Type with
|
match event.Type with
|
||||||
| Hacking h -> h.HackId = item.Id && h.IsInstigator
|
| Hacking h -> h.HackId = item.Id && h.IsInstigator
|
||||||
| Shielding id -> id = item.Id
|
| Shielding id -> id = item.Id
|
||||||
| _ -> false)
|
| _ -> false)
|
||||||
let btnColor = Game.getClassButtonColor item.Class
|
let btnColor = WeaponClass.getClassButtonColor item
|
||||||
match action , ignoreCooldown with
|
match action , ignoreCooldown with
|
||||||
| None , _ | Some _ , true ->
|
| None , _ | Some _ , true ->
|
||||||
DiscordButtonComponent(btnColor, $"{actionId}-{item.Id}-{buttonInfo}-{player.Name}", $"{item.Name}")
|
DiscordButtonComponent(btnColor, $"{actionId}-{item.Id}-{buttonInfo}-{player.Name}", $"{item.Name}")
|
||||||
@ -55,17 +48,18 @@ let constructButtons (actionId: string) (buttonInfo : string) (player: PlayerDat
|
|||||||
|> Seq.cast<DiscordComponent>
|
|> Seq.cast<DiscordComponent>
|
||||||
|
|
||||||
let pickDefense actionId player isTrainer =
|
let pickDefense actionId player isTrainer =
|
||||||
let buttons = constructButtons actionId (string player.DiscordId) player ItemType.Shield isTrainer
|
let shieldItems = player.Inventory |> Inventory.getItemsByType ItemType.Shield
|
||||||
|
let buttons = constructButtons actionId (string player.DiscordId) player shieldItems isTrainer
|
||||||
|
|
||||||
let embed =
|
let embed =
|
||||||
DiscordEmbedBuilder()
|
DiscordEmbedBuilder()
|
||||||
.WithTitle("Shield Defense")
|
.WithTitle("Shield Defense")
|
||||||
.WithDescription("Pick a shield to protect yourself from hacks")
|
.WithDescription("Pick a shield to protect yourself from hacks")
|
||||||
|
|
||||||
for s in Player.getShields player |> Array.sortBy (fun i -> i.Power) do
|
for shield in Inventory.getShields player.Inventory do
|
||||||
let hours = TimeSpan.FromMinutes(int s.Cooldown).TotalHours
|
let hours = TimeSpan.FromMinutes(int shield.Cooldown).TotalHours |> int
|
||||||
let against = Game.getGoodAgainst(s.Class) |> snd
|
let against = WeaponClass.getGoodAgainst(shield.Class) |> snd
|
||||||
embed.AddField(s.Name, $"Active {hours} hours\nDefeats {against}", true) |> ignore
|
embed.AddField(shield.Item.Name, $"Active {hours} hours\nDefeats {against}", true) |> ignore
|
||||||
|
|
||||||
DiscordFollowupMessageBuilder()
|
DiscordFollowupMessageBuilder()
|
||||||
.AddComponents(buttons)
|
.AddComponents(buttons)
|
||||||
@ -73,7 +67,8 @@ let pickDefense actionId player isTrainer =
|
|||||||
.AsEphemeral(true)
|
.AsEphemeral(true)
|
||||||
|
|
||||||
let pickHack actionId attacker defender isTrainer =
|
let pickHack actionId attacker defender isTrainer =
|
||||||
let buttons = constructButtons actionId $"{defender.DiscordId}-{defender.Name}" attacker ItemType.Hack isTrainer
|
let hackItems = attacker.Inventory |> Inventory.getItemsByType ItemType.Hack
|
||||||
|
let buttons = constructButtons actionId $"{defender.DiscordId}-{defender.Name}" attacker hackItems isTrainer
|
||||||
|
|
||||||
let stealMsg = if not isTrainer then $"{defender.Name} has **{defender.Bank} $GBT** we can take from them. " else ""
|
let stealMsg = if not isTrainer then $"{defender.Name} has **{defender.Bank} $GBT** we can take from them. " else ""
|
||||||
let embed =
|
let embed =
|
||||||
@ -82,31 +77,31 @@ let pickHack actionId attacker defender isTrainer =
|
|||||||
.WithDescription($"{stealMsg}Pick the hack you want to use.")
|
.WithDescription($"{stealMsg}Pick the hack you want to use.")
|
||||||
|
|
||||||
if not isTrainer then
|
if not isTrainer then
|
||||||
for h in Player.getHacks attacker |> Array.sortBy (fun i -> i.Power) do
|
for hack in Inventory.getHacks attacker.Inventory do
|
||||||
let amount = if h.Power > int defender.Bank then int defender.Bank else h.Power
|
let amount = if hack.Power > int defender.Bank then int defender.Bank else hack.Power
|
||||||
embed.AddField(h.Name, $"Cooldown {h.Cooldown} mins\nExtract {amount} $GBT", true) |> ignore
|
embed.AddField(hack.Item.Name, $"Cooldown {hack.Cooldown} mins\nExtract {amount} $GBT", true) |> ignore
|
||||||
|
|
||||||
DiscordFollowupMessageBuilder()
|
DiscordFollowupMessageBuilder()
|
||||||
.AddComponents(buttons)
|
.AddComponents(buttons)
|
||||||
.AddEmbeds([ DiscordEmbedBuilder().WithImageUrl(hackGif).Build() ; embed.Build() ])
|
.AddEmbeds([ DiscordEmbedBuilder().WithImageUrl(hackGif).Build() ; embed.Build() ])
|
||||||
.AsEphemeral true
|
.AsEphemeral true
|
||||||
|
|
||||||
let responseSuccessfulHack earnedMoney (targetId : uint64) amountTaken (hack : Item) =
|
let responseSuccessfulHack earnedMoney (targetId : uint64) amountTaken (hack : HackItem) =
|
||||||
let embed =
|
let embed =
|
||||||
DiscordEmbedBuilder()
|
DiscordEmbedBuilder()
|
||||||
.WithImageUrl(getHackGif (enum<HackId>(hack.Id)))
|
.WithImageUrl(getItemGif hack.Item.Id)
|
||||||
.WithTitle("Hack Attack")
|
.WithTitle("Hack Attack")
|
||||||
.WithDescription($"You successfully hacked <@{targetId}> using {hack.Name}"
|
.WithDescription($"You successfully hacked <@{targetId}> using {hack.Item.Name}"
|
||||||
+ (if earnedMoney then $", and took {amountTaken} 💰$GBT from them!" else "!"))
|
+ (if earnedMoney then $", and took {amountTaken} 💰$GBT from them!" else "!"))
|
||||||
|
|
||||||
DiscordFollowupMessageBuilder()
|
DiscordFollowupMessageBuilder()
|
||||||
.AddEmbed(embed.Build())
|
.AddEmbed(embed.Build())
|
||||||
.AsEphemeral(true)
|
.AsEphemeral(true)
|
||||||
|
|
||||||
let responseCreatedShield (shield : Item) =
|
let responseCreatedShield (shield : ShieldItem) =
|
||||||
let embed = DiscordEmbedBuilder().WithImageUrl(getShieldGif (enum<ShieldId>(shield.Id)))
|
let embed = DiscordEmbedBuilder().WithImageUrl(getItemGif shield.Item.Id)
|
||||||
embed.Title <- "Mounted Shield"
|
embed.Title <- "Mounted Shield"
|
||||||
embed.Description <- $"Mounted {shield.Name} shield for {TimeSpan.FromMinutes(int shield.Cooldown).Hours} hours"
|
embed.Description <- $"Mounted {shield.Item.Name} shield for {TimeSpan.FromMinutes(int shield.Cooldown).TotalHours} hours"
|
||||||
|
|
||||||
DiscordFollowupMessageBuilder()
|
DiscordFollowupMessageBuilder()
|
||||||
.AddEmbed(embed)
|
.AddEmbed(embed)
|
||||||
@ -116,66 +111,6 @@ let eventSuccessfulHack (ctx : IDiscordContext) target prize =
|
|||||||
DiscordMessageBuilder()
|
DiscordMessageBuilder()
|
||||||
.WithContent($"{ctx.GetDiscordMember().Username} successfully hacked <@{target.DiscordId}> and took {prize} GoodBoyTokenz")
|
.WithContent($"{ctx.GetDiscordMember().Username} successfully hacked <@{target.DiscordId}> and took {prize} GoodBoyTokenz")
|
||||||
|
|
||||||
let getBuyItemsEmbed (player : PlayerData) (itemType : ItemType) (store : Item array) =
|
|
||||||
let embeds , buttons =
|
|
||||||
store
|
|
||||||
|> Array.filter (fun i -> i.Type = itemType)
|
|
||||||
|> Array.map (fun item ->
|
|
||||||
let embed = DiscordEmbedBuilder()
|
|
||||||
match item.Type with
|
|
||||||
| ItemType.Hack ->
|
|
||||||
embed
|
|
||||||
.AddField($"$GBT Reward |", string item.Power, true)
|
|
||||||
.AddField("Cooldown |", $"{TimeSpan.FromMinutes(int item.Cooldown).Minutes} minutes", true)
|
|
||||||
.WithThumbnail(getHackIcon (enum<HackId>(item.Id)))
|
|
||||||
|> ignore
|
|
||||||
| _ ->
|
|
||||||
embed
|
|
||||||
// .AddField($"Defensive Strength |", string item.Power, true)
|
|
||||||
.AddField($"Strong against |", Game.getGoodAgainst item.Class |> snd |> string, true)
|
|
||||||
.AddField("Active For |", $"{TimeSpan.FromMinutes(int item.Cooldown).Hours} hours", true)
|
|
||||||
.WithThumbnail(getShieldIcon (enum<ShieldId>(item.Id)))
|
|
||||||
|> ignore
|
|
||||||
embed
|
|
||||||
.AddField("Price 💰", (if item.Price = 0<GBT> then "Free" else $"{item.Price} $GBT"), true)
|
|
||||||
.WithColor(Game.getClassEmbedColor item.Class)
|
|
||||||
.WithTitle($"{item.Name}")
|
|
||||||
|> ignore
|
|
||||||
let button =
|
|
||||||
if player.Inventory |> Array.exists (fun i -> i.Id = item.Id)
|
|
||||||
then DiscordButtonComponent(Game.getClassButtonColor item.Class, $"Buy-{item.Id}", $"Own {item.Name}", true)
|
|
||||||
else DiscordButtonComponent(Game.getClassButtonColor item.Class, $"Buy-{item.Id}", $"Buy {item.Name}")
|
|
||||||
embed.Build() , button :> DiscordComponent)
|
|
||||||
|> Array.unzip
|
|
||||||
|
|
||||||
DiscordFollowupMessageBuilder()
|
|
||||||
.AddEmbeds(embeds)
|
|
||||||
.AddComponents(buttons)
|
|
||||||
.AsEphemeral(true)
|
|
||||||
|
|
||||||
let getSellEmbed (itemType : ItemType) (player : PlayerData) =
|
|
||||||
let embeds , buttons =
|
|
||||||
player.Inventory
|
|
||||||
|> Array.filter (fun i -> i.Type = itemType)
|
|
||||||
|> Array.map (fun item ->
|
|
||||||
let embed = DiscordEmbedBuilder()
|
|
||||||
match item.Type with
|
|
||||||
| ItemType.Hack -> embed.WithThumbnail(getHackIcon (enum<HackId>(item.Id))) |> ignore
|
|
||||||
| _ -> embed.WithThumbnail(getShieldIcon (enum<ShieldId>(item.Id))) |> ignore
|
|
||||||
embed
|
|
||||||
.AddField("Sell For 💰", $"{item.Price} $GBT", true)
|
|
||||||
.WithTitle($"{item.Name}")
|
|
||||||
.WithColor(Game.getClassEmbedColor item.Class)
|
|
||||||
|> ignore
|
|
||||||
let button = DiscordButtonComponent(Game.getClassButtonColor item.Class, $"Sell-{item.Id}", $"Sell {item.Name}")
|
|
||||||
embed.Build() , button :> DiscordComponent)
|
|
||||||
|> Array.unzip
|
|
||||||
|
|
||||||
DiscordFollowupMessageBuilder()
|
|
||||||
.AddEmbeds(embeds)
|
|
||||||
.AddComponents(buttons)
|
|
||||||
.AsEphemeral(true)
|
|
||||||
|
|
||||||
let getArsenalEmbed (player : PlayerData) =
|
let getArsenalEmbed (player : PlayerData) =
|
||||||
DiscordFollowupMessageBuilder()
|
DiscordFollowupMessageBuilder()
|
||||||
.AsEphemeral(true)
|
.AsEphemeral(true)
|
||||||
|
125
Bot/Game.fs
125
Bot/Game.fs
@ -1,125 +0,0 @@
|
|||||||
namespace Degenz
|
|
||||||
|
|
||||||
open System.Threading.Tasks
|
|
||||||
open DSharpPlus
|
|
||||||
open DSharpPlus.Entities
|
|
||||||
open Degenz.DbService
|
|
||||||
open Degenz.Messaging
|
|
||||||
|
|
||||||
module Game =
|
|
||||||
let SameTargetAttackCooldown = System.TimeSpan.FromHours(1)
|
|
||||||
|
|
||||||
let getClassButtonColor = function
|
|
||||||
| 0 -> ButtonStyle.Danger
|
|
||||||
| 1 -> ButtonStyle.Primary
|
|
||||||
| _ -> ButtonStyle.Success
|
|
||||||
|
|
||||||
let getClassEmbedColor = function
|
|
||||||
| 0 -> DiscordColor.Red
|
|
||||||
| 1 -> DiscordColor.Blurple
|
|
||||||
| _ -> DiscordColor.Green
|
|
||||||
|
|
||||||
let getGoodAgainst = function
|
|
||||||
| 0 -> ( ShieldId.Firewall , HackId.Virus )
|
|
||||||
| 1 -> ( ShieldId.Encryption , HackId.RemoteAccess )
|
|
||||||
| _ -> ( ShieldId.Cypher , HackId.Worm )
|
|
||||||
|
|
||||||
let executePlayerAction (ctx : IDiscordContext) (dispatch : PlayerData -> Async<unit>) =
|
|
||||||
async {
|
|
||||||
let builder = DiscordInteractionResponseBuilder().AsEphemeral(true)
|
|
||||||
do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
|
|
||||||
let! playerResult = tryFindPlayer GuildEnvironment.pgDb (ctx.GetDiscordMember().Id)
|
|
||||||
match playerResult with
|
|
||||||
| Some player -> do! dispatch player
|
|
||||||
| None -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
|
|
||||||
} |> Async.StartAsTask :> Task
|
|
||||||
|
|
||||||
let executePlayerActionWithTarget (targetPlayer : DiscordUser) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async<unit>) =
|
|
||||||
async {
|
|
||||||
let builder = DiscordInteractionResponseBuilder()
|
|
||||||
builder.IsEphemeral <- true
|
|
||||||
builder.Content <- "Content"
|
|
||||||
do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
|
|
||||||
let! players =
|
|
||||||
[ tryFindPlayer GuildEnvironment.pgDb (ctx.GetDiscordMember().Id)
|
|
||||||
tryFindPlayer GuildEnvironment.pgDb targetPlayer.Id ]
|
|
||||||
|> Async.Parallel
|
|
||||||
match players.[0] , players.[1] with
|
|
||||||
| Some player , Some target -> do! dispatch player target
|
|
||||||
| None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
|
|
||||||
| _ , None ->
|
|
||||||
if targetPlayer.IsBot
|
|
||||||
then do! Messaging.sendFollowUpMessage ctx $"{targetPlayer.Username} is a bot, pick a real human to hack"
|
|
||||||
else do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command"
|
|
||||||
} |> Async.StartAsTask :> Task
|
|
||||||
|
|
||||||
let executePlayerActionWithTargetId defer (targetId : uint64) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async<unit>) =
|
|
||||||
async {
|
|
||||||
let builder = DiscordInteractionResponseBuilder()
|
|
||||||
builder.IsEphemeral <- true
|
|
||||||
builder.Content <- "Content"
|
|
||||||
if defer then
|
|
||||||
do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
|
|
||||||
let! players =
|
|
||||||
[ tryFindPlayer GuildEnvironment.pgDb (ctx.GetDiscordMember().Id)
|
|
||||||
tryFindPlayer GuildEnvironment.pgDb targetId ]
|
|
||||||
|> Async.Parallel
|
|
||||||
match players.[0] , players.[1] with
|
|
||||||
| Some player , Some target -> do! dispatch player target
|
|
||||||
| None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
|
|
||||||
| _ , None -> do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command"
|
|
||||||
} |> Async.StartAsTask :> Task
|
|
||||||
|
|
||||||
module Player =
|
|
||||||
let getItems itemType (player : PlayerData) = player.Inventory |> Array.filter (fun i -> i.Type = itemType)
|
|
||||||
let getHacks (player : PlayerData) = getItems ItemType.Hack player
|
|
||||||
let getShields (player : PlayerData) = getItems ItemType.Shield player
|
|
||||||
let getHackEvents player =
|
|
||||||
player.Events
|
|
||||||
|> Array.filter (fun act -> match act.Type with PlayerEventType.Hacking h -> h.IsInstigator | _ -> false)
|
|
||||||
let getShieldEvents player =
|
|
||||||
player.Events
|
|
||||||
|> Array.filter (fun act -> match act.Type with PlayerEventType.Shielding _ -> true | _ -> false)
|
|
||||||
|
|
||||||
let removeExpiredActions player =
|
|
||||||
let actions =
|
|
||||||
player.Events
|
|
||||||
|> Array.filter (fun (act : PlayerEvent) ->
|
|
||||||
let cooldown = System.TimeSpan.FromMinutes(int act.Cooldown)
|
|
||||||
System.DateTime.UtcNow - act.Timestamp < cooldown)
|
|
||||||
{ player with Events = actions }
|
|
||||||
|
|
||||||
let modifyBank (player : PlayerData) amount = { player with Bank = max (player.Bank + amount) 0<GBT> }
|
|
||||||
|
|
||||||
module Arsenal =
|
|
||||||
let battleItemFormat (items : Item array) =
|
|
||||||
match items with
|
|
||||||
| [||] -> "None"
|
|
||||||
| _ -> items |> Array.toList |> List.map (fun i -> i.Name) |> String.concat ", "
|
|
||||||
|
|
||||||
let actionFormat (actions : PlayerEvent array) =
|
|
||||||
match actions with
|
|
||||||
| [||] -> "None"
|
|
||||||
| acts ->
|
|
||||||
acts
|
|
||||||
|> Array.map (fun act ->
|
|
||||||
match act.Type with
|
|
||||||
| Hacking h ->
|
|
||||||
let item = Armory.getItem h.HackId
|
|
||||||
let cooldown = Messaging.getTimeText false Game.SameTargetAttackCooldown act.Timestamp
|
|
||||||
$"Hacked {h.Adversary.Name} with {item.Name} {cooldown} ago"
|
|
||||||
| Shielding id ->
|
|
||||||
let item = Armory.getItem id
|
|
||||||
let cooldown = Messaging.getTimeText true (System.TimeSpan.FromMinutes(int act.Cooldown)) act.Timestamp
|
|
||||||
$"{item.Name} Shield active for {cooldown}"
|
|
||||||
| _ -> "")
|
|
||||||
|> Array.filter (System.String.IsNullOrWhiteSpace >> not)
|
|
||||||
|> String.concat "\n"
|
|
||||||
|
|
||||||
let statusFormat p =
|
|
||||||
let hacks = Player.getHackEvents p
|
|
||||||
$"**Hacks:** {Player.getHacks p |> battleItemFormat}\n
|
|
||||||
**Shields:** {Player.getShields p |> battleItemFormat}\n
|
|
||||||
**Hack Attacks:**\n{ hacks |> Array.take (min hacks.Length 10) |> actionFormat}\n
|
|
||||||
**Active Shields:**\n{Player.getShieldEvents p |> actionFormat}"
|
|
||||||
|
|
135
Bot/GameHelpers.fs
Normal file
135
Bot/GameHelpers.fs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
namespace Degenz
|
||||||
|
|
||||||
|
open System
|
||||||
|
open DSharpPlus
|
||||||
|
open DSharpPlus.Entities
|
||||||
|
open Degenz
|
||||||
|
open Newtonsoft.Json
|
||||||
|
|
||||||
|
module Armory =
|
||||||
|
// let weapons : ItemDetails list= []
|
||||||
|
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)
|
||||||
|
|
||||||
|
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 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 getFoods inventory =
|
||||||
|
inventory |> List.choose (fun item -> match item with | Food f -> Some f | _ -> None)
|
||||||
|
let getAccessories inventory =
|
||||||
|
inventory |> List.choose (fun item -> match item with | Accessory a -> Some a | _ -> None)
|
||||||
|
|
||||||
|
module WeaponClass =
|
||||||
|
let SameTargetAttackCooldown = TimeSpan.FromHours(2)
|
||||||
|
|
||||||
|
let getClassButtonColor item =
|
||||||
|
match ItemDetails.getClass item with
|
||||||
|
| 0 -> ButtonStyle.Danger
|
||||||
|
| 1 -> ButtonStyle.Primary
|
||||||
|
| 2 -> ButtonStyle.Success
|
||||||
|
| _ -> ButtonStyle.Primary
|
||||||
|
|
||||||
|
let getClassEmbedColor item =
|
||||||
|
match ItemDetails.getClass item with
|
||||||
|
| 0 -> DiscordColor.Red
|
||||||
|
| 1 -> DiscordColor.Blurple
|
||||||
|
| 2 -> DiscordColor.Green
|
||||||
|
| _ -> DiscordColor.Blurple
|
||||||
|
|
||||||
|
let getGoodAgainst = function
|
||||||
|
| 0 -> ( ItemId.Firewall , ItemId.Virus )
|
||||||
|
| 1 -> ( ItemId.Encryption , ItemId.RemoteAccess )
|
||||||
|
| _ -> ( ItemId.Cypher , ItemId.Worm )
|
||||||
|
|
||||||
|
module Player =
|
||||||
|
let getHackEvents player =
|
||||||
|
player.Events
|
||||||
|
|> List.filter (fun act -> match act.Type with PlayerEventType.Hacking h -> h.IsInstigator | _ -> false)
|
||||||
|
let getShieldEvents player =
|
||||||
|
player.Events
|
||||||
|
|> List.filter (fun act -> match act.Type with PlayerEventType.Shielding _ -> true | _ -> false)
|
||||||
|
|
||||||
|
let removeExpiredActions player =
|
||||||
|
let actions =
|
||||||
|
player.Events
|
||||||
|
|> List.filter (fun (act : PlayerEvent) ->
|
||||||
|
let cooldown = TimeSpan.FromMinutes(int act.Cooldown)
|
||||||
|
DateTime.UtcNow - act.Timestamp < cooldown)
|
||||||
|
{ player with Events = actions }
|
||||||
|
|
||||||
|
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 }
|
||||||
|
|
||||||
|
let stats = [ Strength ; Focus ; Luck ; Charisma ]
|
||||||
|
|
||||||
|
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)
|
||||||
|
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) =
|
||||||
|
match items with
|
||||||
|
| [] -> "None"
|
||||||
|
| _ -> items |> List.map (fun item -> item.Name) |> String.concat ", "
|
||||||
|
|
||||||
|
let actionFormat (actions : PlayerEvent List) =
|
||||||
|
match actions with
|
||||||
|
| [] -> "None"
|
||||||
|
| acts ->
|
||||||
|
acts
|
||||||
|
|> List.map (fun event ->
|
||||||
|
match event.Type with
|
||||||
|
| Hacking h ->
|
||||||
|
let item = Armory.weapons |> Inventory.findHackById h.HackId
|
||||||
|
let cooldown = Messaging.getTimeText false WeaponClass.SameTargetAttackCooldown event.Timestamp
|
||||||
|
$"Hacked {h.Adversary.Name} with {item.Item.Name} {cooldown} ago"
|
||||||
|
| Shielding id ->
|
||||||
|
let item = Armory.weapons |> Inventory.findShieldById id
|
||||||
|
let cooldown = Messaging.getTimeText true (TimeSpan.FromMinutes(int event.Cooldown)) event.Timestamp
|
||||||
|
$"{item.Item.Name} Shield active for {cooldown}"
|
||||||
|
| _ -> "")
|
||||||
|
|> List.filter (String.IsNullOrWhiteSpace >> not)
|
||||||
|
|> String.concat "\n"
|
||||||
|
|
||||||
|
let statusFormat p =
|
||||||
|
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}"
|
180
Bot/GameTypes.fs
Normal file
180
Bot/GameTypes.fs
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
[<Microsoft.FSharp.Core.AutoOpen>]
|
||||||
|
module Degenz.Types
|
||||||
|
|
||||||
|
open System
|
||||||
|
open Degenz
|
||||||
|
|
||||||
|
[<Measure>]
|
||||||
|
type mins
|
||||||
|
|
||||||
|
[<Measure>]
|
||||||
|
type GBT
|
||||||
|
|
||||||
|
type Range = { Min : int ; Max : int }
|
||||||
|
|
||||||
|
module Range =
|
||||||
|
let normalized = { Min = 0 ; Max = 100 }
|
||||||
|
let create min max = { Min = min ; Max = max }
|
||||||
|
let constrain value range = if value < range.Min then range.Min elif value > range.Max then range.Max else value
|
||||||
|
|
||||||
|
type ItemId =
|
||||||
|
| Virus = 0
|
||||||
|
| RemoteAccess = 1
|
||||||
|
| Worm = 2
|
||||||
|
| Firewall = 6
|
||||||
|
| Encryption = 7
|
||||||
|
| Cypher = 8
|
||||||
|
| ProteinPowder = 12
|
||||||
|
| ToroLoco = 13
|
||||||
|
| Cigs = 14
|
||||||
|
| MoonPie = 15
|
||||||
|
|
||||||
|
type StatId =
|
||||||
|
| Strength = 0
|
||||||
|
| Focus = 1
|
||||||
|
| Charisma = 2
|
||||||
|
| Luck = 3
|
||||||
|
|
||||||
|
type StatConfig = {
|
||||||
|
Id : StatId
|
||||||
|
BaseDecayRate : float
|
||||||
|
BaseRange : Range
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlayerStat = {
|
||||||
|
Id : StatId
|
||||||
|
Amount : int
|
||||||
|
ModRange : Range
|
||||||
|
LastRead : DateTime
|
||||||
|
}
|
||||||
|
with static member empty = { Id = StatId.Strength ; Amount = 0 ; ModRange = Range.normalized ; LastRead = DateTime.UtcNow}
|
||||||
|
|
||||||
|
type Stats = {
|
||||||
|
Strength : PlayerStat
|
||||||
|
Focus : PlayerStat
|
||||||
|
Luck : PlayerStat
|
||||||
|
Charisma : PlayerStat
|
||||||
|
}
|
||||||
|
with static member empty = { Strength = PlayerStat.empty ; Focus = PlayerStat.empty ; Luck = PlayerStat.empty ; Charisma = PlayerStat.empty }
|
||||||
|
|
||||||
|
type HackResult =
|
||||||
|
| Strong
|
||||||
|
| Weak
|
||||||
|
|
||||||
|
type DiscordPlayer = { Id: uint64; Name: string }
|
||||||
|
with static member empty = { Id = 0uL ; Name = "None" }
|
||||||
|
|
||||||
|
type HackEvent = {
|
||||||
|
IsInstigator : bool
|
||||||
|
Adversary : DiscordPlayer
|
||||||
|
Success : bool
|
||||||
|
HackId : int
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlayerEventType =
|
||||||
|
| Hacking of HackEvent
|
||||||
|
| Shielding of shieldId : int
|
||||||
|
| Stealing of instigator : bool * adversary : DiscordPlayer
|
||||||
|
| Imprison
|
||||||
|
|
||||||
|
type PlayerEvent =
|
||||||
|
{ Type : PlayerEventType
|
||||||
|
Cooldown : int<mins>
|
||||||
|
Timestamp : DateTime }
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
type ItemType =
|
||||||
|
| Hack
|
||||||
|
| Shield
|
||||||
|
| Food
|
||||||
|
| Accessory
|
||||||
|
|
||||||
|
type Item = {
|
||||||
|
Id : int
|
||||||
|
Name : string
|
||||||
|
Price : int<GBT>
|
||||||
|
}
|
||||||
|
|
||||||
|
type HackItem = {
|
||||||
|
Power : int
|
||||||
|
Class : int
|
||||||
|
Cooldown : int<mins>
|
||||||
|
Item : Item
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShieldItem = {
|
||||||
|
Class : int
|
||||||
|
Cooldown : int<mins>
|
||||||
|
Item : Item
|
||||||
|
}
|
||||||
|
|
||||||
|
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 PlayerData = {
|
||||||
|
DiscordId : uint64
|
||||||
|
Name : string
|
||||||
|
Inventory : Inventory
|
||||||
|
Events : PlayerEvent list
|
||||||
|
Stats : Stats
|
||||||
|
Bank : int<GBT>
|
||||||
|
}
|
||||||
|
// Achievements : string array
|
||||||
|
// XP : int
|
||||||
|
with member this.toDiscordPlayer = { Id = this.DiscordId ; Name = this.Name }
|
||||||
|
static member empty =
|
||||||
|
{ DiscordId = 0uL
|
||||||
|
Name = "None"
|
||||||
|
Inventory = []
|
||||||
|
Events = []
|
||||||
|
Stats = Stats.empty
|
||||||
|
// Achievements = [||]
|
||||||
|
// XP = 0
|
||||||
|
Bank = 0<GBT> }
|
@ -1,6 +1,7 @@
|
|||||||
module Degenz.HackerBattle
|
module Degenz.HackerBattle
|
||||||
|
|
||||||
open System
|
open System
|
||||||
|
open System.Text
|
||||||
open System.Threading.Tasks
|
open System.Threading.Tasks
|
||||||
open DSharpPlus
|
open DSharpPlus
|
||||||
open DSharpPlus.Entities
|
open DSharpPlus.Entities
|
||||||
@ -8,6 +9,7 @@ open DSharpPlus.EventArgs
|
|||||||
open DSharpPlus.SlashCommands
|
open DSharpPlus.SlashCommands
|
||||||
open Degenz
|
open Degenz
|
||||||
open Degenz.Messaging
|
open Degenz.Messaging
|
||||||
|
open Degenz.PlayerInteractions
|
||||||
|
|
||||||
let checkPlayerIsAttackingThemselves defender attacker =
|
let checkPlayerIsAttackingThemselves defender attacker =
|
||||||
match attacker.DiscordId = defender.DiscordId with
|
match attacker.DiscordId = defender.DiscordId with
|
||||||
@ -18,7 +20,7 @@ let checkAlreadyHackedTarget defender attacker =
|
|||||||
defender
|
defender
|
||||||
|> Player.removeExpiredActions
|
|> Player.removeExpiredActions
|
||||||
|> fun d -> d.Events
|
|> fun d -> d.Events
|
||||||
|> Array.tryFind (fun event ->
|
|> List.tryFind (fun event ->
|
||||||
match event.Type with
|
match event.Type with
|
||||||
| Hacking h -> h.Adversary.Id = attacker.DiscordId && h.IsInstigator = false
|
| Hacking h -> h.Adversary.Id = attacker.DiscordId && h.IsInstigator = false
|
||||||
| _ -> false)
|
| _ -> false)
|
||||||
@ -31,7 +33,7 @@ let checkAlreadyHackedTarget defender attacker =
|
|||||||
|
|
||||||
let checkWeaponHasCooldown (weapon : Item) attacker =
|
let checkWeaponHasCooldown (weapon : Item) attacker =
|
||||||
attacker.Events
|
attacker.Events
|
||||||
|> Array.tryFind (fun a ->
|
|> List.tryFind (fun a ->
|
||||||
match a.Type with
|
match a.Type with
|
||||||
| Hacking h -> h.HackId = weapon.Id && h.IsInstigator
|
| Hacking h -> h.HackId = weapon.Id && h.IsInstigator
|
||||||
| Shielding id -> id = weapon.Id
|
| Shielding id -> id = weapon.Id
|
||||||
@ -43,68 +45,63 @@ let checkWeaponHasCooldown (weapon : Item) attacker =
|
|||||||
| None -> Ok attacker
|
| None -> Ok attacker
|
||||||
|
|
||||||
let checkHasEmptyHacks attacker =
|
let checkHasEmptyHacks attacker =
|
||||||
match Player.getHacks attacker with
|
match Inventory.getHacks attacker.Inventory with
|
||||||
| [||] -> Error $"You currently do not have any Hacks to take 💰$GBT from others. Please go to the <#{GuildEnvironment.channelArmory}> and purchase one."
|
| [] -> Error $"You currently do not have any Hacks to take 💰$GBT from others. Please go to the <#{GuildEnvironment.channelArmory}> and purchase one."
|
||||||
| _ -> Ok attacker
|
| _ -> Ok attacker
|
||||||
|
|
||||||
let checkPlayerOwnsWeapon (item : Item) player =
|
let checkPlayerOwnsWeapon (item : Item) player =
|
||||||
match player.Inventory |> Array.exists (fun i -> i.Id = item.Id) with
|
match player.Inventory |> List.exists (fun i -> i.Id = item.Id) with
|
||||||
| true -> Ok player
|
| true -> Ok player
|
||||||
| false -> Error $"You sold your weapon already, you cheeky bastard..."
|
| false -> Error $"You sold your weapon already, you cheeky bastard..."
|
||||||
|
|
||||||
let checkPlayerHasShieldSlotsAvailable (shield : Item) player =
|
let checkPlayerHasShieldSlotsAvailable player =
|
||||||
let updatedPlayer = player |> Player.removeExpiredActions
|
let updatedPlayer = player |> Player.removeExpiredActions
|
||||||
let defenses = Player.getShieldEvents updatedPlayer
|
let defenses = Player.getShieldEvents updatedPlayer
|
||||||
match defenses |> Array.length >= 3 with
|
match defenses |> List.length >= 3 with
|
||||||
| true ->
|
| true ->
|
||||||
let timestamp = defenses |> Array.rev |> Array.head |> fun a -> a.Timestamp // This should be the next expiring timestamp
|
let event = defenses |> List.rev |> List.head // This should be the next expiring timestamp
|
||||||
let cooldown = getTimeText true (TimeSpan.FromMinutes(int shield.Cooldown)) timestamp
|
let cooldown = getTimeText true (TimeSpan.FromMinutes(int event.Cooldown)) event.Timestamp
|
||||||
Error $"You are only allowed three shields at a time. Wait {cooldown} to add another shield"
|
Error $"You are only allowed three shields at a time. Wait {cooldown} to add another shield"
|
||||||
| false -> Ok updatedPlayer
|
| false -> Ok updatedPlayer
|
||||||
|
|
||||||
let checkTargetHasFunds target player =
|
let checkTargetHasFunds target player =
|
||||||
match target.Bank = 0<GBT> with
|
match target.Bank <= 0<GBT> with
|
||||||
| true -> Error $"Looks like the poor bastard has no $GBT... pick a different victim."
|
| true -> Error $"Looks like the poor bastard has no $GBT... pick a different victim."
|
||||||
| false -> Ok player
|
| false -> Ok player
|
||||||
|
|
||||||
let calculateDamage (hack : Item) (shield : Item) =
|
let runHackerBattle defender (hack : HackItem) =
|
||||||
if hack.Class = shield.Class
|
|
||||||
then Weak
|
|
||||||
else Strong
|
|
||||||
|
|
||||||
let runHackerBattle defender hack =
|
|
||||||
defender
|
defender
|
||||||
|> Player.removeExpiredActions
|
|> Player.removeExpiredActions
|
||||||
|> fun p -> p.Events
|
|> fun p -> p.Events
|
||||||
|> Array.choose (fun event ->
|
|> List.choose (fun event ->
|
||||||
match event.Type with
|
match event.Type with
|
||||||
| Shielding id -> Armory.battleItems |> Array.find (fun w -> w.Id = id) |> Some
|
| Shielding id -> defender.Inventory |> Inventory.getShields |> List.find (fun item -> item.Item.Id = id) |> Some
|
||||||
| _ -> None)
|
| _ -> None)
|
||||||
|> Array.map (calculateDamage hack)
|
|> List.map (fun shield -> if hack.Class = shield.Class then Weak else Strong)
|
||||||
|> Array.contains Weak
|
|> List.contains Weak
|
||||||
|
|
||||||
let updateCombatants successfulHack (attacker : PlayerData) (defender : PlayerData) (hack : Item) prize =
|
let updateCombatants successfulHack (attacker : PlayerData) (defender : PlayerData) (hack : HackItem) prize =
|
||||||
let updatePlayer amount attack p =
|
let updatePlayer amount attack p =
|
||||||
{ p with Events = Array.append [| attack |] p.Events ; Bank = max (p.Bank + amount) 0<GBT> }
|
{ p with Events = attack::p.Events ; Bank = max (p.Bank + amount) 0<GBT> }
|
||||||
let event isDefenderEvent =
|
let event isDefenderEvent =
|
||||||
let hackEvent = {
|
let hackEvent = {
|
||||||
HackId = hack.Id
|
HackId = hack.Item.Id
|
||||||
Adversary = if isDefenderEvent then attacker.basicPlayer else defender.basicPlayer
|
Adversary = if isDefenderEvent then attacker.toDiscordPlayer else defender.toDiscordPlayer
|
||||||
IsInstigator = not isDefenderEvent
|
IsInstigator = not isDefenderEvent
|
||||||
Success = successfulHack
|
Success = successfulHack
|
||||||
}
|
}
|
||||||
{ Type = Hacking hackEvent
|
{ Type = Hacking hackEvent
|
||||||
Timestamp = DateTime.UtcNow
|
Timestamp = DateTime.UtcNow
|
||||||
Cooldown = if isDefenderEvent then int Game.SameTargetAttackCooldown.TotalMinutes * 1<mins> else hack.Cooldown }
|
Cooldown = if isDefenderEvent then int WeaponClass.SameTargetAttackCooldown.TotalMinutes * 1<mins> else hack.Cooldown }
|
||||||
|
|
||||||
[ DbService.updatePlayer GuildEnvironment.pgDb <| updatePlayer prize (event false) attacker
|
[ DbService.updatePlayer <| updatePlayer prize (event false) attacker
|
||||||
DbService.updatePlayer GuildEnvironment.pgDb <| updatePlayer -prize (event true) defender
|
DbService.updatePlayer <| updatePlayer -prize (event true) defender
|
||||||
DbService.addPlayerEvent GuildEnvironment.pgDb attacker.DiscordId (event false)
|
DbService.addPlayerEvent attacker.DiscordId (event false)
|
||||||
DbService.addPlayerEvent GuildEnvironment.pgDb defender.DiscordId (event true) ]
|
DbService.addPlayerEvent defender.DiscordId (event true) ]
|
||||||
|> Async.Parallel
|
|> Async.Parallel
|
||||||
|> Async.Ignore
|
|> Async.Ignore
|
||||||
|
|
||||||
let successfulHack (ctx : IDiscordContext) attacker defender hack =
|
let successfulHack (ctx : IDiscordContext) attacker defender (hack : HackItem) =
|
||||||
async {
|
async {
|
||||||
let prizeAmount = if hack.Power < int defender.Bank then hack.Power else int defender.Bank
|
let prizeAmount = if hack.Power < int defender.Bank then hack.Power else int defender.Bank
|
||||||
do! updateCombatants true attacker defender hack (prizeAmount * 1<GBT>)
|
do! updateCombatants true attacker defender hack (prizeAmount * 1<GBT>)
|
||||||
@ -119,7 +116,7 @@ let successfulHack (ctx : IDiscordContext) attacker defender hack =
|
|||||||
|> Async.Ignore
|
|> Async.Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
let failedHack (ctx : IDiscordContext) attacker defender hack =
|
let failedHack (ctx : IDiscordContext) attacker defender (hack : HackItem) =
|
||||||
async {
|
async {
|
||||||
let lostAmount = if hack.Power < int attacker.Bank then hack.Power else int attacker.Bank
|
let lostAmount = if hack.Power < int attacker.Bank then hack.Power else int attacker.Bank
|
||||||
let msg = $"Hack failed! {defender.Name} was able to mount a successful defense! You lost {lostAmount} $GBT!"
|
let msg = $"Hack failed! {defender.Name} was able to mount a successful defense! You lost {lostAmount} $GBT!"
|
||||||
@ -136,7 +133,7 @@ let failedHack (ctx : IDiscordContext) attacker defender hack =
|
|||||||
}
|
}
|
||||||
|
|
||||||
let hack (target : DiscordUser) (ctx : IDiscordContext) =
|
let hack (target : DiscordUser) (ctx : IDiscordContext) =
|
||||||
Game.executePlayerActionWithTarget target ctx (fun attacker defender -> async {
|
executePlayerActionWithTarget target ctx (fun attacker defender -> async {
|
||||||
do! attacker
|
do! attacker
|
||||||
|> Player.removeExpiredActions
|
|> Player.removeExpiredActions
|
||||||
|> checkAlreadyHackedTarget defender
|
|> checkAlreadyHackedTarget defender
|
||||||
@ -151,20 +148,21 @@ let hack (target : DiscordUser) (ctx : IDiscordContext) =
|
|||||||
})
|
})
|
||||||
|
|
||||||
let handleAttack (ctx : IDiscordContext) =
|
let handleAttack (ctx : IDiscordContext) =
|
||||||
Game.executePlayerAction ctx (fun attacker -> async {
|
executePlayerAction ctx (fun attacker -> async {
|
||||||
let tokens = ctx.GetInteractionId().Split("-")
|
let tokens = ctx.GetInteractionId().Split("-")
|
||||||
let hackId = int tokens.[1]
|
let hackId = int tokens.[1]
|
||||||
let hack = Armory.getItem hackId
|
let hack = Armory.weapons |> Inventory.findHackById hackId
|
||||||
let resultId , targetId = UInt64.TryParse tokens.[2]
|
let resultId , targetId = UInt64.TryParse tokens.[2]
|
||||||
let! resultTarget = DbService.tryFindPlayer GuildEnvironment.pgDb targetId
|
let! resultTarget = DbService.tryFindPlayer targetId
|
||||||
|
|
||||||
match resultTarget , true , resultId with
|
match resultTarget , true , resultId with
|
||||||
| Some defender , true , true ->
|
| Some defender , true , true ->
|
||||||
do! attacker
|
do! attacker
|
||||||
|> Player.removeExpiredActions
|
|> Player.removeExpiredActions
|
||||||
|> checkAlreadyHackedTarget defender
|
|> checkAlreadyHackedTarget defender
|
||||||
>>= checkPlayerOwnsWeapon hack
|
>>= checkPlayerOwnsWeapon hack.Item
|
||||||
>>= checkWeaponHasCooldown hack
|
>>= checkTargetHasFunds defender
|
||||||
|
>>= checkWeaponHasCooldown hack.Item
|
||||||
|> function
|
|> function
|
||||||
| Ok atkr ->
|
| Ok atkr ->
|
||||||
runHackerBattle defender hack
|
runHackerBattle defender hack
|
||||||
@ -176,8 +174,8 @@ let handleAttack (ctx : IDiscordContext) =
|
|||||||
})
|
})
|
||||||
|
|
||||||
let defend (ctx : IDiscordContext) =
|
let defend (ctx : IDiscordContext) =
|
||||||
Game.executePlayerAction ctx (fun player -> async {
|
executePlayerAction ctx (fun player -> async {
|
||||||
if Player.getShields player |> Array.length > 0 then
|
if player.Inventory |> Inventory.getShields |> List.length > 0 then
|
||||||
let p = Player.removeExpiredActions player
|
let p = Player.removeExpiredActions player
|
||||||
let embed = Embeds.pickDefense "Defend" p false
|
let embed = Embeds.pickDefense "Defend" p false
|
||||||
do! ctx.FollowUp embed |> Async.AwaitTask
|
do! ctx.FollowUp embed |> Async.AwaitTask
|
||||||
@ -187,15 +185,15 @@ let defend (ctx : IDiscordContext) =
|
|||||||
})
|
})
|
||||||
|
|
||||||
let handleDefense (ctx : IDiscordContext) =
|
let handleDefense (ctx : IDiscordContext) =
|
||||||
Game.executePlayerAction ctx (fun player -> async {
|
executePlayerAction ctx (fun player -> async {
|
||||||
let tokens = ctx.GetInteractionId().Split("-")
|
let tokens = ctx.GetInteractionId().Split("-")
|
||||||
let shieldId = int tokens.[1]
|
let shieldId = int tokens.[1]
|
||||||
let shield = Armory.getItem shieldId
|
let shield = Armory.weapons |> Inventory.findShieldById shieldId
|
||||||
|
|
||||||
do! player
|
do! player
|
||||||
|> checkPlayerOwnsWeapon shield
|
|> checkPlayerOwnsWeapon shield.Item
|
||||||
>>= checkPlayerHasShieldSlotsAvailable shield
|
>>= checkPlayerHasShieldSlotsAvailable
|
||||||
>>= checkWeaponHasCooldown shield
|
>>= checkWeaponHasCooldown shield.Item
|
||||||
|> handleResultWithResponse ctx (fun p -> async {
|
|> handleResultWithResponse ctx (fun p -> async {
|
||||||
let embed = Embeds.responseCreatedShield shield
|
let embed = Embeds.responseCreatedShield shield
|
||||||
do! ctx.FollowUp embed |> Async.AwaitTask
|
do! ctx.FollowUp embed |> Async.AwaitTask
|
||||||
@ -204,9 +202,9 @@ let handleDefense (ctx : IDiscordContext) =
|
|||||||
Cooldown = shield.Cooldown
|
Cooldown = shield.Cooldown
|
||||||
Timestamp = DateTime.UtcNow
|
Timestamp = DateTime.UtcNow
|
||||||
}
|
}
|
||||||
do! DbService.updatePlayer GuildEnvironment.pgDb p
|
do! DbService.updatePlayer p
|
||||||
|> Async.Ignore
|
|> Async.Ignore
|
||||||
do! DbService.addPlayerEvent GuildEnvironment.pgDb p.DiscordId defense
|
do! DbService.addPlayerEvent p.DiscordId defense
|
||||||
|> Async.Ignore
|
|> Async.Ignore
|
||||||
let builder = DiscordMessageBuilder()
|
let builder = DiscordMessageBuilder()
|
||||||
builder.WithContent($"{ctx.GetDiscordMember().Username} has protected their system!") |> ignore
|
builder.WithContent($"{ctx.GetDiscordMember().Username} has protected their system!") |> ignore
|
||||||
@ -218,7 +216,7 @@ let handleDefense (ctx : IDiscordContext) =
|
|||||||
})
|
})
|
||||||
|
|
||||||
let arsenal (ctx : IDiscordContext) =
|
let arsenal (ctx : IDiscordContext) =
|
||||||
Game.executePlayerAction ctx (fun player -> async {
|
executePlayerAction ctx (fun player -> async {
|
||||||
let updatedPlayer = Player.removeExpiredActions player
|
let updatedPlayer = Player.removeExpiredActions player
|
||||||
let builder = DiscordFollowupMessageBuilder()
|
let builder = DiscordFollowupMessageBuilder()
|
||||||
let embed = DiscordEmbedBuilder()
|
let embed = DiscordEmbedBuilder()
|
||||||
@ -226,7 +224,7 @@ let arsenal (ctx : IDiscordContext) =
|
|||||||
builder.AddEmbed(embed) |> ignore
|
builder.AddEmbed(embed) |> ignore
|
||||||
builder.IsEphemeral <- true
|
builder.IsEphemeral <- true
|
||||||
do! ctx.FollowUp(builder) |> Async.AwaitTask
|
do! ctx.FollowUp(builder) |> Async.AwaitTask
|
||||||
do! DbService.updatePlayer GuildEnvironment.pgDb updatedPlayer
|
do! DbService.updatePlayer updatedPlayer
|
||||||
|> Async.Ignore
|
|> Async.Ignore
|
||||||
})
|
})
|
||||||
|
|
@ -1,6 +1,5 @@
|
|||||||
module Degenz.RockPaperScissors
|
module Degenz.RockPaperScissors
|
||||||
|
|
||||||
open System
|
|
||||||
open System.Threading.Tasks
|
open System.Threading.Tasks
|
||||||
open DSharpPlus
|
open DSharpPlus
|
||||||
open DSharpPlus.Entities
|
open DSharpPlus.Entities
|
||||||
@ -85,7 +84,7 @@ let matchResultsEmbed winner move1 move2 player1 player2 =
|
|||||||
[ firstEmbed ; secondEmbed ; thirdEmbed ]
|
[ firstEmbed ; secondEmbed ; thirdEmbed ]
|
||||||
|
|
||||||
let playRPS target ctx =
|
let playRPS target ctx =
|
||||||
Game.executePlayerActionWithTarget target ctx (fun _ defender -> async {
|
PlayerInteractions.executePlayerActionWithTarget target ctx (fun _ defender -> async {
|
||||||
let buttons , embed = rpsEmbed false None defender
|
let buttons , embed = rpsEmbed false None defender
|
||||||
let builder =
|
let builder =
|
||||||
DiscordFollowupMessageBuilder()
|
DiscordFollowupMessageBuilder()
|
||||||
@ -100,7 +99,7 @@ let handleRPS (ctx : IDiscordContext) =
|
|||||||
let move = tokens.[1]
|
let move = tokens.[1]
|
||||||
let targetId = uint64 tokens.[2]
|
let targetId = uint64 tokens.[2]
|
||||||
let isResponse = tokens.[4] = "True"
|
let isResponse = tokens.[4] = "True"
|
||||||
Game.executePlayerActionWithTargetId false targetId ctx (fun attacker defender -> async {
|
PlayerInteractions.executePlayerActionWithTargetId false targetId ctx (fun attacker defender -> async {
|
||||||
if isResponse then
|
if isResponse then
|
||||||
let eventCtx = ctx.GetContext() :?> ComponentInteractionCreateEventArgs
|
let eventCtx = ctx.GetContext() :?> ComponentInteractionCreateEventArgs
|
||||||
let buttons , embed = rpsEmbed true None attacker
|
let buttons , embed = rpsEmbed true None attacker
|
@ -1,7 +1,6 @@
|
|||||||
module Degenz.SlotMachine
|
module Degenz.SlotMachine
|
||||||
|
|
||||||
open System
|
open System
|
||||||
open System.Threading.Tasks
|
|
||||||
open DSharpPlus
|
open DSharpPlus
|
||||||
open DSharpPlus.Entities
|
open DSharpPlus.Entities
|
||||||
open DSharpPlus.SlashCommands
|
open DSharpPlus.SlashCommands
|
||||||
@ -15,7 +14,7 @@ type SlotMachine() =
|
|||||||
|
|
||||||
[<SlashCommand("spin", "Want to try your luck?")>]
|
[<SlashCommand("spin", "Want to try your luck?")>]
|
||||||
member this.Spin (ctx : InteractionContext) =
|
member this.Spin (ctx : InteractionContext) =
|
||||||
Game.executePlayerAction (DiscordInteractionContext ctx) (fun player -> async {
|
PlayerInteractions.executePlayerAction (DiscordInteractionContext ctx) (fun player -> async {
|
||||||
let sleepTime = 1000
|
let sleepTime = 1000
|
||||||
let random = Random(System.Guid.NewGuid().GetHashCode())
|
let random = Random(System.Guid.NewGuid().GetHashCode())
|
||||||
let results = [ random.Next(0, 3) ; random.Next(0, 3) ; random.Next(0, 3)]
|
let results = [ random.Next(0, 3) ; random.Next(0, 3) ; random.Next(0, 3)]
|
||||||
@ -24,10 +23,10 @@ type SlotMachine() =
|
|||||||
|| (results.[0] <> results.[1] && results.[1] <> results.[2] && results.[0] <> results.[2])
|
|| (results.[0] <> results.[1] && results.[1] <> results.[2] && results.[0] <> results.[2])
|
||||||
|
|
||||||
if winConditions then
|
if winConditions then
|
||||||
do! DbService.updatePlayer GuildEnvironment.pgDb { player with Bank = player.Bank + 10<GBT> }
|
do! DbService.updatePlayer { player with Bank = player.Bank + 10<GBT> }
|
||||||
|> Async.Ignore
|
|> Async.Ignore
|
||||||
else
|
else
|
||||||
do! DbService.updatePlayer GuildEnvironment.pgDb { player with Bank = max (player.Bank - 1<GBT>) 0<GBT> }
|
do! DbService.updatePlayer { player with Bank = max (player.Bank - 1<GBT>) 0<GBT> }
|
||||||
|> Async.Ignore
|
|> Async.Ignore
|
||||||
|
|
||||||
|
|
240
Bot/Games/Store.fs
Normal file
240
Bot/Games/Store.fs
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
module Degenz.Store
|
||||||
|
|
||||||
|
open System
|
||||||
|
open System.Threading.Tasks
|
||||||
|
open DSharpPlus.Entities
|
||||||
|
open DSharpPlus
|
||||||
|
open DSharpPlus.EventArgs
|
||||||
|
open DSharpPlus.SlashCommands
|
||||||
|
open Degenz
|
||||||
|
open Degenz.Messaging
|
||||||
|
open Degenz.PlayerInteractions
|
||||||
|
|
||||||
|
let getBuyItemsEmbed (playerInventory : Inventory) (storeInventory : Inventory) =
|
||||||
|
let embeds , 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
|
||||||
|
|
||||||
|
DiscordFollowupMessageBuilder()
|
||||||
|
.AddEmbeds(embeds)
|
||||||
|
.AddComponents(buttons)
|
||||||
|
.AsEphemeral(true)
|
||||||
|
|
||||||
|
let getSellEmbed (items : ItemDetails list) =
|
||||||
|
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.unzip
|
||||||
|
|
||||||
|
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 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"
|
||||||
|
|
||||||
|
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 checkSoldItemAlready 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."
|
||||||
|
|
||||||
|
let checkHasItemsInArsenal itemType items player =
|
||||||
|
if List.isEmpty items |> not
|
||||||
|
then Ok player
|
||||||
|
else Error $"You currently have no {itemType} in your arsenal to sell!"
|
||||||
|
|
||||||
|
let buy getItems (ctx : IDiscordContext) =
|
||||||
|
executePlayerAction ctx (fun player -> async {
|
||||||
|
let itemStore = getBuyItemsEmbed (getItems player.Inventory) (getItems Armory.weapons)
|
||||||
|
do! ctx.FollowUp itemStore |> Async.AwaitTask
|
||||||
|
})
|
||||||
|
|
||||||
|
let sell itemType getItems (ctx : IDiscordContext) =
|
||||||
|
executePlayerAction ctx (fun player -> async {
|
||||||
|
let items = getItems player.Inventory
|
||||||
|
match checkHasItemsInArsenal itemType items player with
|
||||||
|
| Ok _ -> let itemStore = getSellEmbed items
|
||||||
|
do! ctx.FollowUp(itemStore) |> Async.AwaitTask
|
||||||
|
| Error e -> do! sendFollowUpMessage ctx e
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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
|
||||||
|
do! player
|
||||||
|
|> checkHasSufficientFunds item.getItem
|
||||||
|
>>= checkAlreadyOwnsItem item.getItem
|
||||||
|
|> 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"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
let handleSell (ctx : IDiscordContext) itemId =
|
||||||
|
executePlayerAction ctx (fun player -> async {
|
||||||
|
let item = Armory.weapons |> Inventory.findItemById itemId
|
||||||
|
do!
|
||||||
|
player
|
||||||
|
|> checkSoldItemAlready item.getItem
|
||||||
|
|> 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}" ]
|
||||||
|
|> Async.Parallel
|
||||||
|
|> Async.Ignore
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
//let inventory (ctx : IDiscordContext) =
|
||||||
|
// executePlayerAction ctx (fun player -> async {
|
||||||
|
// player.Inventory
|
||||||
|
// |> List.groupBy (fun item -> item.Details)
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//let consume (ctx : IDiscordContext) =
|
||||||
|
// executePlayerAction ctx (fun player -> async {
|
||||||
|
//
|
||||||
|
// })
|
||||||
|
|
||||||
|
let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) =
|
||||||
|
let ctx = DiscordEventContext event :> IDiscordContext
|
||||||
|
let id = ctx.GetInteractionId()
|
||||||
|
let itemId = int <| id.Split("-").[1]
|
||||||
|
match id with
|
||||||
|
| id when id.StartsWith("Buy") -> handleBuyItem ctx itemId
|
||||||
|
| id when id.StartsWith("Sell") -> handleSell ctx itemId
|
||||||
|
| _ ->
|
||||||
|
task {
|
||||||
|
let builder = DiscordInteractionResponseBuilder()
|
||||||
|
builder.IsEphemeral <- true
|
||||||
|
builder.Content <- $"Incorrect Action identifier {id}"
|
||||||
|
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
|
||||||
|
}
|
||||||
|
|
||||||
|
type Store() =
|
||||||
|
inherit ApplicationCommandModule ()
|
||||||
|
|
||||||
|
let enforceChannel (ctx : IDiscordContext) (storeFn : IDiscordContext -> Task) =
|
||||||
|
match ctx.GetChannel().Id with
|
||||||
|
| id when id = GuildEnvironment.channelArmory -> storeFn ctx
|
||||||
|
| _ ->
|
||||||
|
task {
|
||||||
|
let msg = $"You must go to <#{GuildEnvironment.channelArmory}> channel to buy or sell weapons"
|
||||||
|
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 (Inventory.getItemsByType 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// [<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 (Inventory.getItemsByType ItemType.Hack))
|
||||||
|
|
||||||
|
[<SlashCommand("buy-shield", "Purchase a hack shield so you can protect your GBT")>]
|
||||||
|
member this.BuyShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy (Inventory.getItemsByType ItemType.Shield))
|
||||||
|
|
||||||
|
// [<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))
|
||||||
|
|
||||||
|
[<SlashCommand("sell-shield", "Sell a shield for GoodBoyTokenz")>]
|
||||||
|
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))
|
||||||
|
|
||||||
|
// [<SlashCommand("inventory", "Check your inventory")>]
|
||||||
|
// member this.Inventory (ctx : InteractionContext) =
|
||||||
|
// enforceChannel (DiscordInteractionContext(ctx)) (sell "Shields" (Inventory.getItemsByType ItemType))
|
||||||
|
|
@ -7,6 +7,7 @@ open DSharpPlus.Entities
|
|||||||
open DSharpPlus.EventArgs
|
open DSharpPlus.EventArgs
|
||||||
open DSharpPlus.SlashCommands
|
open DSharpPlus.SlashCommands
|
||||||
open Degenz.Messaging
|
open Degenz.Messaging
|
||||||
|
open Degenz.PlayerInteractions
|
||||||
|
|
||||||
let ThiefCooldown = TimeSpan.FromMinutes(1)
|
let ThiefCooldown = TimeSpan.FromMinutes(1)
|
||||||
let VictimRecovery = TimeSpan.FromHours(1)
|
let VictimRecovery = TimeSpan.FromHours(1)
|
||||||
@ -71,7 +72,7 @@ let checkVictimStealingCooldown defender attacker =
|
|||||||
defender
|
defender
|
||||||
|> Player.removeExpiredActions
|
|> Player.removeExpiredActions
|
||||||
|> fun p -> p.Events
|
|> fun p -> p.Events
|
||||||
|> Array.tryFind (fun e ->
|
|> List.tryFind (fun e ->
|
||||||
match e.Type with Stealing _ -> true | _ -> false)
|
match e.Type with Stealing _ -> true | _ -> false)
|
||||||
|> function
|
|> function
|
||||||
| Some act ->
|
| Some act ->
|
||||||
@ -81,7 +82,7 @@ let checkVictimStealingCooldown defender attacker =
|
|||||||
| None -> Ok attacker
|
| None -> Ok attacker
|
||||||
|
|
||||||
let checkTargetHasFunds target player =
|
let checkTargetHasFunds target player =
|
||||||
match target.Bank = 0<GBT> with
|
match target.Bank <= 0<GBT> with
|
||||||
| true -> Error $"Looks like the poor bastard has no $GBT... pick a different victim."
|
| true -> Error $"Looks like the poor bastard has no $GBT... pick a different victim."
|
||||||
| false -> Ok player
|
| false -> Ok player
|
||||||
|
|
||||||
@ -94,7 +95,7 @@ let checkThiefCooldown attacker =
|
|||||||
attacker
|
attacker
|
||||||
|> Player.removeExpiredActions
|
|> Player.removeExpiredActions
|
||||||
|> fun p -> p.Events
|
|> fun p -> p.Events
|
||||||
|> Array.tryFind (fun pe -> match pe.Type with Stealing(instigator, _) -> instigator | _ -> false)
|
|> List.tryFind (fun pe -> match pe.Type with Stealing(instigator, _) -> instigator | _ -> false)
|
||||||
|> function
|
|> function
|
||||||
| Some act ->
|
| Some act ->
|
||||||
let cooldown = ThiefCooldown - (DateTime.UtcNow - act.Timestamp)
|
let cooldown = ThiefCooldown - (DateTime.UtcNow - act.Timestamp)
|
||||||
@ -119,7 +120,7 @@ let calculateWinPercentage amountRequested bank attackerStrength defenderStrengt
|
|||||||
//calculateWinPercentage 50 200 100 85
|
//calculateWinPercentage 50 200 100 85
|
||||||
|
|
||||||
let steal target amount (ctx : IDiscordContext) =
|
let steal target amount (ctx : IDiscordContext) =
|
||||||
Game.executePlayerActionWithTarget target ctx (fun thief victim -> async { do!
|
executePlayerActionWithTarget target ctx (fun thief victim -> async { do!
|
||||||
thief
|
thief
|
||||||
|> checkPlayerIsAttackingThemselves victim
|
|> checkPlayerIsAttackingThemselves victim
|
||||||
// |> checkVictimStealingCooldown victim
|
// |> checkVictimStealingCooldown victim
|
||||||
@ -127,7 +128,7 @@ let steal target amount (ctx : IDiscordContext) =
|
|||||||
>>= checkPrizeRequestZero amount
|
>>= checkPrizeRequestZero amount
|
||||||
|> handleResultWithResponse ctx (fun _ -> async {
|
|> handleResultWithResponse ctx (fun _ -> async {
|
||||||
let cappedPrize , winPercentage , wasCapped =
|
let cappedPrize , winPercentage , wasCapped =
|
||||||
calculateWinPercentage amount (int victim.Bank) thief.Traits.Strength victim.Traits.Strength
|
calculateWinPercentage amount (int victim.Bank) thief.Stats.Strength.Amount victim.Stats.Strength.Amount
|
||||||
|
|
||||||
let chance = int (winPercentage * 100.0)
|
let chance = int (winPercentage * 100.0)
|
||||||
let buttons =
|
let buttons =
|
||||||
@ -137,7 +138,7 @@ let steal target amount (ctx : IDiscordContext) =
|
|||||||
|
|
||||||
let cappedMsg = if wasCapped then $"They only have {cappedPrize} $GBT though... " else ""
|
let cappedMsg = if wasCapped then $"They only have {cappedPrize} $GBT though... " else ""
|
||||||
let strengthMsg =
|
let strengthMsg =
|
||||||
match thief.Traits.Strength - victim.Traits.Strength with
|
match thief.Stats.Strength.Amount - victim.Stats.Strength.Amount with
|
||||||
| diff when diff < -50 -> "much stronger"
|
| diff when diff < -50 -> "much stronger"
|
||||||
| diff when diff < 0 -> "stronger"
|
| diff when diff < 0 -> "stronger"
|
||||||
| diff when diff < 50 -> "weaker"
|
| diff when diff < 50 -> "weaker"
|
||||||
@ -160,7 +161,7 @@ let handleSteal (ctx : IDiscordContext) =
|
|||||||
let targetId = uint64 tokens.[2]
|
let targetId = uint64 tokens.[2]
|
||||||
let targetName = tokens.[3]
|
let targetName = tokens.[3]
|
||||||
let amount = int tokens.[4]
|
let amount = int tokens.[4]
|
||||||
let prize , winPercentage , _ = calculateWinPercentage amount (int victim.Bank) thief.Traits.Strength victim.Traits.Strength
|
let prize , winPercentage , _ = calculateWinPercentage amount (int victim.Bank) thief.Stats.Strength.Amount victim.Stats.Strength.Amount
|
||||||
let prize = int prize * 1<GBT>
|
let prize = int prize * 1<GBT>
|
||||||
|
|
||||||
let rand = Random(Guid.NewGuid().GetHashCode())
|
let rand = Random(Guid.NewGuid().GetHashCode())
|
||||||
@ -174,15 +175,15 @@ let handleSteal (ctx : IDiscordContext) =
|
|||||||
| true ->
|
| true ->
|
||||||
let embed = getResultEmbed' Success
|
let embed = getResultEmbed' Success
|
||||||
do! Messaging.sendFollowUpEmbed ctx (embed.Build())
|
do! Messaging.sendFollowUpEmbed ctx (embed.Build())
|
||||||
match! DbService.tryFindPlayer GuildEnvironment.pgDb targetId with
|
match! DbService.tryFindPlayer targetId with
|
||||||
| Some t ->
|
| Some t ->
|
||||||
let mugged = {
|
let mugged = {
|
||||||
Type = Stealing ( false , thief.basicPlayer )
|
Type = Stealing ( false , thief.toDiscordPlayer )
|
||||||
Timestamp = DateTime.UtcNow
|
Timestamp = DateTime.UtcNow
|
||||||
Cooldown = VictimRecovery.Minutes * 1<mins>
|
Cooldown = VictimRecovery.Minutes * 1<mins>
|
||||||
}
|
}
|
||||||
do! DbService.updatePlayer GuildEnvironment.pgDb { t with Bank = max (t.Bank - prize) 0<GBT> } |> Async.Ignore
|
do! DbService.updatePlayer { t with Bank = max (t.Bank - prize) 0<GBT> } |> Async.Ignore
|
||||||
do! DbService.addPlayerEvent GuildEnvironment.pgDb victim.DiscordId mugged |> Async.Ignore
|
do! DbService.addPlayerEvent victim.DiscordId mugged |> Async.Ignore
|
||||||
| None -> ()
|
| None -> ()
|
||||||
|
|
||||||
let stole = {
|
let stole = {
|
||||||
@ -190,8 +191,8 @@ let handleSteal (ctx : IDiscordContext) =
|
|||||||
Cooldown = ThiefCooldown.Minutes * 1<mins>
|
Cooldown = ThiefCooldown.Minutes * 1<mins>
|
||||||
Timestamp = DateTime.UtcNow
|
Timestamp = DateTime.UtcNow
|
||||||
}
|
}
|
||||||
do! DbService.updatePlayer GuildEnvironment.pgDb { thief with Bank = thief.Bank + prize } |> Async.Ignore
|
do! DbService.updatePlayer { thief with Bank = thief.Bank + prize } |> Async.Ignore
|
||||||
do! DbService.addPlayerEvent GuildEnvironment.pgDb victim.DiscordId stole |> Async.Ignore
|
do! DbService.addPlayerEvent victim.DiscordId stole |> Async.Ignore
|
||||||
let builder = DiscordMessageBuilder()
|
let builder = DiscordMessageBuilder()
|
||||||
builder.WithContent($"{thief.Name} stole {prize} from <@{victim.DiscordId}>!") |> ignore
|
builder.WithContent($"{thief.Name} stole {prize} from <@{victim.DiscordId}>!") |> ignore
|
||||||
let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelEventsHackerBattle)
|
let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelEventsHackerBattle)
|
||||||
@ -205,7 +206,7 @@ let handleSteal (ctx : IDiscordContext) =
|
|||||||
Cooldown = ThiefCooldown.Minutes * 1<mins>
|
Cooldown = ThiefCooldown.Minutes * 1<mins>
|
||||||
Timestamp = DateTime.UtcNow
|
Timestamp = DateTime.UtcNow
|
||||||
}
|
}
|
||||||
do! DbService.addPlayerEvent GuildEnvironment.pgDb victim.DiscordId imprisoned |> Async.Ignore
|
do! DbService.addPlayerEvent victim.DiscordId imprisoned |> Async.Ignore
|
||||||
do! Messaging.sendFollowUpEmbed ctx (embed.Build())
|
do! Messaging.sendFollowUpEmbed ctx (embed.Build())
|
||||||
do! Async.Sleep 2000
|
do! Async.Sleep 2000
|
||||||
let role = ctx.GetGuild().GetRole(GuildEnvironment.rolePrisoner)
|
let role = ctx.GetGuild().GetRole(GuildEnvironment.rolePrisoner)
|
||||||
@ -219,7 +220,7 @@ let handleSteal (ctx : IDiscordContext) =
|
|||||||
}
|
}
|
||||||
if answer = "yes" then
|
if answer = "yes" then
|
||||||
let targetId = uint64 tokens.[2]
|
let targetId = uint64 tokens.[2]
|
||||||
Game.executePlayerActionWithTargetId true targetId ctx (fun attacker defender -> async {
|
executePlayerActionWithTargetId true targetId ctx (fun attacker defender -> async {
|
||||||
do! attacker
|
do! attacker
|
||||||
|> Player.removeExpiredActions
|
|> Player.removeExpiredActions
|
||||||
// |> checkVictimStealingCooldown defender
|
// |> checkVictimStealingCooldown defender
|
@ -4,27 +4,29 @@ open System.Text
|
|||||||
open System.Threading.Tasks
|
open System.Threading.Tasks
|
||||||
open DSharpPlus
|
open DSharpPlus
|
||||||
open DSharpPlus.Entities
|
open DSharpPlus.Entities
|
||||||
open DSharpPlus.EventArgs
|
|
||||||
open Degenz.Types
|
open Degenz.Types
|
||||||
open Degenz.Messaging
|
open Degenz.Messaging
|
||||||
|
|
||||||
let trainerAchievement = "FINISHED_TRAINER"
|
let trainerAchievement = "FINISHED_TRAINER"
|
||||||
let Sensei = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" }
|
let Sensei = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" }
|
||||||
let defaultHack = Armory.battleItems |> Array.find (fun i -> i.Id = int HackId.Virus)
|
let defaultHack = Armory.weapons |> Inventory.findHackById (int ItemId.Virus)
|
||||||
let defaultShield = Armory.battleItems |> Array.find (fun i -> i.Id = int ShieldId.Firewall)
|
let defaultShield = Armory.weapons |> Inventory.findShieldById (int ItemId.Firewall)
|
||||||
|
|
||||||
let TrainerEvents = [|
|
let HackEvent () = {
|
||||||
{ Timestamp = System.DateTime.UtcNow
|
Timestamp = System.DateTime.UtcNow
|
||||||
Cooldown = 2<mins>
|
Cooldown = 1<mins>
|
||||||
Type = Hacking {
|
Type = Hacking {
|
||||||
Adversary = Sensei
|
Adversary = Sensei
|
||||||
Success = true
|
Success = true
|
||||||
IsInstigator = true
|
IsInstigator = true
|
||||||
HackId = defaultHack.Id } }
|
HackId = defaultHack.Item.Id
|
||||||
{ Timestamp = System.DateTime.UtcNow
|
}
|
||||||
Cooldown = defaultShield.Cooldown
|
}
|
||||||
Type = Shielding defaultShield.Id }
|
let ShieldEvent () = {
|
||||||
|]
|
Timestamp = System.DateTime.UtcNow
|
||||||
|
Cooldown = defaultShield.Cooldown
|
||||||
|
Type = Shielding defaultShield.Item.Id
|
||||||
|
}
|
||||||
|
|
||||||
let sendInitialEmbed (client : DiscordClient) =
|
let sendInitialEmbed (client : DiscordClient) =
|
||||||
async {
|
async {
|
||||||
@ -53,7 +55,7 @@ let handleTrainerStep1 (ctx : IDiscordContext) =
|
|||||||
|> Async.AwaitTask
|
|> Async.AwaitTask
|
||||||
let msg = "Beautopia© is a dangerous place... quick, put up a SHIELD 🛡 before another Degen hacks you, and takes your 💰$GBT.\n\n"
|
let msg = "Beautopia© is a dangerous place... quick, put up a SHIELD 🛡 before another Degen hacks you, and takes your 💰$GBT.\n\n"
|
||||||
+ "To enable it, you need to run the `/shield` slash command.\n\n"
|
+ "To enable it, you need to run the `/shield` slash command.\n\n"
|
||||||
+ $"Type the `/shield` command now, then select - `{defaultShield.Name}`\n"
|
+ $"Type the `/shield` command now, then select - `{defaultShield.Item.Name}`\n"
|
||||||
let builder =
|
let builder =
|
||||||
DiscordInteractionResponseBuilder()
|
DiscordInteractionResponseBuilder()
|
||||||
.WithContent(msg)
|
.WithContent(msg)
|
||||||
@ -67,7 +69,7 @@ let defend (ctx : IDiscordContext) =
|
|||||||
do! Messaging.defer ctx
|
do! Messaging.defer ctx
|
||||||
let m = ctx.GetDiscordMember()
|
let m = ctx.GetDiscordMember()
|
||||||
let name = if System.String.IsNullOrEmpty m.Nickname then m.DisplayName else m.Nickname
|
let name = if System.String.IsNullOrEmpty m.Nickname then m.DisplayName else m.Nickname
|
||||||
let embed = Embeds.pickDefense "Trainer-2" { PlayerData.empty with Inventory = [| defaultShield |] ; Name = name } true
|
let embed = Embeds.pickDefense "Trainer-2" { PlayerData.empty with Inventory = [ Shield defaultShield ] ; Name = name } true
|
||||||
do! ctx.FollowUp(embed) |> Async.AwaitTask
|
do! ctx.FollowUp(embed) |> Async.AwaitTask
|
||||||
} |> Async.StartAsTask :> Task
|
} |> Async.StartAsTask :> Task
|
||||||
|
|
||||||
@ -84,17 +86,16 @@ let handleDefense (ctx : IDiscordContext) =
|
|||||||
|
|
||||||
let sendMessage' = sendFollowUpMessage ctx
|
let sendMessage' = sendFollowUpMessage ctx
|
||||||
let tokens = ctx.GetInteractionId().Split("-")
|
let tokens = ctx.GetInteractionId().Split("-")
|
||||||
let shieldId = enum<ShieldId>(int tokens.[2])
|
let shieldId = enum<ItemId>(int tokens.[2])
|
||||||
let shield = Armory.getItem (int shieldId)
|
|
||||||
let playerName = tokens.[4]
|
let playerName = tokens.[4]
|
||||||
let embed = Embeds.responseCreatedShield shield
|
let embed = Embeds.responseCreatedShield defaultShield
|
||||||
do! ctx.FollowUp embed |> Async.AwaitTask
|
do! ctx.FollowUp embed |> Async.AwaitTask
|
||||||
do! Async.Sleep 4000
|
do! Async.Sleep 4000
|
||||||
do! sendMessage' $"Ok, good, let me make sure that worked.\n\nI'll try to **hack** you now with **{defaultHack.Name}**"
|
do! sendMessage' $"Ok, good, let me make sure that worked.\n\nI'll try to **hack** you now with **{defaultHack.Item.Name}**"
|
||||||
do! Async.Sleep 5000
|
do! Async.Sleep 5000
|
||||||
do! sendMessage' $"❌ HACKING FAILED!\n\n{playerName} defended hack from <@{Sensei.Id}>!"
|
do! sendMessage' $"❌ HACKING FAILED!\n\n{playerName} defended hack from <@{Sensei.Id}>!"
|
||||||
do! Async.Sleep 4000
|
do! Async.Sleep 4000
|
||||||
do! sendFollowUpMessageWithButton ctx (handleDefenseMsg defaultHack.Name)
|
do! sendFollowUpMessageWithButton ctx (handleDefenseMsg defaultHack.Item.Name)
|
||||||
} |> Async.StartAsTask :> Task
|
} |> Async.StartAsTask :> Task
|
||||||
|
|
||||||
let handleTrainerStep3 (ctx : IDiscordContext) =
|
let handleTrainerStep3 (ctx : IDiscordContext) =
|
||||||
@ -105,7 +106,7 @@ let handleTrainerStep3 (ctx : IDiscordContext) =
|
|||||||
.WithContent
|
.WithContent
|
||||||
( "Now let’s **HACK** 💻... I want you to **HACK ME**!\n\n"
|
( "Now let’s **HACK** 💻... I want you to **HACK ME**!\n\n"
|
||||||
+ "To **hack**, you need to run the `/hack` slash command.\n"
|
+ "To **hack**, you need to run the `/hack` slash command.\n"
|
||||||
+ $"Type the `/hack` command now, then choose me - <@{Sensei.Id}> as your target, and select `{defaultHack.Name}`")
|
+ $"Type the `/hack` command now, then choose me - <@{Sensei.Id}> as your target, and select `{defaultHack.Item.Name}`")
|
||||||
|
|
||||||
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
|
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
|
||||||
} |> Async.StartAsTask :> Task
|
} |> Async.StartAsTask :> Task
|
||||||
@ -117,8 +118,9 @@ let hack (target : DiscordUser) (ctx : IDiscordContext) =
|
|||||||
let isRightTarget = target.Id = Sensei.Id
|
let isRightTarget = target.Id = Sensei.Id
|
||||||
match isRightTarget with
|
match isRightTarget with
|
||||||
| true ->
|
| true ->
|
||||||
|
let player = { PlayerData.empty with Inventory = [ Hack defaultHack ] }
|
||||||
let bot = { PlayerData.empty with DiscordId = Sensei.Id ; Name = Sensei.Name }
|
let bot = { PlayerData.empty with DiscordId = Sensei.Id ; Name = Sensei.Name }
|
||||||
let embed = Embeds.pickHack "Trainer-4" { PlayerData.empty with Inventory = [| defaultHack |] } bot true
|
let embed = Embeds.pickHack "Trainer-4" player bot true
|
||||||
|
|
||||||
do! ctx.FollowUp(embed) |> Async.AwaitTask
|
do! ctx.FollowUp(embed) |> Async.AwaitTask
|
||||||
| false ->
|
| false ->
|
||||||
@ -131,7 +133,7 @@ let hack (target : DiscordUser) (ctx : IDiscordContext) =
|
|||||||
} |> Async.StartAsTask :> Task
|
} |> Async.StartAsTask :> Task
|
||||||
|
|
||||||
let handleHack (ctx : IDiscordContext) =
|
let handleHack (ctx : IDiscordContext) =
|
||||||
Game.executePlayerAction ctx (fun player -> async {
|
PlayerInteractions.executePlayerAction ctx (fun player -> async {
|
||||||
let sendMessage' = sendFollowUpMessage ctx
|
let sendMessage' = sendFollowUpMessage ctx
|
||||||
do! Async.Sleep 1000
|
do! Async.Sleep 1000
|
||||||
let embed = Embeds.responseSuccessfulHack false Sensei.Id defaultHack.Power defaultHack
|
let embed = Embeds.responseSuccessfulHack false Sensei.Id defaultHack.Power defaultHack
|
||||||
@ -147,12 +149,12 @@ let handleHack (ctx : IDiscordContext) =
|
|||||||
|
|
||||||
let sb = StringBuilder("Here, ")
|
let sb = StringBuilder("Here, ")
|
||||||
|
|
||||||
let! completed = DbService.checkHasAchievement GuildEnvironment.pgDb player.DiscordId trainerAchievement
|
let! completed = DbService.checkHasAchievement player.DiscordId trainerAchievement
|
||||||
if not completed then
|
if not completed then
|
||||||
do! DbService.addAchievement GuildEnvironment.pgDb player.DiscordId trainerAchievement
|
do! DbService.addAchievement player.DiscordId trainerAchievement
|
||||||
|> Async.Ignore
|
|> Async.Ignore
|
||||||
|
|
||||||
sb.Append($"I'm going to gift you a hack,`{defaultHack.Name}` and a shield, `{defaultShield.Name}`") |> ignore
|
sb.Append($"I'm going to gift you a hack,`{defaultHack.Item.Name}` and a shield, `{defaultShield.Item.Name}`") |> ignore
|
||||||
sb.Append(", you'll need em to survive\n\n") |> ignore
|
sb.Append(", you'll need em to survive\n\n") |> ignore
|
||||||
sb.AppendLine("To finish your training and collect the loot, type the `/arsenal` command **NOW**") |> ignore
|
sb.AppendLine("To finish your training and collect the loot, type the `/arsenal` command **NOW**") |> ignore
|
||||||
do! Async.Sleep 1000
|
do! Async.Sleep 1000
|
||||||
@ -166,30 +168,50 @@ let handleHack (ctx : IDiscordContext) =
|
|||||||
})
|
})
|
||||||
|
|
||||||
let handleArsenal (ctx : IDiscordContext) =
|
let handleArsenal (ctx : IDiscordContext) =
|
||||||
Game.executePlayerAction ctx (fun player -> async {
|
PlayerInteractions.executePlayerAction ctx (fun player -> async {
|
||||||
let hasStockWeapons = Player.getHacks player |> Array.exists (fun item -> item.Id = defaultHack.Id)
|
let hack =
|
||||||
let updatedPlayer =
|
if player.Inventory |> List.exists (fun i -> i.Id = defaultHack.Item.Id)
|
||||||
if not hasStockWeapons then {
|
then []
|
||||||
Player.removeExpiredActions player with
|
else [ Hack defaultHack ]
|
||||||
Events = TrainerEvents |> Array.append player.Events
|
let shield =
|
||||||
Inventory = [| defaultHack ; defaultShield |] |> Array.append player.Inventory
|
if player.Inventory |> List.exists (fun i -> i.Id = defaultShield.Item.Id)
|
||||||
}
|
then []
|
||||||
else
|
else [ Shield defaultShield ]
|
||||||
Player.removeExpiredActions player
|
let shieldEvent =
|
||||||
if not hasStockWeapons then
|
let hasShield =
|
||||||
do!
|
player
|
||||||
[ DbService.addPlayerEvent GuildEnvironment.pgDb player.DiscordId TrainerEvents.[0]
|
|> Player.removeExpiredActions
|
||||||
DbService.addPlayerEvent GuildEnvironment.pgDb player.DiscordId TrainerEvents.[1]
|
|> fun p -> p.Events
|
||||||
DbService.updatePlayer GuildEnvironment.pgDb updatedPlayer ]
|
|> List.exists (fun e -> match e.Type with Shielding shieldId -> shieldId = defaultShield.Item.Id | _ -> false)
|
||||||
|> Async.Parallel
|
if hasShield
|
||||||
|> Async.Ignore
|
then []
|
||||||
let embed = Embeds.getArsenalEmbed updatedPlayer
|
else [ ShieldEvent() ]
|
||||||
|
let updatedPlayer = {
|
||||||
|
Player.removeExpiredActions player with
|
||||||
|
Events = shieldEvent @ player.Events
|
||||||
|
Inventory = hack @ shield @ player.Inventory
|
||||||
|
}
|
||||||
|
if not (List.isEmpty hack) || not (List.isEmpty shield) then
|
||||||
|
do! DbService.updatePlayer updatedPlayer |> Async.Ignore
|
||||||
|
if not (List.isEmpty shieldEvent) then
|
||||||
|
try
|
||||||
|
do! DbService.addPlayerEvent player.DiscordId (List.head shieldEvent) |> Async.Ignore
|
||||||
|
with ex ->
|
||||||
|
printfn "%s" ex.Message
|
||||||
|
()
|
||||||
|
|
||||||
|
let playerForEmbed = {
|
||||||
|
player with
|
||||||
|
Events = [ HackEvent() ; ShieldEvent() ]
|
||||||
|
Inventory = hack @ shield @ player.Inventory
|
||||||
|
}
|
||||||
|
let embed = Embeds.getArsenalEmbed playerForEmbed
|
||||||
do! ctx.FollowUp(embed) |> Async.AwaitTask
|
do! ctx.FollowUp(embed) |> Async.AwaitTask
|
||||||
|
|
||||||
let! completed = DbService.checkHasAchievement GuildEnvironment.pgDb player.DiscordId trainerAchievement
|
let! completed = DbService.checkHasAchievement player.DiscordId trainerAchievement
|
||||||
if not completed then
|
if not completed then
|
||||||
do! Async.Sleep 3000
|
do! Async.Sleep 3000
|
||||||
let rewards = [ $"{defaultHack.Name} Hack" ; $"{defaultShield.Name} Shield" ]
|
let rewards = [ $"{defaultHack.Item.Name} Hack" ; $"{defaultShield.Item.Name} Shield" ]
|
||||||
let embed = Embeds.getAchievementEmbed rewards "You completed the Training Dojo and collected loot." trainerAchievement
|
let embed = Embeds.getAchievementEmbed rewards "You completed the Training Dojo and collected loot." trainerAchievement
|
||||||
do! ctx.FollowUp(embed) |> Async.AwaitTask
|
do! ctx.FollowUp(embed) |> Async.AwaitTask
|
||||||
do! Async.Sleep 2000
|
do! Async.Sleep 2000
|
@ -4,27 +4,34 @@ module Degenz.GuildEnvironment
|
|||||||
open System
|
open System
|
||||||
open DSharpPlus.Entities
|
open DSharpPlus.Entities
|
||||||
open dotenv.net
|
open dotenv.net
|
||||||
DotEnv.Load(DotEnvOptions(envFilePaths = [ "../../../../.dev.env" ], overwriteExistingVars = false))
|
//DotEnv.Load(DotEnvOptions(envFilePaths = [ "../../../../.dev.env" ], overwriteExistingVars = false))
|
||||||
//DotEnv.Load(DotEnvOptions(envFilePaths = [ "../../../../.stag.env" ], overwriteExistingVars = false))
|
DotEnv.Load(DotEnvOptions(envFilePaths = [ "../../../../.stag.env" ], overwriteExistingVars = false))
|
||||||
//DotEnv.Load(DotEnvOptions(envFilePaths = [ "../../../../.prod.env" ], overwriteExistingVars = false))
|
//DotEnv.Load(DotEnvOptions(envFilePaths = [ "../../../../.prod.env" ], overwriteExistingVars = false))
|
||||||
|
|
||||||
let getVar str = Environment.GetEnvironmentVariable(str)
|
let getVar str = Environment.GetEnvironmentVariable(str)
|
||||||
let getId str = getVar str |> uint64
|
let getId str = getVar str |> uint64
|
||||||
|
|
||||||
let pgDb = (getVar "DATABASE_URL").Replace("postgresql://", "postgres://").Replace("?sslmode=require", "")
|
let connectionString = (getVar "DATABASE_URL").Replace("postgresql://", "postgres://").Replace("?sslmode=require", "")
|
||||||
|
|
||||||
let guildId = getId "DISCORD_GUILD"
|
let guildId = getId "DISCORD_GUILD"
|
||||||
let tokenPlayerInteractions = getVar "TOKEN_PLAYER_INTERACTIONS"
|
let tokenPlayerInteractions = getVar "TOKEN_PLAYER_INTERACTIONS"
|
||||||
let tokenSteal = getVar "TOKEN_STEAL"
|
let tokenSteal = getVar "TOKEN_STEAL"
|
||||||
let tokenHackerBattle = getVar "TOKEN_HACKER_BATTLE"
|
let tokenHackerBattle = getVar "TOKEN_HACKER_BATTLE"
|
||||||
let tokenStore = getVar "TOKEN_STORE"
|
let tokenStore = getVar "TOKEN_STORE"
|
||||||
|
let tokenInviter = getVar "TOKEN_INVITER"
|
||||||
let channelEventsHackerBattle = getId "CHANNEL_EVENTS_HACKER_BATTLE"
|
let channelEventsHackerBattle = getId "CHANNEL_EVENTS_HACKER_BATTLE"
|
||||||
let channelTraining = getId "CHANNEL_TRAINING"
|
let channelTraining = getId "CHANNEL_TRAINING"
|
||||||
let channelArmory = getId "CHANNEL_ARMORY"
|
let channelArmory = getId "CHANNEL_ARMORY"
|
||||||
|
//let channelBackAlley = getId "CHANNEL_BACKALLEY"
|
||||||
let channelBattle = getId "CHANNEL_BATTLE"
|
let channelBattle = getId "CHANNEL_BATTLE"
|
||||||
|
//let channelMarket = getId "CHANNEL_MARKET"
|
||||||
|
//let channelAccessoryShop = getId "CHANNEL_ACCESSORIES"
|
||||||
|
let channelWelcome = getId "CHANNEL_WELCOME"
|
||||||
|
|
||||||
//let channelThievery = getId "CHANNEL_THIEVERY"
|
//let channelThievery = getId "CHANNEL_THIEVERY"
|
||||||
let botIdHackerBattle = getId "BOT_HACKER_BATTLE"
|
let botIdHackerBattle = getId "BOT_HACKER_BATTLE"
|
||||||
let botIdArmory = getId "BOT_ARMORY"
|
let botIdArmory = getId "BOT_ARMORY"
|
||||||
|
//let botInviter = getId "BOT_INVITER"
|
||||||
let roleTrainee = getId "ROLE_TRAINEE"
|
let roleTrainee = getId "ROLE_TRAINEE"
|
||||||
let rolePrisoner = getId "ROLE_PRISONER"
|
let rolePrisoner = getId "ROLE_PRISONER"
|
||||||
|
|
||||||
|
210
Bot/InviteTracker.fs
Normal file
210
Bot/InviteTracker.fs
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
module Degenz.InviteTracker
|
||||||
|
|
||||||
|
|
||||||
|
open System.Text
|
||||||
|
open System.Threading.Tasks
|
||||||
|
open DSharpPlus
|
||||||
|
open DSharpPlus.Entities
|
||||||
|
open DSharpPlus.EventArgs
|
||||||
|
open DSharpPlus.SlashCommands
|
||||||
|
open Degenz.Messaging
|
||||||
|
open Npgsql.FSharp
|
||||||
|
|
||||||
|
let connStr = GuildEnvironment.connectionString
|
||||||
|
|
||||||
|
type Invite = {
|
||||||
|
Code : string
|
||||||
|
Inviter : uint64
|
||||||
|
Count : int
|
||||||
|
}
|
||||||
|
|
||||||
|
let getInvites () = async {
|
||||||
|
let! invites =
|
||||||
|
connStr
|
||||||
|
|> Sql.connect
|
||||||
|
|> Sql.query """
|
||||||
|
SELECT code, inviter, count FROM invite
|
||||||
|
WHERE created_at > (current_timestamp at time zone 'utc') - interval '1 day'
|
||||||
|
"""
|
||||||
|
|> Sql.executeAsync (fun read -> {
|
||||||
|
Code = read.string "code"
|
||||||
|
Inviter = read.string "inviter" |> uint64
|
||||||
|
Count = read.int "count"
|
||||||
|
})
|
||||||
|
|> Async.AwaitTask
|
||||||
|
return
|
||||||
|
invites
|
||||||
|
|> List.map (fun inv -> (inv.Code , (inv.Inviter , inv.Count)))
|
||||||
|
|> Map.ofList
|
||||||
|
}
|
||||||
|
|
||||||
|
let createInvite inviter code =
|
||||||
|
connStr
|
||||||
|
|> Sql.connect
|
||||||
|
|> Sql.parameters [ "code" , Sql.string code ; "inviter" , Sql.string (string inviter) ]
|
||||||
|
|> Sql.query "INSERT INTO invite (code, inviter) VALUES (@code, @inviter)"
|
||||||
|
|> Sql.executeNonQueryAsync
|
||||||
|
|> Async.AwaitTask
|
||||||
|
|
||||||
|
let addInvitedUser did code count =
|
||||||
|
try
|
||||||
|
connStr
|
||||||
|
|> Sql.connect
|
||||||
|
|> Sql.executeTransactionAsync [
|
||||||
|
"""
|
||||||
|
INSERT INTO invited_user (discord_id, invite_id)
|
||||||
|
VALUES (@did, (SELECT id FROM invite WHERE code = @code));
|
||||||
|
""" , [ [ "@code" , Sql.string code ; "@did" , Sql.string (string did) ] ]
|
||||||
|
"UPDATE invite SET count = @count WHERE code = @code" , [ [ "count" , Sql.int count ; "code" , Sql.string code ] ]
|
||||||
|
]
|
||||||
|
|> Async.AwaitTask
|
||||||
|
|> Async.Ignore
|
||||||
|
with _ -> async.Zero ()
|
||||||
|
|
||||||
|
let removeInvitedUser did =
|
||||||
|
try
|
||||||
|
connStr
|
||||||
|
|> Sql.connect
|
||||||
|
|> Sql.parameters [ "did" , Sql.string (string did) ]
|
||||||
|
|> Sql.query "DELETE FROM invited_user WHERE discord_id = @did"
|
||||||
|
|> Sql.executeNonQueryAsync
|
||||||
|
|> Async.AwaitTask
|
||||||
|
|> Async.Ignore
|
||||||
|
with _ -> async.Zero ()
|
||||||
|
|
||||||
|
let getInviteAttributions userId =
|
||||||
|
connStr
|
||||||
|
|> Sql.connect
|
||||||
|
|> Sql.parameters [ "did" , Sql.string (string userId) ]
|
||||||
|
|> Sql.query """
|
||||||
|
SELECT count(*) FROM invited_user
|
||||||
|
JOIN invite ON invite.id = invited_user.invite_id
|
||||||
|
WHERE invite.inviter = @did
|
||||||
|
"""
|
||||||
|
|> Sql.executeRowAsync (fun read -> read.int "count")
|
||||||
|
|> Async.AwaitTask
|
||||||
|
|
||||||
|
let getInvitedUsers userId =
|
||||||
|
connStr
|
||||||
|
|> Sql.connect
|
||||||
|
|> Sql.parameters [ "did" , Sql.string (string userId) ]
|
||||||
|
|> Sql.query """
|
||||||
|
WITH invite AS (SELECT id FROM invite WHERE inviter = @did)
|
||||||
|
SELECT discord_id FROM invited_user, invite WHERE invite.id = invited_user.invite_id
|
||||||
|
"""
|
||||||
|
|> Sql.executeAsync (fun read -> read.string "discord_id" |> uint64)
|
||||||
|
|> Async.AwaitTask
|
||||||
|
|
||||||
|
let createGuildInvite (ctx : IDiscordContext) =
|
||||||
|
task {
|
||||||
|
let channel = ctx.GetGuild().Channels.[GuildEnvironment.channelWelcome]
|
||||||
|
let! invite = channel.CreateInviteAsync(max_age = 86400, unique = true)
|
||||||
|
|
||||||
|
// When a player generates an invite code but it hasn't expired, it generates the same code, creating a duplicate entry
|
||||||
|
// so catch the exception thrown because the code column is unique
|
||||||
|
try
|
||||||
|
let! _ = createInvite (ctx.GetDiscordMember().Id) invite.Code
|
||||||
|
return ()
|
||||||
|
with ex ->
|
||||||
|
printfn "%A" ex.Message
|
||||||
|
()
|
||||||
|
|
||||||
|
let embed =
|
||||||
|
DiscordEmbedBuilder()
|
||||||
|
.WithDescription($"Use this invite link to earn invite points for future rewards.\nExpires in 1 day.
|
||||||
|
```https://discord.gg/{invite.Code}```")
|
||||||
|
.WithImageUrl("https://pbs.twimg.com/profile_banners/1449270642340089856/1640071520/1500x500")
|
||||||
|
.WithTitle("Invite Link")
|
||||||
|
|
||||||
|
let msg =
|
||||||
|
DiscordInteractionResponseBuilder()
|
||||||
|
.AddEmbed(embed)
|
||||||
|
.AsEphemeral(true)
|
||||||
|
.WithContent($"https://discord.gg/{invite.Code}")
|
||||||
|
|
||||||
|
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
let listServerInvites (ctx : IDiscordContext) = task {
|
||||||
|
let! invites = ctx.GetGuild().GetInvitesAsync()
|
||||||
|
let sb = StringBuilder()
|
||||||
|
for invite in invites do
|
||||||
|
sb.AppendLine($"{invite.Inviter.Username} - {invite.Code}") |> ignore
|
||||||
|
let msg =
|
||||||
|
DiscordInteractionResponseBuilder()
|
||||||
|
.AsEphemeral(true)
|
||||||
|
.WithContent("Server Invites\n" + sb.ToString())
|
||||||
|
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
let getAttributions (ctx : IDiscordContext) userId = task {
|
||||||
|
let! total = getInviteAttributions(userId)
|
||||||
|
let msg =
|
||||||
|
DiscordInteractionResponseBuilder()
|
||||||
|
.AsEphemeral(true)
|
||||||
|
.WithContent($"<@{userId}> has invited {total} people")
|
||||||
|
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
let getInvitedUsersForId (ctx : IDiscordContext) userId = task {
|
||||||
|
let! users = getInvitedUsers(userId)
|
||||||
|
let sb = StringBuilder()
|
||||||
|
for user in users do
|
||||||
|
sb.AppendLine($"<@{user}>") |> ignore
|
||||||
|
let msg =
|
||||||
|
DiscordInteractionResponseBuilder()
|
||||||
|
.AsEphemeral(true)
|
||||||
|
.WithContent($"<@{userId}> has invited the following people:\n{sb}")
|
||||||
|
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
let clearInvites (ctx : IDiscordContext) = task {
|
||||||
|
let! invites = ctx.GetGuild().GetInvitesAsync()
|
||||||
|
do!
|
||||||
|
invites
|
||||||
|
|> Seq.map (fun invite -> invite.DeleteAsync() |> Async.AwaitTask)
|
||||||
|
|> Async.Parallel
|
||||||
|
|> Async.Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
let handleGuildMemberAdded _ (eventArgs : GuildMemberAddEventArgs) =
|
||||||
|
task {
|
||||||
|
let! guildInvites = eventArgs.Guild.GetInvitesAsync()
|
||||||
|
let! cachedInvites = getInvites()
|
||||||
|
for invite in guildInvites do
|
||||||
|
let result = cachedInvites.TryFind(invite.Code)
|
||||||
|
match result with
|
||||||
|
| Some (_,count) ->
|
||||||
|
if invite.Uses > count then
|
||||||
|
do! addInvitedUser eventArgs.Member.Id invite.Code invite.Uses |> Async.Ignore
|
||||||
|
| None -> ()
|
||||||
|
} :> Task
|
||||||
|
|
||||||
|
let handleGuildMemberRemoved _ (eventArgs : GuildMemberRemoveEventArgs) =
|
||||||
|
task {
|
||||||
|
do! removeInvitedUser eventArgs.Member.Id
|
||||||
|
} :> Task
|
||||||
|
|
||||||
|
type Inviter() =
|
||||||
|
inherit ApplicationCommandModule ()
|
||||||
|
|
||||||
|
[<SlashCommand("invite-create", "Invite user to this discord and earn rewards")>]
|
||||||
|
member this.CreateInvite (ctx : InteractionContext) =
|
||||||
|
createGuildInvite (DiscordInteractionContext ctx)
|
||||||
|
|
||||||
|
[<SlashCommand("invites-list", "List all the invites")>]
|
||||||
|
member this.ListServerInvites (ctx : InteractionContext) =
|
||||||
|
listServerInvites (DiscordInteractionContext ctx)
|
||||||
|
|
||||||
|
[<SlashCommand("invites-attributions", "Get total invites from a specific user")>]
|
||||||
|
member this.getAttributions (ctx : InteractionContext, [<Option("player", "The player you want to check")>] user : DiscordUser) =
|
||||||
|
getAttributions (DiscordInteractionContext ctx) user.Id
|
||||||
|
|
||||||
|
[<SlashCommand("invited-people", "Get total invites from a specific user")>]
|
||||||
|
member this.ListInvitedPeople (ctx : InteractionContext, [<Option("player", "The player you want to check")>] user : DiscordUser) =
|
||||||
|
getInvitedUsersForId (DiscordInteractionContext ctx) user.Id
|
||||||
|
|
||||||
|
[<SlashCommand("invites-clear", "Get total invites from a specific user")>]
|
||||||
|
member this.ClearInvites (ctx : InteractionContext) =
|
||||||
|
clearInvites (DiscordInteractionContext ctx)
|
||||||
|
|
269
Bot/Items.json
269
Bot/Items.json
@ -1,92 +1,205 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"Id": 0,
|
"Case": "Hack",
|
||||||
"Name": "Virus",
|
"Fields": [
|
||||||
"Type": 0,
|
{
|
||||||
"Price": 0,
|
"Power": 25,
|
||||||
"Power": 25,
|
"Class": 0,
|
||||||
"Cooldown": 1,
|
"Cooldown": 1,
|
||||||
"Class": 0,
|
"Item": {
|
||||||
"Attributes": {
|
"Id": 0,
|
||||||
"Sell": false,
|
"Name": "Virus",
|
||||||
"Buy": false,
|
"Price": 0
|
||||||
"Consume": false,
|
}
|
||||||
"Drop": false
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Id": 1,
|
"Case": "Hack",
|
||||||
"Name": "RemoteAccess",
|
"Fields": [
|
||||||
"Type": 0,
|
{
|
||||||
"Price": 500,
|
"Power": 75,
|
||||||
"Power": 75,
|
"Class": 1,
|
||||||
"Cooldown": 3,
|
"Cooldown": 3,
|
||||||
"Class": 1,
|
"Item": {
|
||||||
"Attributes": {
|
"Id": 1,
|
||||||
"Sell": true,
|
"Name": "Remote Access",
|
||||||
"Buy": true,
|
"Price": 500
|
||||||
"Consume": false,
|
}
|
||||||
"Drop": true
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Id": 2,
|
"Case": "Hack",
|
||||||
"Name": "Worm",
|
"Fields": [
|
||||||
"Type": 0,
|
{
|
||||||
"Price": 5000,
|
"Power": 150,
|
||||||
"Power": 150,
|
"Class": 2,
|
||||||
"Cooldown": 5,
|
"Cooldown": 5,
|
||||||
"Class": 2,
|
"Item": {
|
||||||
"Attributes": {
|
"Id": 2,
|
||||||
"Sell": true,
|
"Name": "Worm",
|
||||||
"Buy": true,
|
"Price": 5000
|
||||||
"Consume": false,
|
}
|
||||||
"Drop": true
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Id": 6,
|
"Case": "Shield",
|
||||||
"Name": "Firewall",
|
"Fields": [
|
||||||
"Type": 1,
|
{
|
||||||
"Price": 0,
|
"Class": 0,
|
||||||
"Power": 10,
|
"Cooldown": 120,
|
||||||
"Class": 0,
|
"Item": {
|
||||||
"Cooldown": 120,
|
"Id": 6,
|
||||||
"Attributes": {
|
"Name": "Firewall",
|
||||||
"Sell": false,
|
"Price": 0
|
||||||
"Buy": false,
|
}
|
||||||
"Consume": false,
|
}
|
||||||
"Drop": false
|
]
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Id": 7,
|
"Case": "Shield",
|
||||||
"Name": "Encryption",
|
"Fields": [
|
||||||
"Type": 1,
|
{
|
||||||
"Price": 500,
|
"Class": 1,
|
||||||
"Power": 50,
|
"Cooldown": 240,
|
||||||
"Class": 1,
|
"Item": {
|
||||||
"Cooldown": 240,
|
"Id": 7,
|
||||||
"Attributes": {
|
"Name": "Encryption",
|
||||||
"Sell": true,
|
"Price": 500
|
||||||
"Buy": true,
|
}
|
||||||
"Consume": false,
|
}
|
||||||
"Drop": true
|
]
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Id": 8,
|
"Case": "Shield",
|
||||||
"Name": "Cypher",
|
"Fields": [
|
||||||
"Type": 1,
|
{
|
||||||
"Price": 5000,
|
"Class": 2,
|
||||||
"Power": 80,
|
"Cooldown": 360,
|
||||||
"Class": 2,
|
"Item": {
|
||||||
"Cooldown": 380,
|
"Id": 8,
|
||||||
"Attributes": {
|
"Name": "Cypher",
|
||||||
"Sell": true,
|
"Price": 5000
|
||||||
"Buy": true,
|
}
|
||||||
"Consume": false,
|
}
|
||||||
"Drop": true
|
]
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"Case": "Food",
|
||||||
|
"Fields": [
|
||||||
|
{
|
||||||
|
"TargetStat" : 0,
|
||||||
|
"BoostAmount" : 30,
|
||||||
|
"Item": {
|
||||||
|
"Id": 12,
|
||||||
|
"Name": "Protein Powder",
|
||||||
|
"Price": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Case": "Food",
|
||||||
|
"Fields": [
|
||||||
|
{
|
||||||
|
"TargetStat" : 1,
|
||||||
|
"BoostAmount" : 30,
|
||||||
|
"Item": {
|
||||||
|
"Id": 13,
|
||||||
|
"Name": "Toro Loco",
|
||||||
|
"Price": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Case": "Food",
|
||||||
|
"Fields": [
|
||||||
|
{
|
||||||
|
"TargetStat" : 2,
|
||||||
|
"BoostAmount" : 30,
|
||||||
|
"Item": {
|
||||||
|
"Id": 14,
|
||||||
|
"Name": "Oldports Cigs",
|
||||||
|
"Price": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Case": "Food",
|
||||||
|
"Fields": [
|
||||||
|
{
|
||||||
|
"TargetStat" : 3,
|
||||||
|
"BoostAmount" : 30,
|
||||||
|
"Item": {
|
||||||
|
"Id": 15,
|
||||||
|
"Name": "Moon Pie",
|
||||||
|
"Price": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Case": "Accessory",
|
||||||
|
"Fields": [
|
||||||
|
{
|
||||||
|
"TargetStat" : 0,
|
||||||
|
"FloorBoost" : 25,
|
||||||
|
"CeilBoost" : 0,
|
||||||
|
"Item": {
|
||||||
|
"Id": 20,
|
||||||
|
"Name": "Kettlebell",
|
||||||
|
"Price": 250
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Case": "Accessory",
|
||||||
|
"Fields": [
|
||||||
|
{
|
||||||
|
"TargetStat" : 1,
|
||||||
|
"FloorBoost" : 25,
|
||||||
|
"CeilBoost" : 0,
|
||||||
|
"Item": {
|
||||||
|
"Id": 21,
|
||||||
|
"Name": "Headphones",
|
||||||
|
"Price": 250
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Case": "Accessory",
|
||||||
|
"Fields": [
|
||||||
|
{
|
||||||
|
"TargetStat" : 2,
|
||||||
|
"FloorBoost" : 0,
|
||||||
|
"CeilBoost" : 25,
|
||||||
|
"Item": {
|
||||||
|
"Id": 22,
|
||||||
|
"Name": "Rolox Watch",
|
||||||
|
"Price": 250
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Case": "Accessory",
|
||||||
|
"Fields": [
|
||||||
|
{
|
||||||
|
"TargetStat" : 3,
|
||||||
|
"FloorBoost" : 0,
|
||||||
|
"CeilBoost" : 25,
|
||||||
|
"Item": {
|
||||||
|
"Id": 23,
|
||||||
|
"Name": "Buddha Keychain",
|
||||||
|
"Price": 250
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
137
Bot/Messaging.fs
Normal file
137
Bot/Messaging.fs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
module Degenz.Messaging
|
||||||
|
|
||||||
|
open System
|
||||||
|
open System.Threading.Tasks
|
||||||
|
open DSharpPlus
|
||||||
|
open DSharpPlus.Entities
|
||||||
|
open DSharpPlus.EventArgs
|
||||||
|
open DSharpPlus.SlashCommands
|
||||||
|
|
||||||
|
type InteractiveMessage = {
|
||||||
|
ButtonId : string
|
||||||
|
ButtonText : string
|
||||||
|
Message : string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscordContext =
|
||||||
|
| Interaction of InteractionContext
|
||||||
|
| Event of ComponentInteractionCreateEventArgs
|
||||||
|
|
||||||
|
type IDiscordContext =
|
||||||
|
abstract member Respond : InteractionResponseType -> Task
|
||||||
|
abstract member Respond : InteractionResponseType * DiscordInteractionResponseBuilder -> Task
|
||||||
|
abstract member FollowUp : DiscordFollowupMessageBuilder -> Task
|
||||||
|
abstract member GetDiscordMember : unit -> DiscordMember
|
||||||
|
abstract member GetGuild : unit -> DiscordGuild
|
||||||
|
abstract member GetInteractionId : unit -> string
|
||||||
|
abstract member GetChannel : unit -> DiscordChannel
|
||||||
|
abstract member GetContext : unit -> obj
|
||||||
|
|
||||||
|
type DiscordInteractionContext(ctx : InteractionContext) =
|
||||||
|
interface IDiscordContext with
|
||||||
|
member this.Respond responseType =
|
||||||
|
async {
|
||||||
|
do! ctx.Interaction.CreateResponseAsync(responseType) |> Async.AwaitTask
|
||||||
|
} |> Async.StartAsTask :> Task
|
||||||
|
member this.Respond (responseType, builder) =
|
||||||
|
async {
|
||||||
|
do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask
|
||||||
|
} |> Async.StartAsTask :> Task
|
||||||
|
member this.FollowUp(builder) =
|
||||||
|
async {
|
||||||
|
do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore
|
||||||
|
} |> Async.StartAsTask :> Task
|
||||||
|
member this.GetDiscordMember() = ctx.Member
|
||||||
|
member this.GetGuild() = ctx.Guild
|
||||||
|
member this.GetInteractionId() = string ctx.InteractionId
|
||||||
|
member this.GetChannel() = ctx.Channel
|
||||||
|
member this.GetContext() = ctx
|
||||||
|
|
||||||
|
type DiscordEventContext(ctx : ComponentInteractionCreateEventArgs) =
|
||||||
|
interface IDiscordContext with
|
||||||
|
member this.Respond responseType =
|
||||||
|
async {
|
||||||
|
do! ctx.Interaction.CreateResponseAsync(responseType) |> Async.AwaitTask
|
||||||
|
} |> Async.StartAsTask :> Task
|
||||||
|
member this.Respond (responseType, builder) =
|
||||||
|
async {
|
||||||
|
do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask
|
||||||
|
} |> Async.StartAsTask :> Task
|
||||||
|
member this.FollowUp(builder) =
|
||||||
|
async {
|
||||||
|
do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore
|
||||||
|
} |> Async.StartAsTask :> Task
|
||||||
|
member this.GetDiscordMember() = ctx.User :?> DiscordMember
|
||||||
|
member this.GetGuild() = ctx.Guild
|
||||||
|
member this.GetInteractionId() = ctx.Id
|
||||||
|
member this.GetChannel() = ctx.Channel
|
||||||
|
member this.GetContext() = ctx
|
||||||
|
|
||||||
|
let getTimeText isCooldown (timespan : TimeSpan) timestamp =
|
||||||
|
let span =
|
||||||
|
if isCooldown
|
||||||
|
then timespan - (DateTime.UtcNow - timestamp)
|
||||||
|
else (DateTime.UtcNow - timestamp)
|
||||||
|
let plural amount = if amount = 1 then "" else "s"
|
||||||
|
let ``and`` = if span.Hours > 0 then "and " else ""
|
||||||
|
let hours = if span.Hours > 0 then $"{span.Hours} hour{plural span.Hours} {``and``}" else String.Empty
|
||||||
|
let totalMins = span.Minutes
|
||||||
|
let minutes = if totalMins > 0 then $"{totalMins} minute{plural totalMins}" else "1 minute"
|
||||||
|
$"{hours}{minutes}"
|
||||||
|
|
||||||
|
let getShortTimeText (timespan : TimeSpan) timestamp =
|
||||||
|
let remaining = timespan - (DateTime.UtcNow - timestamp)
|
||||||
|
let hours = if remaining.Hours > 0 then $"{remaining.Hours}h " else String.Empty
|
||||||
|
let minutesRemaining = if remaining.Hours = 0 then remaining.Minutes + 1 else remaining.Minutes
|
||||||
|
$"{hours}{minutesRemaining}min"
|
||||||
|
|
||||||
|
let defer (ctx: IDiscordContext) = async {
|
||||||
|
let builder = DiscordInteractionResponseBuilder()
|
||||||
|
builder.IsEphemeral <- true
|
||||||
|
do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
|
||||||
|
}
|
||||||
|
|
||||||
|
let sendSimpleResponse (ctx: IDiscordContext) msg =
|
||||||
|
async {
|
||||||
|
let builder = DiscordInteractionResponseBuilder()
|
||||||
|
builder.Content <- msg
|
||||||
|
builder.AsEphemeral true |> ignore
|
||||||
|
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
|
||||||
|
}
|
||||||
|
|
||||||
|
let sendFollowUpMessage (ctx : IDiscordContext) msg =
|
||||||
|
async {
|
||||||
|
let builder = DiscordFollowupMessageBuilder()
|
||||||
|
builder.IsEphemeral <- true
|
||||||
|
builder.Content <- msg
|
||||||
|
do! ctx.FollowUp(builder) |> Async.AwaitTask
|
||||||
|
}
|
||||||
|
|
||||||
|
let sendFollowUpEmbed (ctx : IDiscordContext) embed =
|
||||||
|
async {
|
||||||
|
let builder =
|
||||||
|
DiscordFollowupMessageBuilder()
|
||||||
|
.AsEphemeral(true)
|
||||||
|
.AddEmbed(embed)
|
||||||
|
do! ctx.FollowUp(builder) |> Async.AwaitTask
|
||||||
|
}
|
||||||
|
|
||||||
|
let sendFollowUpMessageWithButton (ctx : IDiscordContext) interactiveMessage =
|
||||||
|
async {
|
||||||
|
let builder = DiscordFollowupMessageBuilder()
|
||||||
|
let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText) :> DiscordComponent
|
||||||
|
builder.AddComponents [| button |] |> ignore
|
||||||
|
builder.IsEphemeral <- true
|
||||||
|
builder.Content <- interactiveMessage.Message
|
||||||
|
do! ctx.FollowUp(builder) |> Async.AwaitTask
|
||||||
|
}
|
||||||
|
|
||||||
|
let updateMessageWithGreyedOutButtons (ctx : IDiscordContext) interactiveMessage =
|
||||||
|
async {
|
||||||
|
let builder = DiscordInteractionResponseBuilder()
|
||||||
|
let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText, true) :> DiscordComponent
|
||||||
|
builder.AddComponents [| button |] |> ignore
|
||||||
|
builder.IsEphemeral <- true
|
||||||
|
builder.Content <- interactiveMessage.Message
|
||||||
|
do! ctx.Respond(InteractionResponseType.UpdateMessage, builder) |> Async.AwaitTask
|
||||||
|
}
|
@ -1,75 +1,58 @@
|
|||||||
module Degenz.PlayerInteractions
|
module Degenz.PlayerInteractions
|
||||||
|
|
||||||
open System.Threading.Tasks
|
open System.Threading.Tasks
|
||||||
open DSharpPlus.SlashCommands
|
open DSharpPlus
|
||||||
open Degenz.Types
|
open DSharpPlus.Entities
|
||||||
|
open Degenz.Messaging
|
||||||
|
open Degenz.DbService
|
||||||
|
|
||||||
module Commands =
|
let executePlayerAction (ctx : IDiscordContext) (dispatch : PlayerData -> Async<unit>) =
|
||||||
let newPlayer nickname (membr : uint64) =
|
async {
|
||||||
let rand = System.Random(System.Guid.NewGuid().GetHashCode())
|
let builder = DiscordInteractionResponseBuilder().AsEphemeral(true)
|
||||||
let randHack = rand.Next(0, 3)
|
do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
|
||||||
let randShield = rand.Next(6, 9)
|
let! playerResult = tryFindPlayer (ctx.GetDiscordMember().Id)
|
||||||
let hack = Armory.battleItems |> Array.find (fun i -> i.Id = randHack)
|
match playerResult with
|
||||||
let shield = Armory.battleItems |> Array.find (fun i -> i.Id = randShield)
|
| Some player -> do! dispatch player
|
||||||
|
| None -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
|
||||||
|
} |> Async.StartAsTask :> Task
|
||||||
|
|
||||||
{ DiscordId = membr
|
let executePlayerActionWithTarget (targetPlayer : DiscordUser) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async<unit>) =
|
||||||
Name = nickname
|
async {
|
||||||
Inventory = [| hack ; shield |]
|
let builder = DiscordInteractionResponseBuilder()
|
||||||
Events = [||]
|
builder.IsEphemeral <- true
|
||||||
// XP = 0
|
builder.Content <- "Content"
|
||||||
// Achievements = [||]
|
do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
|
||||||
Traits = PlayerTraits.empty
|
let! players =
|
||||||
Bank = 100<GBT> }
|
[ tryFindPlayer (ctx.GetDiscordMember().Id)
|
||||||
|
tryFindPlayer targetPlayer.Id ]
|
||||||
let upsertPlayer discordId =
|
|> Async.Parallel
|
||||||
async {
|
match players.[0] , players.[1] with
|
||||||
let! player = DbService.tryFindPlayer GuildEnvironment.pgDb discordId
|
| Some player , Some target -> do! dispatch player target
|
||||||
let! newPlayer =
|
| None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
|
||||||
match player with
|
| _ , None ->
|
||||||
| Some _ -> async.Return false
|
if targetPlayer.IsBot
|
||||||
| None ->
|
then do! Messaging.sendFollowUpMessage ctx $"{targetPlayer.Username} is a bot, pick a real human to hack"
|
||||||
async {
|
else do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command"
|
||||||
// do! newPlayer "" discordId |> DbService.insertNewPlayer
|
} |> Async.StartAsTask :> Task
|
||||||
return true
|
|
||||||
}
|
|
||||||
return newPlayer
|
|
||||||
}
|
|
||||||
|
|
||||||
[<CLIMutable>]
|
|
||||||
type LeaderboardEntry = {
|
|
||||||
Position : string
|
|
||||||
Amount : string
|
|
||||||
Name : string
|
|
||||||
}
|
|
||||||
|
|
||||||
// let leaderboard (ctx : InteractionContext) =
|
|
||||||
// async {
|
|
||||||
// do! ctx.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource) |> Async.AwaitTask
|
|
||||||
//
|
|
||||||
// let builder = DiscordFollowupMessageBuilder()
|
|
||||||
// builder.IsEphemeral <- true
|
|
||||||
//
|
|
||||||
// let! leaders = DbService.getTopPlayers 10
|
|
||||||
// let content =
|
|
||||||
// leaders
|
|
||||||
// |> Seq.toArray
|
|
||||||
// |> Array.sortByDescending (fun p -> p.Bank)
|
|
||||||
// |> Array.mapi (fun i p -> { Position = string (i + 1) ; Amount = string p.Bank ; Name = p.Name })
|
|
||||||
// |> Formatter.Format
|
|
||||||
// builder.Content <- if not <| String.IsNullOrEmpty content then $"```{content}```" else "There are no active hackers"
|
|
||||||
// do! ctx.Interaction.CreateFollowupMessageAsync(builder)
|
|
||||||
// |> Async.AwaitTask
|
|
||||||
// |> Async.Ignore
|
|
||||||
// } |> Async.StartAsTask
|
|
||||||
// :> Task
|
|
||||||
|
|
||||||
type PlayerInteractions() =
|
|
||||||
inherit ApplicationCommandModule ()
|
|
||||||
|
|
||||||
[<SlashCommand("redpill", "Take the redpill and become a hacker")>]
|
|
||||||
member _.AddHackerRole (ctx : InteractionContext) = Commands.upsertPlayer ctx.Member.Id
|
|
||||||
|
|
||||||
// [<SlashCommand("leaderboard", "View the current list of players ranked by highest earnings")>]
|
|
||||||
// member this.Leaderboard (ctx : InteractionContext) = Commands.leaderboard ctx
|
|
||||||
|
|
||||||
|
let executePlayerActionWithTargetId defer (targetId : uint64) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async<unit>) =
|
||||||
|
async {
|
||||||
|
let builder = DiscordInteractionResponseBuilder()
|
||||||
|
builder.IsEphemeral <- true
|
||||||
|
builder.Content <- "Content"
|
||||||
|
if defer then
|
||||||
|
do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
|
||||||
|
let! players =
|
||||||
|
[ tryFindPlayer (ctx.GetDiscordMember().Id)
|
||||||
|
tryFindPlayer targetId ]
|
||||||
|
|> Async.Parallel
|
||||||
|
match players.[0] , players.[1] with
|
||||||
|
| Some player , Some target -> do! dispatch player target
|
||||||
|
| None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
|
||||||
|
| _ , None -> do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command"
|
||||||
|
} |> Async.StartAsTask :> Task
|
||||||
|
|
||||||
|
let handleResultWithResponse ctx fn (player : Result<PlayerData, string>) =
|
||||||
|
match player with
|
||||||
|
| Ok p -> fn p
|
||||||
|
| Error e -> async { do! Messaging.sendFollowUpMessage ctx e }
|
||||||
|
22
Bot/Prelude.fs
Normal file
22
Bot/Prelude.fs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
namespace Degenz
|
||||||
|
|
||||||
|
[<Microsoft.FSharp.Core.AutoOpen>]
|
||||||
|
module ResultHelpers =
|
||||||
|
let (>>=) x f = Result.bind f x
|
||||||
|
let (<!>) x f = Result.map f x
|
||||||
|
|
||||||
|
[<Microsoft.FSharp.Core.AutoOpen>]
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module List =
|
||||||
|
let cons xs x = x :: xs
|
||||||
|
let consTo x xs = x :: xs
|
||||||
|
|
||||||
|
let rec foldk f (acc:'TState) xs =
|
||||||
|
match xs with
|
||||||
|
| [] -> acc
|
||||||
|
| x::xs -> f acc x (fun lacc -> foldk f lacc xs)
|
||||||
|
|
||||||
|
let foldi (f : int -> 'Acc -> 'elem -> 'Acc) (acc : 'Acc) xs =
|
||||||
|
let f' ( i , st ) acc = ( i + 1 , f i st acc )
|
||||||
|
List.fold f' ( 0 , acc ) xs |> snd
|
||||||
|
|
120
Bot/Store.fs
120
Bot/Store.fs
@ -1,120 +0,0 @@
|
|||||||
module Degenz.Store
|
|
||||||
|
|
||||||
open System.Threading.Tasks
|
|
||||||
open DSharpPlus.Entities
|
|
||||||
open DSharpPlus
|
|
||||||
open DSharpPlus.EventArgs
|
|
||||||
open DSharpPlus.SlashCommands
|
|
||||||
open Degenz
|
|
||||||
open Degenz.Messaging
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
let checkAlreadyOwnsItem (item : Item) player =
|
|
||||||
if player.Inventory |> Array.exists (fun w -> item.Id = w.Id)
|
|
||||||
then Error $"You already own {item.Name}!"
|
|
||||||
else Ok player
|
|
||||||
|
|
||||||
let checkSoldItemAlready (item : Item) player =
|
|
||||||
if player.Inventory |> Array.exists (fun w -> item.Id = w.Id)
|
|
||||||
then Ok player
|
|
||||||
else Error $"{item.Name} not found in your arsenal! Looks like you sold it already."
|
|
||||||
|
|
||||||
let checkHasItemsInArsenal itemType player =
|
|
||||||
if player.Inventory |> Array.filter (fun i -> i.Type = itemType ) |> Array.length > 0
|
|
||||||
then Ok player
|
|
||||||
else Error $"You currently have no {itemType}s in your arsenal to sell!"
|
|
||||||
|
|
||||||
let buy itemType (ctx : IDiscordContext) =
|
|
||||||
Game.executePlayerAction ctx (fun player -> async {
|
|
||||||
let itemStore = Embeds.getBuyItemsEmbed player itemType Armory.battleItems
|
|
||||||
do! ctx.FollowUp itemStore |> Async.AwaitTask
|
|
||||||
})
|
|
||||||
|
|
||||||
let sell itemType (ctx : IDiscordContext) =
|
|
||||||
Game.executePlayerAction ctx (fun player -> async {
|
|
||||||
match checkHasItemsInArsenal itemType player with
|
|
||||||
| Ok _ -> let itemStore = Embeds.getSellEmbed itemType player
|
|
||||||
do! ctx.FollowUp(itemStore) |> Async.AwaitTask
|
|
||||||
| Error e -> do! sendFollowUpMessage ctx e
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: When you buy a shield, prompt the user to activate it
|
|
||||||
let handleBuyItem (ctx : IDiscordContext) itemId =
|
|
||||||
Game.executePlayerAction ctx (fun player -> async {
|
|
||||||
let item = Armory.battleItems |> Array.find (fun w -> w.Id = itemId)
|
|
||||||
do! player
|
|
||||||
|> checkHasSufficientFunds item
|
|
||||||
>>= checkAlreadyOwnsItem item
|
|
||||||
|> handleResultWithResponse ctx (fun player -> async {
|
|
||||||
let newBalance = player.Bank - item.Price
|
|
||||||
let p = { player with Bank = newBalance ; Inventory = Array.append [| item |] player.Inventory }
|
|
||||||
do! DbService.updatePlayer GuildEnvironment.pgDb p |> Async.Ignore
|
|
||||||
do! sendFollowUpMessage ctx $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
let handleSell (ctx : IDiscordContext) itemId =
|
|
||||||
Game.executePlayerAction ctx (fun player -> async {
|
|
||||||
let item = Armory.getItem itemId
|
|
||||||
do!
|
|
||||||
player
|
|
||||||
|> checkSoldItemAlready item
|
|
||||||
|> handleResultWithResponse ctx (fun player -> async {
|
|
||||||
let updatedPlayer = {
|
|
||||||
player with
|
|
||||||
Bank = player.Bank + item.Price
|
|
||||||
Inventory = player.Inventory |> Array.filter (fun i -> i.Id <> itemId)
|
|
||||||
}
|
|
||||||
do!
|
|
||||||
[ DbService.updatePlayer GuildEnvironment.pgDb updatedPlayer |> Async.Ignore
|
|
||||||
DbService.removeShieldEvent GuildEnvironment.pgDb updatedPlayer.DiscordId itemId |> Async.Ignore
|
|
||||||
sendFollowUpMessage ctx $"Sold {item.Type} {item.Name} for {item.Price}! Current Balance: {updatedPlayer.Bank}" ]
|
|
||||||
|> Async.Parallel
|
|
||||||
|> Async.Ignore
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) =
|
|
||||||
let ctx = DiscordEventContext event :> IDiscordContext
|
|
||||||
let id = ctx.GetInteractionId()
|
|
||||||
let itemId = int <| id.Split("-").[1]
|
|
||||||
match id with
|
|
||||||
| id when id.StartsWith("Buy") -> handleBuyItem ctx itemId
|
|
||||||
| id when id.StartsWith("Sell") -> handleSell ctx itemId
|
|
||||||
| _ ->
|
|
||||||
task {
|
|
||||||
let builder = DiscordInteractionResponseBuilder()
|
|
||||||
builder.IsEphemeral <- true
|
|
||||||
builder.Content <- $"Incorrect Action identifier {id}"
|
|
||||||
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
|
|
||||||
}
|
|
||||||
|
|
||||||
type Store() =
|
|
||||||
inherit ApplicationCommandModule ()
|
|
||||||
|
|
||||||
let enforceChannel (ctx : IDiscordContext) (storeFn : IDiscordContext -> Task) =
|
|
||||||
match ctx.GetChannel().Id with
|
|
||||||
| id when id = GuildEnvironment.channelArmory -> storeFn ctx
|
|
||||||
| _ ->
|
|
||||||
task {
|
|
||||||
let msg = $"You must go to <#{GuildEnvironment.channelArmory}> channel to buy or sell weapons"
|
|
||||||
do! Messaging.sendSimpleResponse ctx msg
|
|
||||||
}
|
|
||||||
|
|
||||||
[<SlashCommand("buy-hack", "Purchase a hack attack you can use to earn GoodBoyTokenz")>]
|
|
||||||
member _.BuyHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy ItemType.Hack)
|
|
||||||
|
|
||||||
[<SlashCommand("buy-shield", "Purchase a hack shield so you can protect your GoodBoyTokenz")>]
|
|
||||||
member this.BuyShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (buy ItemType.Shield)
|
|
||||||
|
|
||||||
[<SlashCommand("sell-hack", "Sell a hack for GoodBoyTokenz")>]
|
|
||||||
member this.SellHack (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell ItemType.Hack)
|
|
||||||
|
|
||||||
[<SlashCommand("sell-shield", "Sell a shield for GoodBoyTokenz")>]
|
|
||||||
member this.SellShield (ctx : InteractionContext) = enforceChannel (DiscordInteractionContext(ctx)) (sell ItemType.Shield)
|
|
||||||
|
|
@ -6,7 +6,7 @@ open DSharpPlus.Entities
|
|||||||
|
|
||||||
type RewardType =
|
type RewardType =
|
||||||
| Currency of int<GBT>
|
| Currency of int<GBT>
|
||||||
| RandomItem of itemType : ItemType * amount : int
|
// | RandomItem of itemType : ItemType * amount : int
|
||||||
| SpecialItem of id : int
|
| SpecialItem of id : int
|
||||||
|
|
||||||
[<Literal>]
|
[<Literal>]
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="DbService.fs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Shared\Shared.fsproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="..\.paket\Paket.Restore.targets" />
|
|
||||||
</Project>
|
|
@ -1,8 +0,0 @@
|
|||||||
FSharp.Core
|
|
||||||
DSharpPlus
|
|
||||||
// DSharpPlus.CommandsNext
|
|
||||||
// DSharpPlus.Interactivity
|
|
||||||
DSharpPlus.SlashCommands
|
|
||||||
|
|
||||||
MongoDB.Driver
|
|
||||||
Npgsql.FSharp
|
|
@ -5,10 +5,6 @@ VisualStudioVersion = 16.0.30114.105
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Bot", "Bot\Bot.fsproj", "{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}"
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Bot", "Bot\Bot.fsproj", "{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "DbService", "DbService\DbService.fsproj", "{B1D3E1CC-451C-42D4-B054-D64E75E1A3B9}"
|
|
||||||
EndProject
|
|
||||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Shared", "Shared\Shared.fsproj", "{BD80E85E-87C8-4F5F-941E-7DCAF9D69838}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -22,13 +18,5 @@ Global
|
|||||||
{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}.Release|Any CPU.Build.0 = Release|Any CPU
|
{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{B1D3E1CC-451C-42D4-B054-D64E75E1A3B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{B1D3E1CC-451C-42D4-B054-D64E75E1A3B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{B1D3E1CC-451C-42D4-B054-D64E75E1A3B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{B1D3E1CC-451C-42D4-B054-D64E75E1A3B9}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{BD80E85E-87C8-4F5F-941E-7DCAF9D69838}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{BD80E85E-87C8-4F5F-941E-7DCAF9D69838}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{BD80E85E-87C8-4F5F-941E-7DCAF9D69838}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{BD80E85E-87C8-4F5F-941E-7DCAF9D69838}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
283
Shared/Shared.fs
283
Shared/Shared.fs
@ -1,283 +0,0 @@
|
|||||||
namespace Degenz
|
|
||||||
|
|
||||||
open System
|
|
||||||
open System.Threading.Tasks
|
|
||||||
open DSharpPlus
|
|
||||||
open DSharpPlus.Entities
|
|
||||||
open DSharpPlus.EventArgs
|
|
||||||
open DSharpPlus.SlashCommands
|
|
||||||
open Newtonsoft.Json
|
|
||||||
|
|
||||||
[<Microsoft.FSharp.Core.AutoOpen>]
|
|
||||||
module ResultHelpers =
|
|
||||||
let (>>=) x f = Result.bind f x
|
|
||||||
let (<!>) x f = Result.map f x
|
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
|
||||||
module List =
|
|
||||||
let cons xs x = x :: xs
|
|
||||||
let consTo x xs = x :: xs
|
|
||||||
|
|
||||||
let rec foldk f (acc:'TState) xs =
|
|
||||||
match xs with
|
|
||||||
| [] -> acc
|
|
||||||
| x::xs -> f acc x (fun lacc -> foldk f lacc xs)
|
|
||||||
|
|
||||||
let foldi (f : int -> 'Acc -> 'elem -> 'Acc) (acc : 'Acc) xs =
|
|
||||||
let f' ( i , st ) acc = ( i + 1 , f i st acc )
|
|
||||||
List.fold f' ( 0 , acc ) xs |> snd
|
|
||||||
|
|
||||||
[<Microsoft.FSharp.Core.AutoOpen>]
|
|
||||||
module Types =
|
|
||||||
|
|
||||||
[<Measure>]
|
|
||||||
type mins
|
|
||||||
|
|
||||||
[<Measure>]
|
|
||||||
type GBT
|
|
||||||
|
|
||||||
type HackId =
|
|
||||||
| Virus = 0
|
|
||||||
| RemoteAccess = 1
|
|
||||||
| Worm = 2
|
|
||||||
|
|
||||||
type ShieldId =
|
|
||||||
| Firewall = 6
|
|
||||||
| Encryption = 7
|
|
||||||
| Cypher = 8
|
|
||||||
|
|
||||||
type ItemType =
|
|
||||||
| Hack = 0
|
|
||||||
| Shield = 1
|
|
||||||
| Food = 1
|
|
||||||
|
|
||||||
type ItemAttributes = {
|
|
||||||
Sell : bool
|
|
||||||
Buy : bool
|
|
||||||
Consume : bool
|
|
||||||
Drop : bool
|
|
||||||
}
|
|
||||||
with static member empty = { Sell = false ; Buy = false ; Consume = false ; Drop = false }
|
|
||||||
|
|
||||||
type Item = {
|
|
||||||
Id : int
|
|
||||||
Name : string
|
|
||||||
Price : int<GBT>
|
|
||||||
Type : ItemType
|
|
||||||
Power : int
|
|
||||||
Class : int
|
|
||||||
Cooldown : int<mins>
|
|
||||||
Attributes : ItemAttributes
|
|
||||||
}
|
|
||||||
with static member empty =
|
|
||||||
{ Id = -1
|
|
||||||
Name = "None"
|
|
||||||
Price = 0<GBT>
|
|
||||||
Type = ItemType.Hack
|
|
||||||
Power = 0
|
|
||||||
Class = -1
|
|
||||||
Cooldown = 0<mins>
|
|
||||||
Attributes = ItemAttributes.empty }
|
|
||||||
|
|
||||||
type HackResult =
|
|
||||||
| Strong
|
|
||||||
| Weak
|
|
||||||
|
|
||||||
[<CLIMutable>]
|
|
||||||
type DiscordPlayer = { Id: uint64; Name: string }
|
|
||||||
with static member empty = { Id = 0uL ; Name = "None" }
|
|
||||||
|
|
||||||
type HackEvent = {
|
|
||||||
IsInstigator : bool
|
|
||||||
Adversary : DiscordPlayer
|
|
||||||
Success : bool
|
|
||||||
HackId : int
|
|
||||||
}
|
|
||||||
|
|
||||||
type PlayerEventType =
|
|
||||||
| Hacking of HackEvent
|
|
||||||
| Shielding of shieldId : int
|
|
||||||
| Stealing of instigator : bool * adversary : DiscordPlayer
|
|
||||||
| Imprison
|
|
||||||
|
|
||||||
type PlayerEvent =
|
|
||||||
{ Type : PlayerEventType
|
|
||||||
Cooldown : int<mins>
|
|
||||||
Timestamp : DateTime }
|
|
||||||
|
|
||||||
type PlayerTraits = {
|
|
||||||
Strength : int
|
|
||||||
Focus : int
|
|
||||||
Luck : int
|
|
||||||
Charisma : int
|
|
||||||
}
|
|
||||||
with static member empty = { Strength = 0 ; Focus = 0 ; Luck = 0 ; Charisma = 0 }
|
|
||||||
|
|
||||||
[<CLIMutable>]
|
|
||||||
type PlayerData = {
|
|
||||||
DiscordId : uint64
|
|
||||||
Name : string
|
|
||||||
Inventory : Item array
|
|
||||||
Events : PlayerEvent array
|
|
||||||
Traits : PlayerTraits
|
|
||||||
Bank : int<GBT>
|
|
||||||
}
|
|
||||||
// Achievements : string array
|
|
||||||
// XP : int
|
|
||||||
with member this.basicPlayer = { Id = this.DiscordId ; Name = this.Name }
|
|
||||||
static member empty =
|
|
||||||
{ DiscordId = 0uL
|
|
||||||
Name = "None"
|
|
||||||
Inventory = [||]
|
|
||||||
Events = [||]
|
|
||||||
Traits = PlayerTraits.empty
|
|
||||||
// Achievements = [||]
|
|
||||||
// XP = 0
|
|
||||||
Bank = 0<GBT> }
|
|
||||||
|
|
||||||
module Armory =
|
|
||||||
let battleItems =
|
|
||||||
let file = System.IO.File.ReadAllText("Items.json")
|
|
||||||
// let file = System.IO.File.ReadAllText("Bot/Items.json")
|
|
||||||
JsonConvert.DeserializeObject<Item array>(file)
|
|
||||||
|
|
||||||
let hacks = battleItems |> Array.filter (fun bi -> match bi.Type with ItemType.Hack -> true | _ -> false)
|
|
||||||
let shields = battleItems |> Array.filter (fun bi -> match bi.Type with ItemType.Shield -> true | _ -> false)
|
|
||||||
|
|
||||||
let getItem itemId = battleItems |> Array.find (fun w -> w.Id = itemId)
|
|
||||||
|
|
||||||
module Messaging =
|
|
||||||
type InteractiveMessage = {
|
|
||||||
ButtonId : string
|
|
||||||
ButtonText : string
|
|
||||||
Message : string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DiscordContext =
|
|
||||||
| Interaction of InteractionContext
|
|
||||||
| Event of ComponentInteractionCreateEventArgs
|
|
||||||
|
|
||||||
type IDiscordContext =
|
|
||||||
abstract member Respond : InteractionResponseType -> Task
|
|
||||||
abstract member Respond : InteractionResponseType * DiscordInteractionResponseBuilder -> Task
|
|
||||||
abstract member FollowUp : DiscordFollowupMessageBuilder -> Task
|
|
||||||
abstract member GetDiscordMember : unit -> DiscordMember
|
|
||||||
abstract member GetGuild : unit -> DiscordGuild
|
|
||||||
abstract member GetInteractionId : unit -> string
|
|
||||||
abstract member GetChannel : unit -> DiscordChannel
|
|
||||||
abstract member GetContext : unit -> obj
|
|
||||||
|
|
||||||
type DiscordInteractionContext(ctx : InteractionContext) =
|
|
||||||
interface IDiscordContext with
|
|
||||||
member this.Respond responseType =
|
|
||||||
async {
|
|
||||||
do! ctx.Interaction.CreateResponseAsync(responseType) |> Async.AwaitTask
|
|
||||||
} |> Async.StartAsTask :> Task
|
|
||||||
member this.Respond (responseType, builder) =
|
|
||||||
async {
|
|
||||||
do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask
|
|
||||||
} |> Async.StartAsTask :> Task
|
|
||||||
member this.FollowUp(builder) =
|
|
||||||
async {
|
|
||||||
do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore
|
|
||||||
} |> Async.StartAsTask :> Task
|
|
||||||
member this.GetDiscordMember() = ctx.Member
|
|
||||||
member this.GetGuild() = ctx.Guild
|
|
||||||
member this.GetInteractionId() = string ctx.InteractionId
|
|
||||||
member this.GetChannel() = ctx.Channel
|
|
||||||
member this.GetContext() = ctx
|
|
||||||
|
|
||||||
type DiscordEventContext(ctx : ComponentInteractionCreateEventArgs) =
|
|
||||||
interface IDiscordContext with
|
|
||||||
member this.Respond responseType =
|
|
||||||
async {
|
|
||||||
do! ctx.Interaction.CreateResponseAsync(responseType) |> Async.AwaitTask
|
|
||||||
} |> Async.StartAsTask :> Task
|
|
||||||
member this.Respond (responseType, builder) =
|
|
||||||
async {
|
|
||||||
do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask
|
|
||||||
} |> Async.StartAsTask :> Task
|
|
||||||
member this.FollowUp(builder) =
|
|
||||||
async {
|
|
||||||
do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore
|
|
||||||
} |> Async.StartAsTask :> Task
|
|
||||||
member this.GetDiscordMember() = ctx.User :?> DiscordMember
|
|
||||||
member this.GetGuild() = ctx.Guild
|
|
||||||
member this.GetInteractionId() = ctx.Id
|
|
||||||
member this.GetChannel() = ctx.Channel
|
|
||||||
member this.GetContext() = ctx
|
|
||||||
|
|
||||||
let getTimeText isCooldown (timespan : TimeSpan) timestamp =
|
|
||||||
let span =
|
|
||||||
if isCooldown
|
|
||||||
then timespan - (DateTime.UtcNow - timestamp)
|
|
||||||
else (DateTime.UtcNow - timestamp)
|
|
||||||
let plural amount = if amount = 1 then "" else "s"
|
|
||||||
let ``and`` = if span.Hours > 0 then "and " else ""
|
|
||||||
let hours = if span.Hours > 0 then $"{span.Hours} hour{plural span.Hours} {``and``}" else String.Empty
|
|
||||||
let totalMins = span.Minutes
|
|
||||||
let minutes = if totalMins > 0 then $"{totalMins} minute{plural totalMins}" else "1 minute"
|
|
||||||
$"{hours}{minutes}"
|
|
||||||
|
|
||||||
let getShortTimeText (timespan : TimeSpan) timestamp =
|
|
||||||
let remaining = timespan - (DateTime.UtcNow - timestamp)
|
|
||||||
let hours = if remaining.Hours > 0 then $"{remaining.Hours}h " else String.Empty
|
|
||||||
let minutesRemaining = if remaining.Hours = 0 then remaining.Minutes + 1 else remaining.Minutes
|
|
||||||
$"{hours}{minutesRemaining}min"
|
|
||||||
|
|
||||||
let defer (ctx: IDiscordContext) = async {
|
|
||||||
let builder = DiscordInteractionResponseBuilder()
|
|
||||||
builder.IsEphemeral <- true
|
|
||||||
do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
|
|
||||||
}
|
|
||||||
|
|
||||||
let sendSimpleResponse (ctx: IDiscordContext) msg =
|
|
||||||
async {
|
|
||||||
let builder = DiscordInteractionResponseBuilder()
|
|
||||||
builder.Content <- msg
|
|
||||||
builder.AsEphemeral true |> ignore
|
|
||||||
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
|
|
||||||
}
|
|
||||||
|
|
||||||
let sendFollowUpMessage (ctx : IDiscordContext) msg =
|
|
||||||
async {
|
|
||||||
let builder = DiscordFollowupMessageBuilder()
|
|
||||||
builder.IsEphemeral <- true
|
|
||||||
builder.Content <- msg
|
|
||||||
do! ctx.FollowUp(builder) |> Async.AwaitTask
|
|
||||||
}
|
|
||||||
|
|
||||||
let sendFollowUpEmbed (ctx : IDiscordContext) embed =
|
|
||||||
async {
|
|
||||||
let builder =
|
|
||||||
DiscordFollowupMessageBuilder()
|
|
||||||
.AsEphemeral(true)
|
|
||||||
.AddEmbed(embed)
|
|
||||||
do! ctx.FollowUp(builder) |> Async.AwaitTask
|
|
||||||
}
|
|
||||||
|
|
||||||
let sendFollowUpMessageWithButton (ctx : IDiscordContext) interactiveMessage =
|
|
||||||
async {
|
|
||||||
let builder = DiscordFollowupMessageBuilder()
|
|
||||||
let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText) :> DiscordComponent
|
|
||||||
builder.AddComponents [| button |] |> ignore
|
|
||||||
builder.IsEphemeral <- true
|
|
||||||
builder.Content <- interactiveMessage.Message
|
|
||||||
do! ctx.FollowUp(builder) |> Async.AwaitTask
|
|
||||||
}
|
|
||||||
|
|
||||||
let updateMessageWithGreyedOutButtons (ctx : IDiscordContext) interactiveMessage =
|
|
||||||
async {
|
|
||||||
let builder = DiscordInteractionResponseBuilder()
|
|
||||||
let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText, true) :> DiscordComponent
|
|
||||||
builder.AddComponents [| button |] |> ignore
|
|
||||||
builder.IsEphemeral <- true
|
|
||||||
builder.Content <- interactiveMessage.Message
|
|
||||||
do! ctx.Respond(InteractionResponseType.UpdateMessage, builder) |> Async.AwaitTask
|
|
||||||
}
|
|
||||||
|
|
||||||
let handleResultWithResponse ctx fn (player : Result<PlayerData, string>) =
|
|
||||||
match player with
|
|
||||||
| Ok p -> fn p
|
|
||||||
| Error e -> async { do! sendFollowUpMessage ctx e }
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Include="paket.references" />
|
|
||||||
<Compile Include="Shared.fs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="..\.paket\Paket.Restore.targets" />
|
|
||||||
</Project>
|
|
@ -1,3 +0,0 @@
|
|||||||
FSharp.Core
|
|
||||||
DSharpPlus
|
|
||||||
DSharpPlus.SlashCommands
|
|
Loading…
x
Reference in New Issue
Block a user