diff --git a/Bot/Embeds.fs b/Bot/Embeds.fs index cda11e4..14426c5 100644 --- a/Bot/Embeds.fs +++ b/Bot/Embeds.fs @@ -5,23 +5,27 @@ open Degenz.Shared open DSharpPlus.Entities open AsciiTableFormatter +let hackGif = "https://s10.gifyu.com/images/Hacker-Degenz-V2.gif" +let shieldGif = "https://s10.gifyu.com/images/Defense-Degenz-V2.gif" + let getHackGif = function - | Hack.Virus -> "https://s10.gifyu.com/images/Attack-DegenZ.gif" - | Hack.Ransom -> "https://s10.gifyu.com/images/Mind-Control-Degenz-V2.gif" - | Hack.Worm -> "https://s10.gifyu.com/images/WormBugAttack_Degenz.gif" - | Hack.DDos -> "https://s10.gifyu.com/images/Attack-DegenZ.gif" - | Hack.Crack -> "https://s10.gifyu.com/images/Attack-DegenZ.gif" - | Hack.Injection -> "https://s10.gifyu.com/images/Attack-DegenZ.gif" - | _ -> "https://s10.gifyu.com/images/Hacker-Degenz-V2.gif" + | HackId.Virus -> "https://s10.gifyu.com/images/Attack-DegenZ.gif" + | HackId.Ransom -> "https://s10.gifyu.com/images/Mind-Control-Degenz-V2.gif" + | HackId.Worm -> "https://s10.gifyu.com/images/WormBugAttack_Degenz.gif" + | HackId.DDos -> "https://s10.gifyu.com/images/Attack-DegenZ.gif" + | HackId.Crack -> "https://s10.gifyu.com/images/Attack-DegenZ.gif" + | HackId.Injection -> "https://s10.gifyu.com/images/Attack-DegenZ.gif" + | _ -> hackGif let getShieldGif = function - | Shield.Firewall -> "https://s10.gifyu.com/images/Defense-GIF-1-Degenz.gif" - | Shield.PortScan -> "https://s10.gifyu.com/images/PortScanDefense_Degenz.gif" - | Shield.Encryption -> "https://s10.gifyu.com/images/Anonymous-Degenz-V2.gif" - | Shield.Hardening -> "https://s10.gifyu.com/images/Encryption-Degenz-V2.gif" - | Shield.Sanitation -> "https://s10.gifyu.com/images/VPN-Degenz.gif" - | Shield.Cypher -> "https://s10.gifyu.com/images/Matrix_Degenz.gif" - | _ -> "https://s10.gifyu.com/images/Hacker-Degenz-V2.gif" + | ShieldId.Firewall -> "https://s10.gifyu.com/images/Defense-GIF-1-Degenz.gif" + | ShieldId.PortScan -> "https://s10.gifyu.com/images/PortScanDefense_Degenz.gif" + | ShieldId.Encryption -> "https://s10.gifyu.com/images/Anonymous-Degenz-V2.gif" + | ShieldId.Hardening -> "https://s10.gifyu.com/images/Encryption-Degenz-V2.gif" + | ShieldId.Sanitation -> "https://s10.gifyu.com/images/VPN-Degenz.gif" + | ShieldId.Cypher -> "https://s10.gifyu.com/images/Matrix_Degenz.gif" + | _ -> shieldGif + let constructEmbed message = let builder = DiscordEmbedBuilder() @@ -36,14 +40,14 @@ let constructEmbed message = let pickDefense actionId player = let buttons = - constructButtons actionId (string player.DiscordId) player.Shields + constructButtons actionId (string player.DiscordId) (Player.shields player) |> Seq.cast let embed = DiscordEmbedBuilder() .WithColor(DiscordColor.Blurple) .WithDescription("Pick a defense to mount for 10 hours") - .WithImageUrl("https://s10.gifyu.com/images/Defense-Degenz-V2.gif") + .WithImageUrl(shieldGif) DiscordInteractionResponseBuilder() .AddComponents(buttons) @@ -52,37 +56,61 @@ let pickDefense actionId player = let pickHack actionId attacker defender = let buttons = - constructButtons actionId $"{defender.DiscordId}-{defender.Name}" attacker.Weapons + constructButtons actionId $"{defender.DiscordId}-{defender.Name}" (Player.hacks attacker) |> Seq.cast let embed = DiscordEmbedBuilder() .WithColor(DiscordColor.Blurple) .WithDescription("Pick the hack that you want to use") - .WithImageUrl("https://s10.gifyu.com/images/Hacker-Degenz-V2.gif") + .WithImageUrl(hackGif) DiscordInteractionResponseBuilder() .AddComponents(buttons) .AddEmbed(embed.Build()) .AsEphemeral true -let eventSuccessfulHack (event : ComponentInteractionCreateEventArgs) targetId prize = - let embed = - DiscordEmbedBuilder() - .WithColor(DiscordColor.Blurple) - .WithDescription("Pick the hack that you want to use") - .WithImageUrl("https://s10.gifyu.com/images/Hacker-Degenz-V2.gif") +let responseSuccessfulHack defenderName hack prize = + let embed = DiscordEmbedBuilder() + embed.ImageUrl <- getHackGif hack + DiscordInteractionResponseBuilder() + .WithContent($"Successfully hacked {defenderName} using {hack}! You just won {prize} GoodBoyTokenz!") + .AddEmbed(embed.Build()) + .AsEphemeral(true) + +let responseSuccessfulHackTrainer defenderName (hack : BattleItem) prize = + let embed = DiscordEmbedBuilder() + embed.ImageUrl <- getHackGif (enum(hack.Id)) + + DiscordFollowupMessageBuilder() + .WithContent($"Successfully hacked {defenderName} using {hack}! You just won {prize} GoodBoyTokenz!") + .AddEmbed(embed.Build()) + .AsEphemeral(true) + +let responseCreatedShield shield = + DiscordInteractionResponseBuilder() + .AddEmbed(DiscordEmbedBuilder().WithImageUrl(getShieldGif shield)) + .AsEphemeral(true) + .WithContent($"Mounted a {shield} defense for 6 hours") + +let responseCreatedShieldTrainer (shield : BattleItem) = + DiscordFollowupMessageBuilder() + .AddEmbed(DiscordEmbedBuilder().WithImageUrl(getShieldGif (enum(shield.Id)))) + .AsEphemeral(true) + .WithContent($"Mounted a {shield.Name} defense for 6 hours") + +let eventSuccessfulHack (event : ComponentInteractionCreateEventArgs) targetId prize = DiscordMessageBuilder() .WithContent($"{event.User.Username} successfully hacked <@{targetId}> for a total of {prize} GoodBoyTokenz") let eventFailedHack (event : ComponentInteractionCreateEventArgs) targetId prize = - let embed = - DiscordEmbedBuilder() - .WithColor(DiscordColor.Blurple) - .WithDescription("Pick the hack that you want to use") - .WithImageUrl("https://s10.gifyu.com/images/Hacker-Degenz-V2.gif") - +// let embed = +// DiscordEmbedBuilder() +// .WithColor(DiscordColor.Blurple) +// .WithDescription("Pick the hack that you want to use") +// .WithImageUrl(hackGif) +// DiscordMessageBuilder() .WithContent($"{event.User.Username} successfully hacked <@{targetId}> for a total of {prize} GoodBoyTokenz") @@ -96,20 +124,10 @@ type Table = { let storeListing store = let embeds = store + |> Array.groupBy (fun (bi : BattleItem) -> bi.Type) |> Array.map (fun ( itemType , items ) -> items - |> Array.map (fun (item : Item) -> - let itemClass = - if itemType = ItemType.Hack - then hackInventory - |> Array.find (fun w -> item.Name = string w) - |> int - |> getClass - else shieldInventory - |> Array.find (fun w -> item.Name = string w) - |> int - |> getClass - { Name = item.Name ; Cost = string item.Cost ; Class = string itemClass }) + |> Array.map (fun item -> { Name = item.Name ; Cost = string item.Cost ; Class = string item.Class }) |> Formatter.Format |> sprintf "**%As**\n``` %s ```" itemType |> constructEmbed) diff --git a/Bot/HackerBattle.fs b/Bot/HackerBattle.fs index 12b844b..4639040 100644 --- a/Bot/HackerBattle.fs +++ b/Bot/HackerBattle.fs @@ -25,22 +25,21 @@ let checkIfPlayerIsAttackingThemselves defender attacker = | false -> Ok attacker let checkForExistingHack defenderId attacker = - let updatedAttacks = - attacker.Attacks - |> removeExpiredActions (TimeSpan.FromHours(24)) (fun atk -> atk.Timestamp) - updatedAttacks - |> Array.tryFind (fun a -> a.Target.Id = defenderId) + attacker.Actions + |> removeExpiredActions + |> getAttacksFlat + |> Array.tryFind (fun (_,t,_) -> t.Id = defenderId) |> function - | Some attack -> - let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromHours(24)) attack.Timestamp - Error $"You can only hack the same target once every 24 hours, wait {cooldown} to attempt another hack on {attack.Target.Name}." + | Some ( atk , target , _ ) -> + let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromHours(24)) atk.Timestamp + Error $"You can only hack the same target once every 24 hours, wait {cooldown} to attempt another hack on {target.Name}." | None -> Ok attacker -let checkIfHackHasCooldown hack attacker = +let checkIfHackHasCooldown hackId attacker = let mostRecentHackAttack = - attacker.Attacks - |> Array.tryFind (fun a -> a.HackType = hack) + attacker.Actions + |> Array.tryFind (fun a -> a.ActionId = hackId) |> function | Some a -> a.Timestamp | None -> DateTime.MinValue @@ -48,42 +47,40 @@ let checkIfHackHasCooldown hack attacker = Ok attacker else let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromMinutes(5)) mostRecentHackAttack - Error $"{hack} is currently on cooldown, wait {cooldown} to use it again." + let item = armoury |> Array.find (fun i -> i.Id = hackId) + Error $"{item.Name} is currently on cooldown, wait {cooldown} to use it again." let checkIfInventoryIsEmpty attacker = - match attacker.Weapons with + match attacker.Arsenal with | [||] -> Error $"You currently do not have any Hacks to use against others. Please go to the store and purchase one." | _ -> Ok attacker -let calculateDamage (hack: int) (shield: int) = - let hackClass = getClass hack - let protectionClass = getClass shield - - match hackClass, protectionClass with - | h, p when h = p -> Weak - | _ -> Strong +let calculateDamage (hack : BattleItem) (shield : BattleItem) = + if hack.Power > shield.Power + then Strong + else Weak let runHackerBattle defender hack = - defender.Defenses - |> removeExpiredActions (TimeSpan.FromHours(6)) (fun (pro : Defense) -> pro.Timestamp) - |> Seq.toArray - |> Array.map (fun dfn -> int dfn.DefenseType) - |> Array.map (calculateDamage (int hack)) + Player.defenses defender + |> removeExpiredActions + |> Array.map (fun dfn -> armoury |> Array.find (fun w -> w.Id = dfn.ActionId)) + |> Array.map (calculateDamage (hack)) |> Array.contains Weak let updateCombatants attacker defender hack prize = - let updatePlayer amount attack p = - { p with Attacks = Array.append [| attack |] p.Attacks ; Bank = Math.Max(p.Bank + amount, 0) } - let attack = { HackType = enum(int hack) ; Timestamp = DateTime.UtcNow ; Target = { Id = defender.DiscordId ; Name = defender.Name } } + let updatePlayer amount attack p = + { p with Actions = Array.append [| attack |] p.Actions ; Bank = max (p.Bank + amount) 0 } + let target = { Id = defender.DiscordId ; Name = defender.Name } + let attack = { ActionId = int hack ; Type = Attack ( target , prize > 0 ) ; Timestamp = DateTime.UtcNow } - [ DbService.updatePlayer <| updatePlayer prize attack attacker - DbService.updatePlayer <| modifyPlayerBank defender -prize ] - |> Async.Parallel - |> Async.Ignore + [ DbService.updatePlayer <| updatePlayer prize attack attacker + DbService.updatePlayer <| modifyPlayerBank defender -prize ] + |> Async.Parallel + |> Async.Ignore let successfulHack (event : ComponentInteractionCreateEventArgs) attacker defender hack = async { - let prize = 3 + let prize = 3 do! updateCombatants attacker defender hack prize @@ -101,7 +98,7 @@ let successfulHack (event : ComponentInteractionCreateEventArgs) attacker defend let failedHack (event : ComponentInteractionCreateEventArgs) attacker defender hack = async { let builder = DiscordInteractionResponseBuilder() - let prize = 2 + let prize = 2 builder.IsEphemeral <- true builder.Content <- $"Hack failed! {defender.Name} was able to mount a successful defense! You lost {prize} GoodBoyTokenz!" do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) @@ -147,7 +144,7 @@ let attack (ctx : InteractionContext) (target : DiscordUser) = let handleAttack (event : ComponentInteractionCreateEventArgs) = async { let split = event.Id.Split("-") - let hack = Enum.Parse(typedefof, split.[1]) :?> Hack + let hack = enum(int split.[1]) let ( resultId , targetId ) = UInt64.TryParse split.[2] let! resultPlayer = DbService.tryFindPlayer event.User.Id let! resultTarget = DbService.tryFindPlayer targetId @@ -155,10 +152,10 @@ let handleAttack (event : ComponentInteractionCreateEventArgs) = match resultPlayer , resultTarget , true , resultId with | Some attacker , Some defender , true , true -> do! checkForExistingHack defender.DiscordId attacker - |> Result.bind (checkIfHackHasCooldown hack) + |> Result.bind (checkIfHackHasCooldown (int hack)) |> function | Ok _ -> - runHackerBattle defender hack + runHackerBattle defender (getItemFromArmoury <| int hack) |> function | false -> successfulHack event attacker defender hack | true -> failedHack event attacker defender hack @@ -182,13 +179,13 @@ let defend (ctx : InteractionContext) = let! player = DbService.tryFindPlayer ctx.Member.Id match player with | Some player -> - if player.Shields.Length > 0 then + if Player.defenses player |> Array.length > 0 then let embed = Embeds.pickDefense "Defend" player do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, embed) |> Async.AwaitTask else let builder = DiscordInteractionResponseBuilder() - builder.Content <- $"You currently do not have any Shields to protect your system. Please go to the store and purchase one." + builder.Content <- $"You currently do not have any Shields to protect your system. Please go to the armoury and purchase one." builder.AsEphemeral true |> ignore do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask @@ -199,20 +196,20 @@ let defend (ctx : InteractionContext) = let handleDefense (event : ComponentInteractionCreateEventArgs) = async { let split = event.Id.Split("-") - let ( shieldResult , shield ) = Shield.TryParse(split.[1]) + let shield = enum(int split.[1]) let! playerResult = DbService.tryFindPlayer event.User.Id - match playerResult , shieldResult with - | Some player , true -> - let updatedDefenses = removeExpiredActions (TimeSpan.FromHours(6)) (fun (pro : Defense) -> pro.Timestamp) player.Defenses - let alreadyUsedShield = updatedDefenses |> Array.exists (fun d -> d.DefenseType = shield) + match playerResult with + | Some player -> + let updatedDefenses = Player.defenses player |> removeExpiredActions + let alreadyUsedShield = updatedDefenses |> Array.exists (fun d -> d.ActionId = int shield) match alreadyUsedShield , updatedDefenses.Length < 2 with | false , true -> let embed = Embeds.responseCreatedShield shield do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, embed) |> Async.AwaitTask - let defense = { DefenseType = shield ; Timestamp = DateTime.UtcNow } - do! DbService.updatePlayer <| { player with Defenses = Array.append [| defense |] player.Defenses } + let defense = { ActionId = int shield ; Type = Defense ; Timestamp = DateTime.UtcNow } + do! DbService.updatePlayer <| { player with Actions = Array.append [| defense |] player.Actions } let builder = DiscordMessageBuilder() builder.WithContent($"{event.User.Username} has protected their system!") |> ignore let channel = event.Guild.GetChannel(GuildEnvironment.channelEventsHackerBattle) @@ -227,16 +224,16 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) = builder.Content <- $"You are only allowed two shields at a time. Wait {cooldown} minutes to add another shield" do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask - do! DbService.updatePlayer <| { player with Defenses = updatedDefenses } + do! DbService.updatePlayer <| { player with Actions = updatedDefenses } | true , _ -> let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true - let timestamp = updatedDefenses |> Array.find (fun d -> d.DefenseType = shield) |> fun a -> a.Timestamp + let timestamp = updatedDefenses |> Array.find (fun d -> d.ActionId = int shield) |> fun a -> a.Timestamp let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromHours(6)) timestamp builder.Content <- $"{shield} shield is already in use. Wait {cooldown} minutes to use this shield again" do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask - do! DbService.updatePlayer <| { player with Defenses = updatedDefenses } + do! DbService.updatePlayer <| { player with Actions = updatedDefenses } | _ -> let builder = DiscordInteractionResponseBuilder() diff --git a/Bot/Items.json b/Bot/Items.json index 3289061..06fba25 100644 --- a/Bot/Items.json +++ b/Bot/Items.json @@ -1,62 +1,158 @@ [ { - "Name" : "Virus", - "ItemType" : { "Case" : "Hack" }, - "Cost" : 5 + "Id": 0, + "Name": "Virus", + "Type": { + "Case": "Hack" + }, + "Class": { + "Case": "Network" + }, + "Cost": 100, + "Power": 50, + "Cooldown": 260 }, { - "Name" : "Ransom", - "ItemType" : { "Case" : "Hack" }, - "Cost" : 10 + "Id": 1, + "Name": "Ransom", + "Type": { + "Case": "Hack" + }, + "Class": { + "Case": "Network" + }, + "Cost": 100, + "Power": 50, + "Cooldown": 260 }, { - "Name" : "Worm", - "ItemType" : { "Case" : "Hack" }, - "Cost" : 5 + "Id": 2, + "Name": "Worm", + "Type": { + "Case": "Hack" + }, + "Class": { + "Case": "Network" + }, + "Cost": 100, + "Power": 50, + "Cooldown": 260 }, { - "Name" : "DDos", - "ItemType" : { "Case" : "Hack" }, - "Cost" : 10 + "Id": 3, + "Name": "DDos", + "Type": { + "Case": "Hack" + }, + "Class": { + "Case": "Network" + }, + "Cost": 100, + "Power": 50, + "Cooldown": 260 }, { - "Name" : "Crack", - "ItemType" : { "Case" : "Hack" }, - "Cost" : 5 + "Id": 4, + "Name": "Crack", + "Type": { + "Case": "Hack" + }, + "Class": { + "Case": "Network" + }, + "Cost": 100, + "Power": 50, + "Cooldown": 260 }, { - "Name" : "Injection", - "ItemType" : { "Case" : "Hack" }, - "Cost" : 10 + "Id": 5, + "Name": "Injection", + "Type": { + "Case": "Hack" + }, + "Class": { + "Case": "Network" + }, + "Cost": 100, + "Power": 50, + "Cooldown": 260 }, { - "Name" : "Firewall", - "ItemType" : { "Case" : "Shield" }, - "Cost" : 5 + "Id": 6, + "Name": "Firewall", + "Type": { + "Case": "Shield" + }, + "Class": { + "Case": "Network" + }, + "Cost": 100, + "Power": 50, + "Cooldown": 260 }, { - "Name" : "PortScan", - "ItemType" : { "Case" : "Shield" }, - "Cost" : 10 + "Id": 7, + "Name": "PortScan", + "Type": { + "Case": "Shield" + }, + "Class": { + "Case": "Network" + }, + "Cost": 100, + "Power": 50, + "Cooldown": 260 }, { - "Name" : "Cypher", - "ItemType" : { "Case" : "Shield" }, - "Cost" : 5 + "Id": 8, + "Name": "Encryption", + "Type": { + "Case": "Shield" + }, + "Class": { + "Case": "Network" + }, + "Cost": 100, + "Power": 50, + "Cooldown": 260 }, { - "Name" : "Encryption", - "ItemType" : { "Case" : "Shield" }, - "Cost" : 10 + "Id": 9, + "Name": "Hardening", + "Type": { + "Case": "Shield" + }, + "Class": { + "Case": "Network" + }, + "Cost": 100, + "Power": 50, + "Cooldown": 260 }, { - "Name" : "Sanitation", - "ItemType" : { "Case" : "Shield" }, - "Cost" : 5 + "Id": 10, + "Name": "Sanitation", + "Type": { + "Case": "Shield" + }, + "Class": { + "Case": "Network" + }, + "Cost": 100, + "Power": 50, + "Cooldown": 260 }, { - "Name" : "Hardening", - "ItemType" : { "Case" : "Shield" }, - "Cost" : 10 + "Id": 11, + "Name": "Cypher", + "Type": { + "Case": "Shield" + }, + "Class": { + "Case": "Network" + }, + "Cost": 100, + "Power": 50, + "Cooldown": 260 } ] diff --git a/Bot/PlayerInteractions.fs b/Bot/PlayerInteractions.fs index 9416897..e0196fd 100644 --- a/Bot/PlayerInteractions.fs +++ b/Bot/PlayerInteractions.fs @@ -10,7 +10,7 @@ open Degenz open Degenz.Shared module Commands = - let newPlayer nickname (membr : uint64) = +// let newPlayer nickname (membr : uint64) = // let h1 = [| Weapon.Virus ; Weapon.Ransom |] // let h2 = [| Weapon.DDos ; Weapon.Worm |] // let h3 = [| Weapon.Crack ; Weapon.Injection |] @@ -18,51 +18,49 @@ module Commands = // let d2 = [| Shield.Encryption ; Shield.Cypher |] // let d3 = [| Shield.Hardening ; Shield.Sanitation |] - let rand = System.Random(System.Guid.NewGuid().GetHashCode()) - let getRandom (actions : 'a array) = actions.[rand.Next(0, Math.Max(0, actions.Length - 1))] - - let weapons = [| getRandom hackInventory |] - let shields = [| getRandom shieldInventory |] - - { DiscordId = membr - Name = nickname - Weapons = weapons - Shields = shields - Attacks = [||] - Defenses = [||] - Bank = 15 } - - let addHackerRole (ctx : InteractionContext) = - async { - let! player = DbService.tryFindPlayer ctx.Member.Id - let! newPlayer = - match player with - | Some _ -> async.Return false - | None -> - async { - do! newPlayer ctx.Member.DisplayName ctx.Member.Id - |> DbService.insertNewPlayer +// let rand = System.Random(System.Guid.NewGuid().GetHashCode()) +// let getRandom (actions : 'a array) = actions.[rand.Next(0, max 0 (actions.Length - 1))] +// +// let weapons = [| getRandom hackInventory |] +// let shields = [| getRandom shieldInventory |] +// +// { DiscordId = membr +// Name = nickname +// Weapons = weapons +// Shields = shields +// Attacks = [||] +// Defenses = [||] +// Bank = 15 } +// let addHackerRole (ctx : InteractionContext) = +// async { +// let! player = DbService.tryFindPlayer ctx.Member.Id +// let! newPlayer = +// match player with +// | Some _ -> async.Return false +// | None -> +// async { +// do! newPlayer ctx.Member.DisplayName ctx.Member.Id +// |> DbService.insertNewPlayer +// // for role in ctx.Guild.Roles do // if role.Value.Name = "Hacker" then // do! ctx.Member.GrantRoleAsync(role.Value) // |> Async.AwaitTask +// return true +// } +// if newPlayer then +// do! ctx.CreateResponseAsync("You are now an elite haxxor", true) +// |> Async.AwaitTask +// else +// do! ctx.CreateResponseAsync("Already registered as an elite haxxor", true) +// |> Async.AwaitTask +// +// } |> Async.StartAsTask +// :> Task - return true - } - - if newPlayer then - do! ctx.CreateResponseAsync("You are now an elite haxxor", true) - |> Async.AwaitTask - else - do! ctx.CreateResponseAsync("Already registered as an elite haxxor", true) - |> Async.AwaitTask - - } |> Async.StartAsTask - :> Task - - let removeHackerRole (ctx : InteractionContext) = - async { +// let removeHackerRole (ctx : InteractionContext) = +// async { // for role in ctx.Member.Roles do // if role.Name = "Hacker" then // do! ctx.Member.RevokeRoleAsync(role) @@ -70,10 +68,10 @@ module Commands = // do! DbService.removePlayer ctx.Member.Id - do! ctx.CreateResponseAsync("You are now lame", true) - |> Async.AwaitTask - } |> Async.StartAsTask - :> Task +// do! ctx.CreateResponseAsync("You are now lame", true) +// |> Async.AwaitTask +// } |> Async.StartAsTask +// :> Task [] type LeaderboardEntry = { @@ -108,13 +106,13 @@ module Commands = let! player = DbService.tryFindPlayer ctx.Member.Id match player with | Some p -> - let updatedAttacks = p.Attacks |> removeExpiredActions (TimeSpan.FromHours(24)) (fun (atk : Attack) -> atk.Timestamp) - let updatedDefenses = p.Defenses |> removeExpiredActions (TimeSpan.FromHours(6)) (fun (p : Defense) -> p.Timestamp) - let updatedPlayer = { p with Attacks = updatedAttacks ; Defenses = updatedDefenses } - do! DbService.updatePlayer updatedPlayer +// let updatedAttacks = p.Attacks |> removeExpiredActions (TimeSpan.FromHours(24)) (fun (atk : Attack) -> atk.Timestamp) +// let updatedDefenses = p.Defenses |> removeExpiredActions (TimeSpan.FromHours(6)) (fun (p : Defense) -> p.Timestamp) +// let updatedPlayer = { p with Attacks = updatedAttacks ; Defenses = updatedDefenses } +// do! DbService.updatePlayer updatedPlayer let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true - builder.Content <- statusFormat updatedPlayer +// builder.Content <- statusFormat updatedPlayer do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask | None -> do! notYetAHackerMsg ctx @@ -124,11 +122,11 @@ module Commands = type PlayerInteractions() = inherit ApplicationCommandModule () - [] - member _.AddHackerRole (ctx : InteractionContext) = Commands.addHackerRole ctx +// [] +// member _.AddHackerRole (ctx : InteractionContext) = Commands.addHackerRole ctx - [] - member _.RemoveHackerRole (ctx : InteractionContext) = Commands.removeHackerRole ctx +// [] +// member _.RemoveHackerRole (ctx : InteractionContext) = Commands.removeHackerRole ctx [] member this.Status (ctx : InteractionContext) = Commands.status ctx diff --git a/Bot/SlotMachine.fs b/Bot/SlotMachine.fs index 5db886f..153bc14 100644 --- a/Bot/SlotMachine.fs +++ b/Bot/SlotMachine.fs @@ -26,9 +26,9 @@ type SlotMachine() = || (results.[0] <> results.[1] && results.[1] <> results.[2] && results.[0] <> results.[2]) if winConditions then - do! DbService.updatePlayer { player with Bank = player.Bank + 10 } + do! DbService.updatePlayer { player with Bank = player.Bank + 10 } else - do! DbService.updatePlayer { player with Bank = Math.Max(player.Bank - 1, 0) } + do! DbService.updatePlayer { player with Bank = max (player.Bank - 1) 0 } do! ctx.Interaction.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource) diff --git a/Bot/Store.fs b/Bot/Store.fs index 712ca85..2603b15 100644 --- a/Bot/Store.fs +++ b/Bot/Store.fs @@ -8,74 +8,32 @@ open DSharpPlus.SlashCommands open Degenz open Degenz.Embeds open Degenz.Shared -open Newtonsoft.Json - -let store = - let file = System.IO.File.ReadAllText("Items.json") - JsonConvert.DeserializeObject(file) - |> Array.groupBy (fun (i : Item) -> i.ItemType) let viewStore (ctx : InteractionContext) = async { - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, Embeds.storeListing store) + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, Embeds.storeListing armoury) |> Async.AwaitTask } |> Async.StartAsTask :> Task -let getItems itemType = store |> Array.find (fun ( t , _ ) -> t = itemType) |> snd - -let buyHack (ctx : InteractionContext) hackId = +let buyItem (ctx : InteractionContext) itemId = async { let! playerResult = DbService.tryFindPlayer ctx.Member.Id - let weapons = getItems ItemType.Hack - let weaponResult = weapons |> Array.tryFind (fun w -> w.Name = string hackId) - return! - match playerResult , weaponResult with - | Some player , Some item -> - async { - let newBalance = player.Bank - item.Cost - if newBalance >= 0 then - let playerHasItem = player.Weapons |> Array.exists (fun w -> item.Name = string w) - if not playerHasItem then - let weapon = hackInventory |> Array.find (fun w -> item.Name = string w) - let p = { player with Bank = newBalance ; Weapons = Array.append [| weapon |] player.Weapons } - do! DbService.updatePlayer p - do! createSimpleResponseAsync $"Successfully purchased {item.Name}! You now have {newBalance} remaining" ctx - else - do! createSimpleResponseAsync $"You already own this item!" ctx - else - do! createSimpleResponseAsync $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT" ctx - } - | None , _ -> notYetAHackerMsg ctx - | _ -> createSimpleResponseAsync "Something is wrong" ctx - } |> Async.StartAsTask - :> Task - -let buyShield (ctx : InteractionContext) shieldId = - async { - let! playerResult = DbService.tryFindPlayer ctx.Member.Id - let shieldResult = - getItems ItemType.Shield - |> Array.tryFind (fun w -> w.Name = string shieldId) - return! - match playerResult , shieldResult with - | Some player , Some item -> - async { - let newBalance = player.Bank - item.Cost - if newBalance >= 0 then - let playerHasItem = player.Shields |> Array.exists (fun w -> item.Name = string w) - if not playerHasItem then - let shield = shieldInventory |> Array.find (fun w -> item.Name = string w) - let p = { player with Bank = newBalance ; Shields = Array.append [| shield |] player.Shields } - do! DbService.updatePlayer p - do! createSimpleResponseAsync $"Successfully purchased {item.Name}! You now have {newBalance} remaining" ctx - else - do! createSimpleResponseAsync $"You already own this item!" ctx - else - do! createSimpleResponseAsync $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT" ctx - } - | None , _ -> notYetAHackerMsg ctx - | _ -> createSimpleResponseAsync "Something is wrong" ctx + let item = armoury |> Array.find (fun w -> w.Id = itemId) + match playerResult with + | Some player -> + let newBalance = player.Bank - item.Cost + if newBalance >= 0 then + let playerHasItem = player.Arsenal |> Array.exists (fun w -> item.Id = w.Id) + if not playerHasItem then + let p = { player with Bank = newBalance ; Arsenal = Array.append [| item |] player.Arsenal } + do! DbService.updatePlayer p + do! createSimpleResponseAsync $"Successfully purchased {item.Name}! You now have {newBalance} remaining" ctx + else + do! createSimpleResponseAsync $"You already own this item!" ctx + else + do! createSimpleResponseAsync $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT" ctx + | None -> do! notYetAHackerMsg ctx } |> Async.StartAsTask :> Task @@ -88,28 +46,19 @@ let sell (ctx : InteractionContext) = let! playerResult = DbService.tryFindPlayer ctx.Member.Id match playerResult with | Some player -> - let hasInventoryToSell = Array.length player.Weapons + Array.length player.Shields > 0 + let hasInventoryToSell = Array.length player.Arsenal > 0 if hasInventoryToSell then let builder = DiscordInteractionResponseBuilder() builder.AddEmbed (constructEmbed "Pick the item you wish to sell.") |> ignore - Array.chunkBySize 3 player.Weapons + Array.chunkBySize 5 player.Arsenal |> Array.iter (fun wps -> wps - |> Array.map (fun w -> DiscordButtonComponent(ButtonStyle.Primary, $"Hack-{w}", $"{w}")) + |> Array.map (fun w -> DiscordButtonComponent(ButtonStyle.Primary, $"{w.Type}-{w.Id}", $"{w.Name}")) |> Seq.cast |> builder.AddComponents |> ignore) - Array.chunkBySize 3 player.Shields - |> Array.iter - (fun shs -> - shs - |> Array.map (fun s -> DiscordButtonComponent(ButtonStyle.Primary, $"Shield-{s}", $"{s}")) - |> Seq.cast - |> builder.AddComponents - |> ignore) - builder.AsEphemeral true |> ignore do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) @@ -121,19 +70,16 @@ let sell (ctx : InteractionContext) = } |> Async.StartAsTask :> Task -let updateShields player salePrice updatedShields = { player with Bank = player.Bank + salePrice ; Shields = updatedShields } -let updateHacks player salePrice updatedHacks = { player with Bank = player.Bank + salePrice ; Weapons = updatedHacks } +let updateArsenal player salePrice updatedArsenal = { player with Bank = player.Bank + salePrice ; Arsenal = updatedArsenal } -let sellItem (event : ComponentInteractionCreateEventArgs) updateFn player inventory itemType itemName = +let sellItem (event : ComponentInteractionCreateEventArgs) player itemId = async { - let item = getItems itemType |> Array.find (fun i -> i.Name = itemName) - let salePrice = item.Cost - let updatedItems = inventory |> Array.filter (fun i -> string i <> itemName) - let updatedPlayer = updateFn player salePrice updatedItems + let item = armoury |> Array.find (fun i -> i.Id = itemId) + let updatedPlayer = { player with Bank = player.Bank + item.Cost ; Arsenal = player.Arsenal |> Array.filter (fun w -> w.Id = itemId)} do! DbService.updatePlayer updatedPlayer let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true - builder.Content <- $"Sold {itemType.ToString().ToLower()} {itemName} for {salePrice}! Current Balance: {updatedPlayer.Bank}" + builder.Content <- $"Sold {item.Type} {item.Name} for {item.Cost}! Current Balance: {updatedPlayer.Bank}" do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) |> Async.AwaitTask } @@ -143,12 +89,8 @@ let handleSellButtonEvents (_ : DiscordClient) (event : ComponentInteractionCrea let! playerResult = DbService.tryFindPlayer event.User.Id match playerResult with | Some player -> - let split = event.Id.Split("-") - let itemType = match split.[0] with "Hack" -> ItemType.Hack | _ -> ItemType.Shield - let itemName = split.[1] - match itemType with - | ItemType.Hack -> do! sellItem event updateHacks player player.Weapons ItemType.Hack itemName - | ItemType.Shield -> do! sellItem event updateShields player player.Shields ItemType.Shield itemName + let itemId = int <| event.Id.Split("-").[1] + do! sellItem event player itemId | None -> let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true @@ -165,12 +107,12 @@ type Store() = member _.ViewStore (ctx : InteractionContext) = viewStore ctx [] - member _.BuyHack (ctx : InteractionContext, [] hackId : Hack) = - buyHack ctx hackId + member _.BuyHack (ctx : InteractionContext, [] hackId : HackId) = + buyItem ctx (int hackId) [] - member this.BuyShield (ctx : InteractionContext, [] shieldId : Shield) = - buyShield ctx shieldId + member this.BuyShield (ctx : InteractionContext, [] shieldId : ShieldId) = + buyItem ctx (int shieldId) [] member this.SellItem (ctx : InteractionContext) = sell ctx diff --git a/Bot/Trainer.fs b/Bot/Trainer.fs index 702b0b0..b15f081 100644 --- a/Bot/Trainer.fs +++ b/Bot/Trainer.fs @@ -7,8 +7,8 @@ open DSharpPlus.EventArgs open DSharpPlus.SlashCommands open Degenz.Shared -let defaultHack = Hack.Virus -let defaultShield = Shield.Firewall +let defaultHack = armoury |> Array.find (fun i -> i.Id = int HackId.Virus) +let defaultShield = armoury |> Array.find (fun i -> i.Id = int ShieldId.Firewall) let sendInitialEmbed (client : DiscordClient) = async { @@ -32,13 +32,17 @@ let handleTrainerStep1 (event : ComponentInteractionCreateEventArgs) = do! event.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate) |> Async.AwaitTask match maybePlayer with - | Some player when not <| Array.isEmpty player.Weapons && not <| Array.isEmpty player.Shields -> + | Some player when player.Arsenal |> Array.exists (fun w -> w.Type = Hack) + && player.Arsenal |> Array.exists (fun w -> w.Type = Shield) -> let msg = "First time, eh? Beautopia is a dangerous place. I'm going to teach you how to protect yourself from other degenerates. " + "And in the process, I'll also show you how to hack some sheeple, so you can earn some cash." do! Message.sendFollowUpMessageWithButton event "Trainer-2" msg | Some player -> - let missingItem = match player.Weapons with [||] -> "Hacks" | _ -> "Shields" - let msg = $"Looks like you don't own any {missingItem}. Go to the store and purchase some before proceeding." + let missingItem = + if player.Arsenal |> Array.exists (fun w -> w.Type = Hack) + then "Shields" + else "Hacks" + let msg = $"Looks like you're missing {missingItem}. Go to the store and purchase some before proceeding." do! Message.sendFollowUpMessage event msg | None -> let msg = "An error occurred, please contact a moderator" @@ -50,7 +54,7 @@ let handleTrainerStep2 (event : ComponentInteractionCreateEventArgs) = let! result = DbService.tryFindPlayer event.User.Id match result with | Some player -> - let weaponName = player.Shields |> Array.tryHead |> Option.defaultValue defaultShield + let weaponName = Player.shields player |> Array.tryHead |> Option.defaultValue defaultShield do! Message.sendInteractionEvent event ($"First things first, let's get your system protected. Let's enable a shield to protect you from potential hackers. " + $"You currently have {weaponName} in your arsenal. To enable it and protect your system, you can use the `/defend` slash command to choose a shield." @@ -65,8 +69,8 @@ let defend (ctx : InteractionContext) = match playerResult with | Some player -> let playerWithShields = - match player.Shields with - | [||] -> { player with Shields = [| defaultShield |] } + match player.Arsenal with + | [||] -> { player with Arsenal = [| defaultShield |] } | _ -> player let embed = Embeds.pickDefense "Trainer-3" playerWithShields @@ -92,7 +96,7 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) = match result with | Some player -> let prize = 0.223f - let shield = player.Shields |> Array.tryHead |> Option.defaultValue defaultShield + let shield = player.Arsenal |> Array.tryHead |> Option.defaultValue defaultShield let embed = Embeds.responseCreatedShieldTrainer shield do! event.Interaction.CreateFollowupMessageAsync(embed) |> Async.AwaitTask |> Async.Ignore do! Async.Sleep 2000 @@ -112,7 +116,7 @@ let handleTrainerStep4 (event : ComponentInteractionCreateEventArgs) = let! result = DbService.tryFindPlayer event.User.Id match result with | Some player -> - let weaponName = player.Weapons |> Array.tryHead |> Option.defaultValue defaultHack + let weaponName = player.Arsenal |> Array.tryHead |> Option.defaultValue defaultHack do! Message.sendInteractionEvent event ($"Next why don't you try hacking me. You currently have {weaponName} equipped. To hack me and get some money, " + $" you can use the '/hack' slash command and select a user to hack, then choose the hack attack you wish to use." @@ -160,7 +164,7 @@ let handleAttack (event : ComponentInteractionCreateEventArgs) = | Some player -> let prize = 2 do! Async.Sleep 1000 - let hack = player.Weapons |> Array.tryHead |> Option.defaultValue defaultHack + let hack = player.Arsenal |> Array.tryHead |> Option.defaultValue defaultHack let embed = Embeds.responseSuccessfulHackTrainer $"<@{GuildEnvironment.botHackerBattle}>" hack prize do! event.Interaction.CreateFollowupMessageAsync(embed) |> Async.AwaitTask |> Async.Ignore do! Async.Sleep 3000 diff --git a/DbService/DbService.fs b/DbService/DbService.fs index c8809cf..ccd089b 100644 --- a/DbService/DbService.fs +++ b/DbService/DbService.fs @@ -20,11 +20,11 @@ let tryFindPlayer (id : uint64) = | p -> return p .GetValue("Player") .ToBsonDocument() - |> BsonSerializer.Deserialize + |> BsonSerializer.Deserialize |> Some } -let insertNewPlayer (player : Player) = +let insertNewPlayer (player : PlayerData) = async { let dict = [ KeyValuePair("Player" , player.ToBsonDocument() :> Object) ] do! BsonDocument(dict) @@ -32,7 +32,7 @@ let insertNewPlayer (player : Player) = |> Async.AwaitTask } -let deletePlayer (player : Player) = +let deletePlayer (player : PlayerData) = async { let dict = [ KeyValuePair("Player" , player.ToBsonDocument() :> Object) ] do! BsonDocument(dict) diff --git a/Shared/Shared.fs b/Shared/Shared.fs index 87c075c..c8cf4d3 100644 --- a/Shared/Shared.fs +++ b/Shared/Shared.fs @@ -5,17 +5,20 @@ open DSharpPlus open DSharpPlus.Entities open DSharpPlus.EventArgs open DSharpPlus.SlashCommands +open Newtonsoft.Json -type ItemType = - | Hack - | Shield +[] +type mins -type ActionClass = +[] +type GBT + +type BattleClass = | Network | Exploit | Penetration -type Hack = +type HackId = | Virus = 0 | Ransom = 1 | Worm = 2 @@ -23,30 +26,28 @@ type Hack = | Crack = 4 | Injection = 5 -type Shield = - | Firewall = 0 - | PortScan = 1 - | Encryption = 2 - | Hardening = 4 - | Sanitation = 5 - | Cypher = 3 +type ShieldId = + | Firewall = 6 + | PortScan = 7 + | Encryption = 8 + | Hardening = 9 + | Sanitation = 10 + | Cypher = 11 -[] -type Item = { +type ItemType = + | Hack + | Shield + +type BattleItem = { + Id : int Name : string - ItemType : ItemType - Cost : int + Cost : int + Type : ItemType + Class : BattleClass + Power : int + Cooldown : int } -let hackInventory = [| Hack.Virus ; Hack.Ransom ; Hack.DDos ; Hack.Worm ; Hack.Crack ; Hack.Injection |] -let shieldInventory = [| Shield.Firewall ; Shield.PortScan ; Shield.Encryption ; Shield.Cypher ; Shield.Hardening ; Shield.Sanitation |] - -let getClass = - function - | 0 | 1 -> Network - | 2 | 3 -> Exploit - | 4 | _ -> Penetration - type HackResult = | Strong | Weak @@ -54,26 +55,37 @@ type HackResult = [] type DiscordPlayer = { Id: uint64; Name: string } -[] -type Attack = - { HackType: Hack - Target: DiscordPlayer - Timestamp: DateTime } +type Attack = { + Result : bool + Target : DiscordPlayer +} + +type ActionType = + | Attack of target : DiscordPlayer * result : bool + | Defense + +type Action = + { ActionId : int + Type : ActionType + Timestamp : DateTime } [] -type Defense = - { DefenseType: Shield - Timestamp: DateTime } +type PlayerData = + { DiscordId : uint64 + Name : string + Arsenal : BattleItem array + Actions : Action array + Bank : int } -[] -type Player = - { DiscordId: uint64 - Name: string - Weapons: Hack array - Shields: Shield array - Attacks: Attack array - Defenses: Defense array - Bank: int } +module Player = + let hacks player = player.Arsenal |> Array.filter (fun bi -> bi.Type = Hack) + let shields player = player.Arsenal |> Array.filter (fun bi -> bi.Type = Hack) + let attacks player = + player.Actions + |> Array.choose (fun act -> match act.Type with Attack (t,r) -> Some (act,t,r) | Defense -> None) + let defenses player = player.Actions |> Array.filter (fun act -> match act.Type with Defense _ -> true | _ -> false) + +let getAttacksFlat actions = actions |> Array.choose (fun act -> match act.Type with Attack (t,r) -> Some (act,t,r) | Defense -> None) let createSimpleResponseAsync msg (ctx: InteractionContext) = async { @@ -91,21 +103,30 @@ let notYetAHackerMsg = let hackDescription = "" -let statusFormat player = - $"Hack Inventory: {player.Weapons |> Array.toList} -Shield Inventory: {player.Shields |> Array.toList} -Active Hacks: {player.Attacks |> Array.toList} -Active Defenses: {player.Defenses |> Array.toList} -Bank: {player.Bank}" +let statusFormat p = + $"Hacks: {Player.hacks p |> Array.toList} +Shields: {Player.defenses p |> Array.toList} +Hack Attacks: {Player.attacks p |> Array.toList} +Active Defenses: {Player.defenses p |> Array.toList} +Bank: {p.Bank}" + +let armoury = + let file = System.IO.File.ReadAllText("Items.json") + JsonConvert.DeserializeObject(file) + +let getItemFromArmoury id = armoury |> Array.find (fun w -> w.Id = id) let constructButtons (actionType: string) (playerInfo: string) (weapons: 'a array) = weapons |> Seq.map (fun hack -> DiscordButtonComponent(ButtonStyle.Primary, $"{actionType}-{hack}-{playerInfo}", $"{hack}")) -let removeExpiredActions timespan (timestamp: 'a -> DateTime) actions = - actions |> Array.filter (fun act -> DateTime.UtcNow - (timestamp act) < timespan) +let removeExpiredActions actions = + actions + |> Array.filter (fun (act : Action) -> + let item = armoury |> Array.find (fun w -> w.Id = act.ActionId) + DateTime.UtcNow - act.Timestamp < TimeSpan.FromMinutes(int item.Cooldown)) -let modifyPlayerBank player amount = { player with Bank = Math.Max(player.Bank + amount, 0) } +let modifyPlayerBank player amount = { player with Bank = max (player.Bank + amount) 0 } module Message = let sendFollowUpMessage (event : ComponentInteractionCreateEventArgs) msg = @@ -118,6 +139,18 @@ module Message = |> 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) buttonId msg = async { let builder = DiscordFollowupMessageBuilder()