diff --git a/Program.fs b/Program.fs index 44f7d36..5ad1f9a 100644 --- a/Program.fs +++ b/Program.fs @@ -1,5 +1,4 @@ open System -open System.IO open System.Threading.Tasks open DSharpPlus open DSharpPlus.Entities @@ -23,13 +22,19 @@ type Protection = | Hardening = 4 | Sanitation = 5 +type DiscordPlayer = { + Id : uint64 + Name : string +} + type Attack = { HackType : Hack + Target : DiscordPlayer Timestamp : DateTime } type Defense = { - DefenseType : Hack + DefenseType : Protection Timestamp : DateTime } @@ -62,13 +67,12 @@ let newPlayer (membr : uint64) = Bank = 0L Defenses = [] } -let constructButtons (actionType : string) (playerId : uint64) (weapons : 'a list) = +let constructButtons (actionType : string) (playerInfo : string) (weapons : 'a list) = weapons |> Seq.map (fun hack -> - // TODO:L Button ID should be a GUID and we should keep an in-memory store of the buttons we're waiting for DiscordButtonComponent( ButtonStyle.Primary, - $"{actionType}-{hack}-{playerId}", + $"{actionType}-{hack}-{playerInfo}", $"{hack}")) let notRegisteredYetMessage (ctx : InteractionContext) = @@ -91,6 +95,17 @@ let removeExpiredActions timespan (timestamp : 'a -> DateTime) actions = then true else false) +let constructEmbed message = + let builder = DiscordEmbedBuilder() + builder.Color <- Optional(DiscordColor.PhthaloGreen) + builder.Description <- message + let author = DiscordEmbedBuilder.EmbedAuthor() + author.Name <- "Joebot Pro" + author.Url <- "https://ferano.io" + author.IconUrl <- "https://i.kym-cdn.com/entries/icons/original/000/028/861/cover3.jpg" + builder.Author <- author + builder.Build() + type JoeBot() = inherit ApplicationCommandModule () @@ -132,18 +147,23 @@ type JoeBot() = :> Task [] - member this.Attack (ctx : InteractionContext, [] player : DiscordUser) = + member this.AttackCommand (ctx : InteractionContext, [] target : DiscordUser) = + // TODO: We need to check if the player has any active hacks going, if not they can cheat players |> List.tryFind (fun p -> p.DiscordId = ctx.Member.Id) |> function | Some player -> - let updatedAttacks = removeExpiredActions (TimeSpan.FromMinutes(5)) (fun (atk : Attack) -> atk.Timestamp) player.Attacks - if updatedAttacks.Length <= 3 then + let updatedAttacks = removeExpiredActions (TimeSpan.FromMinutes(15)) (fun (atk : Attack) -> atk.Timestamp) player.Attacks + players <- + players + |> List.map (fun p -> if p.DiscordId = player.DiscordId then { p with Attacks = updatedAttacks } else p) + if updatedAttacks.Length < 1 then async { let builder = DiscordInteractionResponseBuilder() - builder.AddEmbed (this.Embed("Pick the hack you wish to use. ")) |> ignore + builder.AddEmbed (constructEmbed "Pick the hack you wish to use.") |> ignore - constructButtons "Attack" player.DiscordId player.Hacks + let targetInfo = $"{target.Id}-{target.Username}" + constructButtons "Attack" targetInfo player.Hacks |> Seq.cast |> builder.AddComponents |> ignore @@ -158,7 +178,8 @@ type JoeBot() = else async { let builder = DiscordInteractionResponseBuilder() - builder.Content <- "You have no more hacks available, please wait for another hack to cooldown" + let timeRemaining = TimeSpan.FromMinutes(15) - (DateTime.UtcNow - updatedAttacks.Head.Timestamp) + builder.Content <- $"You already hacked, please wait {timeRemaining.Minutes} minutes and {timeRemaining.Seconds} seconds to attempt another hack" builder.AsEphemeral true |> ignore @@ -171,16 +192,20 @@ type JoeBot() = [] - member this.Defend (ctx : InteractionContext) = + member this.DefendCommand (ctx : InteractionContext) = players |> List.tryFind (fun p -> p.DiscordId = ctx.Member.Id) |> function | Some player -> async { + let updatedDefenses = removeExpiredActions (TimeSpan.FromMinutes(60)) (fun (pro : Defense) -> pro.Timestamp) player.Defenses + players <- + players + |> List.map (fun p -> if p.DiscordId = player.DiscordId then { p with Defenses = updatedDefenses } else p) let builder = DiscordInteractionResponseBuilder() - builder.AddEmbed (this.Embed("Pick a defense to mount for a duration of time")) |> ignore + builder.AddEmbed (constructEmbed "Pick a defense to mount for a duration of time") |> ignore - constructButtons "Defense" player.DiscordId player.Protections + constructButtons "Defense" (string player.DiscordId) player.Protections |> Seq.cast |> builder.AddComponents |> ignore @@ -194,36 +219,27 @@ type JoeBot() = :> Task | None -> notRegisteredYetMessage ctx - member _.Embed message = - let builder = DiscordEmbedBuilder() - builder.Color <- Optional(DiscordColor.PhthaloGreen) - builder.Description <- message - let author = DiscordEmbedBuilder.EmbedAuthor() - author.Name <- "Joebot Pro" - author.Url <- "https://ferano.io" - author.IconUrl <- "https://i.kym-cdn.com/entries/icons/original/000/028/861/cover3.jpg" - builder.Author <- author - builder.Build() - let handleAttack (event : ComponentInteractionCreateEventArgs) = async { let split = event.Id.Split("-") let ( resultHack , hackType ) = Enum.TryParse(typedefof, split.[1]) - let ( resultId , target ) = UInt64.TryParse split.[2] + let ( resultId , targetId ) = UInt64.TryParse split.[2] match resultHack , resultId with | true , true -> + let hackType = hackType :?> Hack let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true - builder.Content <- $"Hack has been sent to {target}!" + builder.Content <- $"Sent {hackType} to {split.[3]}!" do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) |> Async.AwaitTask + let attack = { HackType = hackType ; Timestamp = DateTime.UtcNow ; Target = { Id = targetId ; Name = split.[3] } } players <- players - |> List.map (fun p -> { p with Attacks = { HackType = hackType :?> Hack ; Timestamp = DateTime.UtcNow }::p.Attacks }) + |> List.map (fun p -> if p.DiscordId = event.User.Id then { p with Attacks = attack::p.Attacks } else p) let builder = DiscordMessageBuilder() - builder.WithContent($"{event.User.Username} has sent a hack to <@{target}>") |> ignore + builder.WithContent($"{event.User.Username} has sent a hack to <@{targetId}>") |> ignore let battleChannel = (event.Guild.GetChannel(927449884204867664uL)) do! battleChannel.SendMessageAsync(builder) |> Async.AwaitTask @@ -239,16 +255,21 @@ let handleAttack (event : ComponentInteractionCreateEventArgs) = let handleDefense (event : ComponentInteractionCreateEventArgs) = async { let split = event.Id.Split("-") - let ( resultHack , hackType ) = Enum.TryParse(typedefof, split.[1]) - let ( resultId , target ) = UInt64.TryParse split.[2] - match resultHack , resultId with - | true , true -> + let ( resultHack , protectionType ) = Enum.TryParse(typedefof, split.[1]) + match resultHack with + | true -> + let protectionType = protectionType :?> Protection let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true - builder.Content <- $"Hack has been sent to your target!" + builder.Content <- $"Mounted a {protectionType} defense for 1 hour" do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) |> Async.AwaitTask + let defense = { DefenseType = protectionType ; Timestamp = DateTime.UtcNow } + players <- + players + |> List.map (fun p -> { p with Defenses = defense::p.Defenses }) + let builder = DiscordMessageBuilder() builder.WithContent($"{event.User.Username} has protected their system!") |> ignore let battleChannel = (event.Guild.GetChannel(927449884204867664uL)) @@ -265,10 +286,10 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) = let handleButtonEvent (client : DiscordClient) (event : ComponentInteractionCreateEventArgs) = async { - return match event.Id with - | id when id.StartsWith("Attack") -> handleAttack event - | id when id.StartsWith("Defend") -> handleDefense event - | _ -> async { return () } + return! match event.Id with + | id when id.StartsWith("Attack") -> handleAttack event + | id when id.StartsWith("Defend") -> handleDefense event + | _ -> async { return () } } |> Async.StartAsTask :> Task