262 lines
8.9 KiB
Forth

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
Cooldown : int<mins>
Attributes : ItemAttributes
}
type HackResult =
| Strong
| Weak
[<CLIMutable>]
type DiscordPlayer = { Id: uint64; Name: string }
with static member empty = { Id = 0uL ; Name = "None" }
type PlayerEventResult =
| Positive = 0
| Neutral = 1
| Negative = 2
type PlayerEventType =
| Hacking = 0
| Shielding = 1
| Steal = 2
| Imprison = 3
[<CLIMutable>]
type PlayerEvent =
{ Type : PlayerEventType
Result : PlayerEventResult
Adversary : DiscordPlayer
ItemId : int
Timestamp : DateTime }
[<CLIMutable>]
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
Achievements : string array
XP : int
Bank : int<GBT>
}
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 -> 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 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 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 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 }