Move things around some more
This commit is contained in:
parent
9f80069b2f
commit
c45f1c6ca6
@ -13,17 +13,19 @@
|
||||
<Content Include="paket.references" />
|
||||
<Compile Include="Prelude.fs" />
|
||||
<Compile Include="GuildEnvironment.fs" />
|
||||
<Compile Include="Game.fs" />
|
||||
<Compile Include="GameTypes.fs" />
|
||||
<Compile Include="Messaging.fs" />
|
||||
<Compile Include="DbService.fs" />
|
||||
<Compile Include="PlayerInteractions.fs" />
|
||||
<Compile Include="GameHelpers.fs" />
|
||||
<Compile Include="XP.fs" />
|
||||
<Compile Include="Embeds.fs" />
|
||||
<Compile Include="SlotMachine.fs" />
|
||||
<Compile Include="Thief.fs" />
|
||||
<Compile Include="RockPaperScissors.fs" />
|
||||
<Compile Include="Store.fs" />
|
||||
<Compile Include="Trainer.fs" />
|
||||
<Compile Include="HackerBattle.fs" />
|
||||
<Compile Include="Games\SlotMachine.fs" />
|
||||
<Compile Include="Games\Thief.fs" />
|
||||
<Compile Include="Games\RockPaperScissors.fs" />
|
||||
<Compile Include="Games\Store.fs" />
|
||||
<Compile Include="Games\Trainer.fs" />
|
||||
<Compile Include="Games\HackerBattle.fs" />
|
||||
<Compile Include="Bot.fs" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\.paket\Paket.Restore.targets" />
|
||||
|
337
Bot/Game.fs
337
Bot/Game.fs
@ -1,337 +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 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 }
|
||||
|
||||
open 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 )
|
||||
|
||||
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}"
|
||||
|
88
Bot/GameHelpers.fs
Normal file
88
Bot/GameHelpers.fs
Normal file
@ -0,0 +1,88 @@
|
||||
namespace Degenz
|
||||
|
||||
open DSharpPlus
|
||||
open DSharpPlus.Entities
|
||||
open Newtonsoft.Json
|
||||
|
||||
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 WeaponClass =
|
||||
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 )
|
||||
|
||||
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 WeaponClass.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}"
|
||||
|
117
Bot/GameTypes.fs
Normal file
117
Bot/GameTypes.fs
Normal file
@ -0,0 +1,117 @@
|
||||
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 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> }
|
143
Bot/Messaging.fs
Normal file
143
Bot/Messaging.fs
Normal file
@ -0,0 +1,143 @@
|
||||
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
|
||||
}
|
||||
|
||||
let handleResultWithResponse ctx fn (player : Result<PlayerData, string>) =
|
||||
match player with
|
||||
| Ok p -> fn p
|
||||
| Error e -> async { do! sendFollowUpMessage ctx e }
|
||||
|
Loading…
x
Reference in New Issue
Block a user