diff --git a/Bot/Bot.fs b/Bot/Bot.fs index 98ac128..e6edc53 100644 --- a/Bot/Bot.fs +++ b/Bot/Bot.fs @@ -26,7 +26,6 @@ for conf in configs do conf.TokenType <- TokenType.Bot conf.Intents <- DiscordIntents.All - let guild = GuildEnvironment.guildId playerInteractionsConfig.Token <- GuildEnvironment.tokenPlayerInteractions diff --git a/Bot/Embeds.fs b/Bot/Embeds.fs index 14426c5..674766c 100644 --- a/Bot/Embeds.fs +++ b/Bot/Embeds.fs @@ -84,7 +84,7 @@ let responseSuccessfulHackTrainer defenderName (hack : BattleItem) prize = embed.ImageUrl <- getHackGif (enum(hack.Id)) DiscordFollowupMessageBuilder() - .WithContent($"Successfully hacked {defenderName} using {hack}! You just won {prize} GoodBoyTokenz!") + .WithContent($"Successfully hacked {defenderName} using {hack.Name}! You just won {prize} GoodBoyTokenz!") .AddEmbed(embed.Build()) .AsEphemeral(true) diff --git a/Bot/HackerBattle.fs b/Bot/HackerBattle.fs index 4639040..ab7e659 100644 --- a/Bot/HackerBattle.fs +++ b/Bot/HackerBattle.fs @@ -8,6 +8,7 @@ open DSharpPlus.EventArgs open DSharpPlus.SlashCommands open Degenz open Degenz.Shared +open Degenz.Store let getTimeTillCooldownFinishes (timespan : TimeSpan) timestamp = let timeRemaining = timespan - (DateTime.UtcNow - timestamp) @@ -42,7 +43,7 @@ let checkIfHackHasCooldown hackId attacker = |> Array.tryFind (fun a -> a.ActionId = hackId) |> function | Some a -> a.Timestamp - | None -> DateTime.MinValue + | None -> DateTime.MinValue; if DateTime.UtcNow - mostRecentHackAttack > TimeSpan.FromMinutes(5) then Ok attacker else @@ -56,7 +57,7 @@ let checkIfInventoryIsEmpty attacker = | _ -> Ok attacker let calculateDamage (hack : BattleItem) (shield : BattleItem) = - if hack.Power > shield.Power + if hack.Class = shield.Class then Strong else Weak @@ -71,7 +72,7 @@ let updateCombatants attacker defender hack prize = 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 } + let attack = { ActionId = int hack ; Type = Attack { Target = target ; Result = prize > 0 } ; Timestamp = DateTime.UtcNow } [ DbService.updatePlayer <| updatePlayer prize attack attacker DbService.updatePlayer <| modifyPlayerBank defender -prize ] @@ -179,7 +180,7 @@ let defend (ctx : InteractionContext) = let! player = DbService.tryFindPlayer ctx.Member.Id match player with | Some player -> - if Player.defenses player |> Array.length > 0 then + if Player.shields player |> Array.length > 0 then let embed = Embeds.pickDefense "Defend" player do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, embed) |> Async.AwaitTask diff --git a/Bot/Items.json b/Bot/Items.json index 06fba25..de9142d 100644 --- a/Bot/Items.json +++ b/Bot/Items.json @@ -32,7 +32,7 @@ "Case": "Hack" }, "Class": { - "Case": "Network" + "Case": "Exploit" }, "Cost": 100, "Power": 50, @@ -45,7 +45,7 @@ "Case": "Hack" }, "Class": { - "Case": "Network" + "Case": "Exploit" }, "Cost": 100, "Power": 50, @@ -58,7 +58,7 @@ "Case": "Hack" }, "Class": { - "Case": "Network" + "Case": "Penetration" }, "Cost": 100, "Power": 50, @@ -71,7 +71,7 @@ "Case": "Hack" }, "Class": { - "Case": "Network" + "Case": "Penetration" }, "Cost": 100, "Power": 50, @@ -110,7 +110,7 @@ "Case": "Shield" }, "Class": { - "Case": "Network" + "Case": "Exploit" }, "Cost": 100, "Power": 50, @@ -123,7 +123,7 @@ "Case": "Shield" }, "Class": { - "Case": "Network" + "Case": "Penetration" }, "Cost": 100, "Power": 50, @@ -136,7 +136,7 @@ "Case": "Shield" }, "Class": { - "Case": "Network" + "Case": "Penetration" }, "Cost": 100, "Power": 50, @@ -149,7 +149,7 @@ "Case": "Shield" }, "Class": { - "Case": "Network" + "Case": "Exploit" }, "Cost": 100, "Power": 50, diff --git a/Bot/PlayerInteractions.fs b/Bot/PlayerInteractions.fs index e0196fd..b7b00a3 100644 --- a/Bot/PlayerInteractions.fs +++ b/Bot/PlayerInteractions.fs @@ -1,77 +1,48 @@ module Degenz.PlayerInteractions -open System open System.Threading.Tasks -open AsciiTableFormatter open DSharpPlus.Entities open DSharpPlus open DSharpPlus.SlashCommands -open Degenz +open Degenz.Store open Degenz.Shared module Commands = -// let newPlayer nickname (membr : uint64) = -// let h1 = [| Weapon.Virus ; Weapon.Ransom |] -// let h2 = [| Weapon.DDos ; Weapon.Worm |] -// let h3 = [| Weapon.Crack ; Weapon.Injection |] -// let d1 = [| Shield.Firewall ; Shield.PortScan |] -// let d2 = [| Shield.Encryption ; Shield.Cypher |] -// let d3 = [| Shield.Hardening ; Shield.Sanitation |] + let newPlayer nickname (membr : uint64) = -// 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 rand = System.Random(System.Guid.NewGuid().GetHashCode()) + let randHack = rand.Next(0, 3) + let randShield = rand.Next(3, 6) + let hack = armoury |> Array.find (fun i -> i.Id = randHack) + let shield = armoury |> Array.find (fun i -> i.Id = randShield) -// 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 + { DiscordId = membr + Name = nickname + Arsenal = [| hack ; shield |] + Actions = [||] + Bank = 100 } -// let removeHackerRole (ctx : InteractionContext) = -// async { -// for role in ctx.Member.Roles do -// if role.Name = "Hacker" then -// do! ctx.Member.RevokeRoleAsync(role) -// |> Async.AwaitTask + 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 + 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 -// do! DbService.removePlayer ctx.Member.Id - -// do! ctx.CreateResponseAsync("You are now lame", true) -// |> Async.AwaitTask -// } |> Async.StartAsTask -// :> Task + } |> Async.StartAsTask + :> Task [] type LeaderboardEntry = { @@ -103,18 +74,17 @@ module Commands = let status (ctx : InteractionContext) = async { - 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! maybePlayer = DbService.tryFindPlayer ctx.Member.Id + match maybePlayer with + | Some player -> + let updatedActions = removeExpiredActions player.Actions + let updatedPlayer = { player with Actions = updatedActions } let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true -// builder.Content <- statusFormat updatedPlayer + builder.Content <- statusFormat updatedPlayer do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask + do! DbService.updatePlayer updatedPlayer | None -> do! notYetAHackerMsg ctx } |> Async.StartAsTask :> Task @@ -122,11 +92,8 @@ module Commands = type PlayerInteractions() = inherit ApplicationCommandModule () -// [] -// member _.AddHackerRole (ctx : InteractionContext) = Commands.addHackerRole ctx - -// [] -// member _.RemoveHackerRole (ctx : InteractionContext) = Commands.removeHackerRole ctx + [] + member _.AddHackerRole (ctx : InteractionContext) = Commands.addHackerRole ctx [] member this.Status (ctx : InteractionContext) = Commands.status ctx diff --git a/Bot/Store.fs b/Bot/Store.fs index 2603b15..28fe2a8 100644 --- a/Bot/Store.fs +++ b/Bot/Store.fs @@ -1,5 +1,6 @@ module Degenz.Store +open System open System.Threading.Tasks open DSharpPlus.Entities open DSharpPlus @@ -8,6 +9,15 @@ open DSharpPlus.SlashCommands open Degenz open Degenz.Embeds open Degenz.Shared +open Newtonsoft.Json + +let getItemFromArmoury id = armoury |> Array.find (fun w -> w.Id = id) + +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 viewStore (ctx : InteractionContext) = async { diff --git a/Bot/Trainer.fs b/Bot/Trainer.fs index b15f081..3c30768 100644 --- a/Bot/Trainer.fs +++ b/Bot/Trainer.fs @@ -6,6 +6,7 @@ open DSharpPlus.Entities open DSharpPlus.EventArgs open DSharpPlus.SlashCommands open Degenz.Shared +open Degenz.Store let defaultHack = armoury |> Array.find (fun i -> i.Id = int HackId.Virus) let defaultShield = armoury |> Array.find (fun i -> i.Id = int ShieldId.Firewall) @@ -54,7 +55,7 @@ let handleTrainerStep2 (event : ComponentInteractionCreateEventArgs) = let! result = DbService.tryFindPlayer event.User.Id match result with | Some player -> - let weaponName = Player.shields player |> Array.tryHead |> Option.defaultValue defaultShield + let weaponName = Player.shields player |> Array.tryHead |> Option.defaultValue defaultShield |> fun w -> w.Name 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." @@ -92,11 +93,11 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) = async { do! event.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate) |> Async.AwaitTask - let! result = DbService.tryFindPlayer event.User.Id - match result with + let! maybePlayer = DbService.tryFindPlayer event.User.Id + match maybePlayer with | Some player -> let prize = 0.223f - let shield = player.Arsenal |> Array.tryHead |> Option.defaultValue defaultShield + let shield = Player.shields player |> Array.tryHead |> Option.defaultValue defaultShield let embed = Embeds.responseCreatedShieldTrainer shield do! event.Interaction.CreateFollowupMessageAsync(embed) |> Async.AwaitTask |> Async.Ignore do! Async.Sleep 2000 @@ -116,7 +117,7 @@ let handleTrainerStep4 (event : ComponentInteractionCreateEventArgs) = let! result = DbService.tryFindPlayer event.User.Id match result with | Some player -> - let weaponName = player.Arsenal |> Array.tryHead |> Option.defaultValue defaultHack + let weaponName = Player.hacks player |> Array.tryHead |> Option.defaultValue defaultShield |> fun w -> w.Name 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." diff --git a/DbService/DbService.fs b/DbService/DbService.fs index ccd089b..6dfb54a 100644 --- a/DbService/DbService.fs +++ b/DbService/DbService.fs @@ -7,6 +7,68 @@ open MongoDB.Bson open MongoDB.Bson.Serialization open MongoDB.Driver +[] +type AttackAction = + { ActionId : int + Result : bool + Target : DiscordPlayer + Timestamp : DateTime } + +[] +type DefenseAction = + { ActionId : int + Timestamp : DateTime } + +[] +type PlayerEntry = + { DiscordId : uint64 + Name : string + Arsenal : int array + Attacks : AttackAction array + Defenses : DefenseAction array + Bank : int } + +let private actionToAttack (action : Action) (hack : AttackResult) = + { ActionId = action.ActionId + Result = hack.Result + Target = hack.Target + Timestamp = action.Timestamp } + +let private actionToDefense (action : Action) = + { ActionId = action.ActionId + Timestamp = action.Timestamp } + +let private attackToAction (attack : AttackAction) = + { ActionId = attack.ActionId + Type = Attack { Target = attack.Target ; Result = attack.Result } + Timestamp = attack.Timestamp } + +let private defenseToAction (action : DefenseAction) = + { ActionId = action.ActionId + Type = Defense + Timestamp = action.Timestamp } + +let private playerMap (player : PlayerData) = { + DiscordId = player.DiscordId + Name = player.Name + Arsenal = player.Arsenal |> Array.map (fun w -> w.Id) + Attacks = [||] + Defenses = [||] + Bank = int player.Bank +} + +let private mapBack (player : PlayerEntry) : PlayerData = { + DiscordId = player.DiscordId + Name = player.Name + Arsenal = player.Arsenal |> Array.map (fun w -> armoury |> Array.find (fun w' -> w = w'.Id)) + Actions = + let atks = player.Attacks |> Array.map attackToAction + let dfns = player.Defenses |> Array.map defenseToAction + Array.append atks dfns + Bank = player.Bank * 1 +} + + let mongo = MongoClient(Environment.GetEnvironmentVariable("CONN_STRING")) let db = mongo.GetDatabase("degenz") let players = db.GetCollection("players") @@ -20,30 +82,32 @@ let tryFindPlayer (id : uint64) = | p -> return p .GetValue("Player") .ToBsonDocument() - |> BsonSerializer.Deserialize + |> BsonSerializer.Deserialize + |> mapBack |> Some } let insertNewPlayer (player : PlayerData) = async { - let dict = [ KeyValuePair("Player" , player.ToBsonDocument() :> Object) ] + let p = playerMap player + let dict = [ KeyValuePair("Player" , p.ToBsonDocument() :> Object) ] do! BsonDocument(dict) |> players.InsertOneAsync |> Async.AwaitTask } -let deletePlayer (player : PlayerData) = - async { - let dict = [ KeyValuePair("Player" , player.ToBsonDocument() :> Object) ] - do! BsonDocument(dict) - |> players.InsertOneAsync - |> Async.AwaitTask - } - -let updatePlayer player = +//let deletePlayer (player : PlayerData) = +// async { +// let dict = [ KeyValuePair("Player" , player.ToBsonDocument() :> Object) ] +// do! BsonDocument(dict) +// |> players.InsertOneAsync +// |> Async.AwaitTask +// } +// +let updatePlayer (player : PlayerData) = async { let filter = Builders.Filter.Eq("Player.DiscordId", player.DiscordId) - let update = Builders.Update.Set("Player", player) + let update = Builders.Update.Set("Player", playerMap player) return! players.UpdateOneAsync(filter, update) |> Async.AwaitTask |> Async.Ignore } diff --git a/Shared/Shared.fs b/Shared/Shared.fs index c8cf4d3..ed90a9d 100644 --- a/Shared/Shared.fs +++ b/Shared/Shared.fs @@ -55,15 +55,17 @@ type HackResult = [] type DiscordPlayer = { Id: uint64; Name: string } -type Attack = { +[] +type AttackResult = { Result : bool Target : DiscordPlayer } type ActionType = - | Attack of target : DiscordPlayer * result : bool + | Attack of AttackResult | Defense +[] type Action = { ActionId : int Type : ActionType @@ -79,13 +81,13 @@ type PlayerData = 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 shields player = player.Arsenal |> Array.filter (fun bi -> bi.Type = Shield) let attacks player = player.Actions - |> Array.choose (fun act -> match act.Type with Attack (t,r) -> Some (act,t,r) | Defense -> None) + |> Array.choose (fun act -> match act.Type with Attack ar -> Some (act,ar.Target,ar.Result) | 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 getAttacksFlat actions = actions |> Array.choose (fun act -> match act.Type with Attack ar -> Some (act,ar.Target,ar.Result) | Defense -> None) let createSimpleResponseAsync msg (ctx: InteractionContext) = async { @@ -105,29 +107,21 @@ let hackDescription = "" let statusFormat p = $"Hacks: {Player.hacks p |> Array.toList} -Shields: {Player.defenses p |> Array.toList} +Shields: {Player.shields p |> Array.toList} Hack Attacks: {Player.attacks p |> Array.toList} Active Defenses: {Player.defenses p |> Array.toList} Bank: {p.Bank}" +let constructButtons (actionType: string) (playerInfo: string) (weapons: BattleItem array) = + weapons + |> Array.map (fun w -> DiscordButtonComponent(ButtonStyle.Primary, $"{actionType}-{w.Id}-{playerInfo}", $"{w.Name}")) + +let modifyPlayerBank player amount = { player with Bank = max (player.Bank + amount) 0 } + 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 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 = max (player.Bank + amount) 0 } - module Message = let sendFollowUpMessage (event : ComponentInteractionCreateEventArgs) msg = async {