From 97bf07e7ecd4c607cc2e1d0b825baba475fd4258 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sun, 9 Jan 2022 15:43:39 +0700 Subject: [PATCH] Weapon cooldowns --- Program.fs | 279 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 157 insertions(+), 122 deletions(-) diff --git a/Program.fs b/Program.fs index f0e6c00..44f7d36 100644 --- a/Program.fs +++ b/Program.fs @@ -7,7 +7,7 @@ open DSharpPlus.EventArgs open DSharpPlus.SlashCommands open Emzi0767.Utilities -type HackType = +type Hack = | Virus = 0 | Ransom = 1 | DDos = 2 @@ -15,7 +15,7 @@ type HackType = | Crack = 4 | Injection = 5 -type DefenseType = +type Protection = | Firewall = 0 | PortScan = 1 | Encryption = 2 @@ -23,10 +23,22 @@ type DefenseType = | Hardening = 4 | Sanitation = 5 +type Attack = { + HackType : Hack + Timestamp : DateTime +} + +type Defense = { + DefenseType : Hack + Timestamp : DateTime +} + type Player = { DiscordId : uint64 - Hacks : HackType list - Defenses : DefenseType list + Hacks : Hack list + Protections : Protection list + Attacks : Attack list + Defenses : Defense list Bank : int64 } @@ -44,9 +56,40 @@ let newPlayer (membr : uint64) = // |> Set.map (fun _ -> enum(rand.Next(0, 6))) { DiscordId = membr - Hacks = [ HackType.Virus ; HackType.Worm ; HackType.Injection ] - Defenses = [ DefenseType.Cypher ; DefenseType.Sanitation ; DefenseType.Firewall ] - Bank = 0L } + Hacks = [ Hack.Virus ; Hack.Worm ; Hack.Injection ] + Protections = [ Protection.Cypher ; Protection.Sanitation ; Protection.Firewall ] + Attacks = [] + Bank = 0L + Defenses = [] } + +let constructButtons (actionType : string) (playerId : uint64) (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}", + $"{hack}")) + +let notRegisteredYetMessage (ctx : InteractionContext) = + async { + let builder = DiscordInteractionResponseBuilder() + builder.Content <- $"You are not currently a hacker, first use the /redpill command to become one" + + builder.AsEphemeral true |> ignore + + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + + } |> Async.StartAsTask + :> Task + +let removeExpiredActions timespan (timestamp : 'a -> DateTime) actions = + actions + |> List.filter (fun act -> + if DateTime.UtcNow - (timestamp act) < timespan + then true + else false) type JoeBot() = inherit ApplicationCommandModule () @@ -89,25 +132,55 @@ type JoeBot() = :> Task [] - member this.Hack (ctx : InteractionContext, [] player : DiscordUser) = - let constructButtons (playerId : uint64) (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, - $"Hack-{hack}-{player.Id}", - $"{hack}")) - + member this.Attack (ctx : InteractionContext, [] player : DiscordUser) = + 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 + async { + let builder = DiscordInteractionResponseBuilder() + builder.AddEmbed (this.Embed("Pick the hack you wish to use. ")) |> ignore + + constructButtons "Attack" player.DiscordId player.Hacks + |> Seq.cast + |> builder.AddComponents + |> ignore + + builder.AsEphemeral true |> ignore + + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + + } |> Async.StartAsTask + :> Task + else + async { + let builder = DiscordInteractionResponseBuilder() + builder.Content <- "You have no more hacks available, please wait for another hack to cooldown" + + builder.AsEphemeral true |> ignore + + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + + } |> Async.StartAsTask + :> Task + | None -> notRegisteredYetMessage ctx + + + [] + member this.Defend (ctx : InteractionContext) = players |> List.tryFind (fun p -> p.DiscordId = ctx.Member.Id) |> function | Some player -> async { let builder = DiscordInteractionResponseBuilder() - builder.AddEmbed (this.Embed()) |> ignore + builder.AddEmbed (this.Embed("Pick a defense to mount for a duration of time")) |> ignore - constructButtons player.DiscordId player.Hacks + constructButtons "Defense" player.DiscordId player.Protections |> Seq.cast |> builder.AddComponents |> ignore @@ -119,121 +192,83 @@ type JoeBot() = } |> Async.StartAsTask :> Task - | None -> - async { - let builder = DiscordInteractionResponseBuilder() - builder.Content <- $"You are not currently a hacker, first use the /redpill command to become one" - - builder.AsEphemeral true |> ignore - - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - - } |> Async.StartAsTask - :> Task - + | None -> notRegisteredYetMessage ctx - [] - member _.StartMatch (ctx : InteractionContext, [] player : DiscordUser) = - async { - // We won't be able to find the user if they are Away or Sleeping apparently - let ( result , discordMember ) = ctx.Guild.Members.TryGetValue(player.Id) - - if result then - let yes = DiscordButtonComponent( - ButtonStyle.Primary, - "first_button", - "I do") - let no = DiscordButtonComponent( - ButtonStyle.Danger, - "second_button", - "No thank you") -// let yes = DiscordButtonComponent( -// ButtonStyle.Primary, -// $"yes_for_{ctx.Member.Id}", -// "I do") -// let no = DiscordButtonComponent( -// ButtonStyle.Danger, -// $"no_for_{ctx.Member.Id}", -// "No thank you") -// builder.AddComponents(yes, no) |> ignore - let builder = DiscordMessageBuilder() - let builder = builder.AddComponents(yes, no) - use img = new FileStream("challenge.jpg", FileMode.Open) - builder.WithFile(img) |> ignore - builder.Content <- $"You have been challenged by {player.Username}, do you accept?" - - for channel in ctx.Guild.Channels do - if channel.Value.Name = "battle-1" then - do! channel.Value.SendMessageAsync builder - |> Async.AwaitTask - |> Async.Ignore - - - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- $"Sending challenge to {player.Username}" - do! ctx.CreateResponseAsync (builder) - |> Async.AwaitTask - else - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- $"Unable to find user in this server" - do! ctx.CreateResponseAsync (builder) - |> Async.AwaitTask - - } |> Async.StartAsTask - :> Task - - member _.Embed () = + member _.Embed message = let builder = DiscordEmbedBuilder() builder.Color <- Optional(DiscordColor.PhthaloGreen) - builder.Description <- "Pick the hack you wish to use. " + 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() - - [] - member this.TestEmbed (ctx : InteractionContext) = - async { - - do! ctx.CreateResponseAsync (this.Embed() , true) - |> Async.AwaitTask - } |> Async.StartAsTask - :> Task +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] + match resultHack , resultId with + | true , true -> + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- $"Hack has been sent to {target}!" + do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) + |> Async.AwaitTask + + players <- + players + |> List.map (fun p -> { p with Attacks = { HackType = hackType :?> Hack ; Timestamp = DateTime.UtcNow }::p.Attacks }) + + let builder = DiscordMessageBuilder() + builder.WithContent($"{event.User.Username} has sent a hack to <@{target}>") |> ignore + let battleChannel = (event.Guild.GetChannel(927449884204867664uL)) + do! battleChannel.SendMessageAsync(builder) + |> Async.AwaitTask + |> Async.Ignore + | _ -> + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- "Error parsing Button Id" + do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + } + +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 builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- $"Hack has been sent to your target!" + do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) + |> Async.AwaitTask + + let builder = DiscordMessageBuilder() + builder.WithContent($"{event.User.Username} has protected their system!") |> ignore + let battleChannel = (event.Guild.GetChannel(927449884204867664uL)) + do! battleChannel.SendMessageAsync(builder) + |> Async.AwaitTask + |> Async.Ignore + | _ -> + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- "Error parsing Button Id" + do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + } + let handleButtonEvent (client : DiscordClient) (event : ComponentInteractionCreateEventArgs) = async { - match event.Id with - | id when id.StartsWith("Hack") -> - 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 builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- $"Hack has been sent to {target}!" - do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) - |> Async.AwaitTask - - let builder = DiscordMessageBuilder() - builder.WithContent($"{event.User.Username} has sent a hack to <@{target}>") |> ignore - let battleChannel = (event.Guild.GetChannel(927449884204867664uL)) - do! battleChannel.SendMessageAsync(builder) - |> Async.AwaitTask - |> Async.Ignore - | _ -> - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- "Error parsing Button Id" - do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - - | _ -> () + return match event.Id with + | id when id.StartsWith("Attack") -> handleAttack event + | id when id.StartsWith("Defend") -> handleDefense event + | _ -> async { return () } } |> Async.StartAsTask :> Task