271 lines
9.1 KiB
Forth
271 lines
9.1 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
|
|
}
|
|
with static member empty =
|
|
{ Id = -1
|
|
Name = "None"
|
|
Price = 0<GBT>
|
|
Type = ItemType.Hack
|
|
Power = 0
|
|
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 PlayerEventResult =
|
|
| Positive = 0
|
|
| Neutral = 1
|
|
| Negative = 2
|
|
|
|
type PlayerEventType =
|
|
| Hacking = 0
|
|
| Shielding = 1
|
|
| Steal = 2
|
|
| Imprison = 3
|
|
|
|
[<CLIMutable>]
|
|
type PlayerEvent =
|
|
{ Type : PlayerEventType
|
|
Result : PlayerEventResult
|
|
IsInstigator : bool
|
|
Adversary : DiscordPlayer
|
|
ItemId : int
|
|
Cooldown : int<mins>
|
|
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 }
|
|
|