discord-bot-game/Commands.fs

288 lines
14 KiB
Forth

module Joebot.Commands
open System
open System.Threading.Tasks
open DSharpPlus
open DSharpPlus.Entities
open DSharpPlus.EventArgs
open DSharpPlus.SlashCommands
open Joebot.Types
open Joebot.Functions
let mutable players : Player list = []
[<Literal>]
let battleChannel = 930363007781978142uL
let addHackerRole (ctx : InteractionContext) =
async {
for role in ctx.Guild.Roles do
if role.Value.Name = "Hacker" then
do! ctx.Member.GrantRoleAsync(role.Value)
|> Async.AwaitTask
let player = players |> List.tryFind (fun p -> int64 p.DiscordId = int64 ctx.Member.Id)
players <-
match player with
| Some _ -> players
| None -> (newPlayer ctx.Member.Username ctx.Member.Id)::players
if Option.isSome player then
do! ctx.CreateResponseAsync("Already registered as an elite haxxor", true)
|> Async.AwaitTask
else
do! ctx.CreateResponseAsync("You are now an elite haxxor", true)
|> Async.AwaitTask
} |> Async.StartAsTask
:> Task
let removeHackerRole (ctx : InteractionContext) =
async {
for role in ctx.Member.Roles do
if role.Name = "Hacker" then
do! ctx.Member.RevokeRoleAsync(role)
|> Async.AwaitTask
players <- players |> List.filter (fun p -> p.DiscordId <> ctx.User.Id)
do! ctx.CreateResponseAsync("You are now lame", true)
|> Async.AwaitTask
} |> Async.StartAsTask
:> Task
let attack (ctx : InteractionContext) (target : DiscordUser) =
// TODO: We need to check if the player has any active embed hacks going, if not they can cheat
let attacker = players |> List.tryFind (fun p -> p.DiscordId = ctx.Member.Id)
let defender = players |> List.tryFind (fun p -> p.DiscordId = target.Id)
match attacker , defender with
| Some attacker , Some defender ->
let updatedAttacks = removeExpiredActions (TimeSpan.FromMinutes(5)) (fun (atk : Attack) -> atk.Timestamp) attacker.Attacks
players <-
players
|> List.map (fun p -> if p.DiscordId = attacker.DiscordId then { p with Attacks = updatedAttacks } else p)
if updatedAttacks.Length < 2 then
async {
let builder = DiscordInteractionResponseBuilder()
builder.AddEmbed (constructEmbed "Pick the hack you wish to use.") |> ignore
let defenderInfo = $"{defender.DiscordId}-{target.Username}"
constructButtons "Attack" defenderInfo attacker.Weapons
|> Seq.cast<DiscordComponent>
|> builder.AddComponents
|> ignore
builder.AsEphemeral true |> ignore
do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder)
|> Async.AwaitTask
} |> Async.StartAsTask
:> Task
else
async {
let builder = DiscordInteractionResponseBuilder()
let timestamp = updatedAttacks |> List.rev |> List.head |> fun a -> a.Timestamp // This should be the next expiring timestamp
let timeRemaining = TimeSpan.FromMinutes(15) - (DateTime.UtcNow - timestamp)
builder.Content <- $"No more hacks available, please wait {timeRemaining.Minutes} minutes and {timeRemaining.Seconds} seconds to attempt another hack"
builder.AsEphemeral true |> ignore
do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder)
|> Async.AwaitTask
} |> Async.StartAsTask
:> Task
| None , _ -> notYetAHackerMsg ctx
| _ , None -> createSimpleResponseAsync "Your target is not connected to the network, they must join first by using the /redpill command" ctx
let defend (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)
if updatedDefenses.Length < 2 then
let builder = DiscordInteractionResponseBuilder()
builder.AddEmbed (constructEmbed "Pick a defense to mount for a duration of time") |> ignore
constructButtons "Defend" (string player.DiscordId) player.Shields
|> Seq.cast<DiscordComponent>
|> builder.AddComponents
|> ignore
builder.AsEphemeral true |> ignore
do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder)
|> Async.AwaitTask
else
let builder = DiscordInteractionResponseBuilder()
let timestamp = updatedDefenses |> List.rev |> List.head |> fun a -> a.Timestamp // This should be the next expiring timestamp
let timeRemaining = TimeSpan.FromMinutes(15) - (DateTime.UtcNow - timestamp)
builder.Content <- $"Cannot add new defense, please wait {timeRemaining.Minutes} minutes and {timeRemaining.Seconds} seconds to add another defense"
builder.AsEphemeral true |> ignore
do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder)
|> Async.AwaitTask
} |> Async.StartAsTask
:> Task
| None -> notYetAHackerMsg ctx
let status (ctx : InteractionContext) =
async {
return!
match players |> List.tryFind (fun p -> p.DiscordId = ctx.Member.Id) with
| Some player ->
async {
let builder = DiscordInteractionResponseBuilder()
builder.IsEphemeral <- true
builder.Content <- Functions.statusFormat player
do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder)
|> Async.AwaitTask
}
| None -> notYetAHackerMsg ctx |> Async.AwaitTask
} |> Async.StartAsTask
:> Task
let leaderboard (ctx : InteractionContext) =
async {
let builder = DiscordInteractionResponseBuilder()
builder.IsEphemeral <- true
let content =
players
|> List.sortByDescending (fun p -> p.Bank)
|> List.mapi (fun i p -> $"{i + 1}. {p.Bank} {p.Name}")
|> String.concat "\n"
builder.Content <- if not <| String.IsNullOrEmpty content then content else "There are no active hackers"
do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder)
|> Async.AwaitTask
} |> Async.StartAsTask
:> Task
let handleAttack (event : ComponentInteractionCreateEventArgs) =
let updatePlayer amount attack p = {
p with Attacks = attack::p.Attacks
Bank = MathF.Max(p.Bank + amount, 0f)
}
async {
let split = event.Id.Split("-")
let resultHack = Weapon.TryParse(split.[1])
let ( resultId , targetId ) = UInt64.TryParse split.[2]
return!
match resultHack , resultId with
| Some weapon , true ->
let hackType = weapon
players
|> List.find (fun p -> p.DiscordId = targetId)
|> fun p -> p.Defenses
|> List.map (fun dfn -> dfn.DefenseType)
|> List.map (calculateDamage hackType)
|> List.contains Weak
|> function
| false ->
async {
let prize = 0.1726f
let attack = { HackType = hackType ; Timestamp = DateTime.UtcNow ; Target = { Id = targetId ; Name = split.[3] } }
players <-
players
|> List.map (fun p -> if p.DiscordId = event.User.Id then updatePlayer prize attack p else p)
let builder = DiscordInteractionResponseBuilder()
builder.IsEphemeral <- true
builder.Content <- $"Successfully hacked {split.[3]} using {hackType}! You just won {prize} genz!"
do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder)
|> Async.AwaitTask
let builder = DiscordMessageBuilder()
builder.WithContent($"{event.User.Username} successfully hacked <@{targetId}>!") |> ignore
let channel = (event.Guild.GetChannel(battleChannel))
do! channel.SendMessageAsync(builder)
|> Async.AwaitTask
|> Async.Ignore
}
| true ->
async {
let builder = DiscordInteractionResponseBuilder()
let loss = -0.0623f
builder.IsEphemeral <- true
builder.Content <- $"Hack failed! {split.[3]} was able to mount a successful defense! You lost {loss} genz!"
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 -> if p.DiscordId = event.User.Id then updatePlayer loss attack p else p)
let builder = DiscordMessageBuilder()
builder.WithContent($"{event.User.Username} failed to hack <@{targetId}>!") |> ignore
let channel = (event.Guild.GetChannel(battleChannel))
do! channel.SendMessageAsync(builder)
|> Async.AwaitTask
|> Async.Ignore
}
| _ ->
async {
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("-")
return!
match Shield.TryParse(split.[1]) with
| Some shield ->
async {
let builder = DiscordInteractionResponseBuilder()
builder.IsEphemeral <- true
builder.Content <- $"Mounted a {shield} defense for 1 hour"
do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder)
|> Async.AwaitTask
let defense = { DefenseType = shield ; 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 channel = (event.Guild.GetChannel(battleChannel))
do! channel.SendMessageAsync(builder)
|> Async.AwaitTask
|> Async.Ignore
}
| _ ->
async {
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 {
return! match event.Id with
| id when id.StartsWith("Attack") -> handleAttack event
| id when id.StartsWith("Defend") -> handleDefense event
| _ ->
async {
let builder = DiscordInteractionResponseBuilder()
builder.IsEphemeral <- true
builder.Content <- $"Incorrect Action identifier {event.Id}"
do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder)
|> Async.AwaitTask
}
} |> Async.StartAsTask
:> Task