namespace Degenz open System open DSharpPlus open DSharpPlus.Entities open DSharpPlus.EventArgs open DSharpPlus.SlashCommands open Newtonsoft.Json [] module Types = [] type mins [] type GBT type BattleClass = | Network | Exploit | Penetration type HackId = | Virus = 0 | RemoteAccess = 1 | Worm = 2 type ShieldId = | Firewall = 6 | Encryption = 7 | Cypher = 8 type ItemType = | Hack | Shield type BattleItem = { Id : int Name : string Cost : int Type : ItemType Class : BattleClass Power : int Cooldown : int } type HackResult = | Strong | Weak [] type DiscordPlayer = { Id: uint64; Name: string } [] type AttackResult = { Result : bool Target : DiscordPlayer } type ActionType = | Attack of AttackResult | Defense [] type Action = { ActionId : int Type : ActionType Timestamp : DateTime } [] type PlayerData = { DiscordId : uint64 Name : string Arsenal : BattleItem array Actions : Action array Bank : int } module Armory = let battleItems = let file = System.IO.File.ReadAllText("Items.json") JsonConvert.DeserializeObject(file) let getItem itemId = battleItems |> Array.find (fun w -> w.Id = itemId) module Player = let SameTargetAttackCooldown = TimeSpan.FromHours(2) let hacks player = player.Arsenal |> Array.filter (fun bi -> bi.Type = Hack) let shields player = player.Arsenal |> Array.filter (fun bi -> bi.Type = Shield) let attacks player = player.Actions |> Array.filter (fun act -> match act.Type with Attack _ -> true | _ -> false) let defenses player = player.Actions |> Array.filter (fun act -> match act.Type with Defense -> true | _ -> false) let removeExpiredActions player = let actions = player.Actions |> Array.filter (fun (act : Action) -> match act.Type with // So the player doesnt attack the same player so many times in a row | Attack _ -> DateTime.UtcNow - act.Timestamp < SameTargetAttackCooldown | Defense -> let item = Armory.getItem act.ActionId DateTime.UtcNow - act.Timestamp < TimeSpan.FromMinutes(int item.Cooldown)) { player with Actions = actions } let modifyBank player amount = { player with Bank = max (player.Bank + amount) 0 } let getAttacksFlat actions = actions |> Array.choose (fun act -> match act.Type with Attack ar -> Some (act,ar.Target,ar.Result) | Defense -> None) module Messaging = type InteractiveMessage = { ButtonId : string ButtonText : string Message : string } let getTimeTillCooldownFinishes (timespan : TimeSpan) timestamp = let remaining = timespan - (DateTime.UtcNow - timestamp) let plural amount = if amount = 1 then "" else "s" let ``and`` = if remaining.Hours > 0 then "and " else "" let hours = if remaining.Hours > 0 then $"{remaining.Hours} hour{plural remaining.Hours} {``and``}" else String.Empty let totalMins = remaining.Minutes + 1 let minutes = if totalMins > 0 then $"{totalMins} minute{plural totalMins}" else "1 minute" $"{hours}{minutes}" let battleItemFormat (items : BattleItem array) = match items with | [||] -> "None" | _ -> items |> Array.toList |> List.map (fun i -> i.Name) |> String.concat ", " let actionFormat (actions : Action array) = match actions with | [||] -> "None" | _ -> actions |> Array.map (fun act -> match act.Type with | Attack atk -> $"Hacked {atk.Target.Name} at {act.Timestamp.ToShortTimeString()}" | Defense -> let item = Armory.getItem act.ActionId let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromMinutes(int item.Cooldown)) act.Timestamp $"{item.Name} active for {cooldown}") |> String.concat "\n" let statusFormat p = $"**Hacks:** {Player.hacks p |> battleItemFormat}\n **Shields:** {Player.shields p |> battleItemFormat}\n **Hack Attacks:**\n{Player.attacks p |> actionFormat}\n **Active Shields:** {Player.defenses p |> actionFormat}" let constructButtons (actionType: string) (playerInfo: string) (weapons: BattleItem array) = weapons |> Array.map (fun w -> DiscordButtonComponent(ButtonStyle.Success, $"{actionType}-{w.Id}-{playerInfo}", $"{w.Name}")) let sendSimpleResponse (ctx: InteractionContext) msg = async { let builder = DiscordInteractionResponseBuilder() builder.Content <- msg builder.AsEphemeral true |> ignore do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask } let sendFollowUpMessage (event : ComponentInteractionCreateEventArgs) msg = async { let builder = DiscordFollowupMessageBuilder() builder.IsEphemeral <- true builder.Content <- msg do! event.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore } let sendFollowUpMessageFromCtx (ctx : InteractionContext) msg = async { let builder = DiscordFollowupMessageBuilder() builder.IsEphemeral <- true builder.Content <- msg do! ctx.FollowUpAsync(builder) |> Async.AwaitTask |> Async.Ignore } let sendFollowUpMessageWithEmbed (event : ComponentInteractionCreateEventArgs) (embed : DiscordEmbed) msg = async { let builder = DiscordFollowupMessageBuilder() .AsEphemeral(true) .WithContent(msg) .AddEmbed(embed) do! event.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore } let sendFollowUpMessageWithButton (event : ComponentInteractionCreateEventArgs) 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! event.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore } let updateMessageWithGreyedOutButtons (event : ComponentInteractionCreateEventArgs) 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! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) |> Async.AwaitTask } let sendInteractionEvent (event : ComponentInteractionCreateEventArgs) msg = async { let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true builder.Content <- msg do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask } let sendInteractionEventWithButton (event : ComponentInteractionCreateEventArgs) buttonId msg = async { let builder = DiscordInteractionResponseBuilder() let button = DiscordButtonComponent(ButtonStyle.Success, buttonId, "Got it") :> DiscordComponent builder.AddComponents [| button |] |> ignore builder.IsEphemeral <- true builder.Content <- msg do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask }