diff --git a/Bot/Embeds.fs b/Bot/Embeds.fs index 4fc6fdb..f3a0524 100644 --- a/Bot/Embeds.fs +++ b/Bot/Embeds.fs @@ -121,7 +121,7 @@ let getBuyItemsEmbed (player : PlayerData) (itemType : ItemType) (store : Item a .WithThumbnail(getShieldIcon (enum(item.Id))) |> ignore embed - .AddField("Cost 💰", $"{item.Price} $GBT", true) + .AddField("Price 💰", $"{item.Price} $GBT", true) .WithTitle($"{item.Name}") |> ignore let button = diff --git a/Bot/Game.fs b/Bot/Game.fs index bfaf375..95088bd 100644 --- a/Bot/Game.fs +++ b/Bot/Game.fs @@ -68,10 +68,10 @@ module Player = let getShields (player : PlayerData) = getItems ItemType.Shield player let getHackEvents player = player.Events - |> Array.filter (fun act -> match act.Type with PlayerEventType.Hacking -> true | _ -> false || act.ItemId < 12) + |> Array.filter (fun act -> match act.Type with PlayerEventType.Hacking -> true | _ -> false && act.ItemId < 12) let getShieldEvents player = player.Events - |> Array.filter (fun act -> match act.Type with PlayerEventType.Shielding -> true | _ -> false || act.ItemId < 12) + |> Array.filter (fun act -> match act.Type with PlayerEventType.Shielding -> true | _ -> false && act.ItemId < 12) // TODO: This parameter is a result of putting the cooldown on the attack side. Put the cooldown on the defender // side and only check if it's the same target, we need to refactor Actions @@ -106,28 +106,22 @@ module Arsenal = let actionFormat (actions : PlayerEvent array) = match actions with | [||] -> "None" - | _ -> - let hacks , defenses = - actions - |> Array.filter (fun act -> act.ItemId < 12) - |> Array.partition (fun act -> match act.Type with PlayerEventType.Hacking -> true | _ -> false) - let hacks = hacks |> Array.take (min hacks.Length 10) - hacks - |> Array.append defenses + | _ -> actions |> Array.map (fun act -> - let item = Armory.getItem act.ItemId - match act.Type with - | PlayerEventType.Hacking -> - let cooldown = Messaging.getTimeText false Game.SameTargetAttackCooldown act.Timestamp - $"Hacked {act.Adversary.Name} {cooldown} ago" - | _ -> - let cooldown = Messaging.getTimeText true (System.TimeSpan.FromMinutes(int item.Cooldown)) act.Timestamp - $"{item.Name} Shield active for {cooldown}") + let item = Armory.getItem act.ItemId + match act.Type with + | PlayerEventType.Hacking -> + let cooldown = Messaging.getTimeText false Game.SameTargetAttackCooldown act.Timestamp + $"Hacked {act.Adversary.Name} with {item.Name} {cooldown} ago" + | _ -> + let cooldown = Messaging.getTimeText true (System.TimeSpan.FromMinutes(int item.Cooldown)) act.Timestamp + $"{item.Name} Shield active for {cooldown}") |> String.concat "\n" let statusFormat p = + let hacks = Player.getHackEvents p $"**Hacks:** {Player.getHacks p |> battleItemFormat}\n **Shields:** {Player.getShields p |> battleItemFormat}\n - **Hack Attacks:**\n{Player.getHackEvents p |> actionFormat}\n + **Hack Attacks:**\n{ hacks |> Array.take (min hacks.Length 10) |> actionFormat}\n **Active Shields:**\n{Player.getShieldEvents p |> actionFormat}" diff --git a/Bot/HackerBattle.fs b/Bot/HackerBattle.fs index 90200b3..d366cab 100644 --- a/Bot/HackerBattle.fs +++ b/Bot/HackerBattle.fs @@ -264,7 +264,7 @@ type HackerGame() = [] member this.AttackCommand (ctx : InteractionContext, [] target : DiscordUser) = - enforceChannels (DiscordInteractionContext ctx) (Trainer.attack target) (attack target) + enforceChannels (DiscordInteractionContext ctx) (Trainer.hack target) (attack target) [] member this.ShieldCommand (ctx : InteractionContext) = diff --git a/Bot/Items.json b/Bot/Items.json index e9c7bbc..3f3c49d 100644 --- a/Bot/Items.json +++ b/Bot/Items.json @@ -3,48 +3,84 @@ "Id": 0, "Name": "Virus", "Type": 0, - "Cost": 50, - "Power": 50, - "Cooldown": 2 + "Price": 0, + "Power": 10, + "Cooldown": 2, + "Attributes": { + "Sell": false, + "Buy": false, + "Consume": false, + "Drop": false + } }, { "Id": 1, "Name": "RemoteAccess", "Type": 0, - "Cost": 50, + "Price": 500, "Power": 50, - "Cooldown": 2 + "Cooldown": 2, + "Attributes": { + "Sell": true, + "Buy": true, + "Consume": false, + "Drop": true + } }, { "Id": 2, "Name": "Worm", "Type": 0, - "Cost": 50, - "Power": 50, - "Cooldown": 2 + "Price": 5000, + "Power": 80, + "Cooldown": 2, + "Attributes": { + "Sell": true, + "Buy": true, + "Consume": false, + "Drop": true + } }, { "Id": 6, "Name": "Firewall", "Type": 1, - "Cost": 50, - "Power": 50, - "Cooldown": 600 + "Price": 0, + "Power": 10, + "Cooldown": 600, + "Attributes": { + "Sell": false, + "Buy": false, + "Consume": false, + "Drop": false + } }, { "Id": 8, "Name": "Cypher", "Type": 1, - "Cost": 50, + "Price": 500, "Power": 50, - "Cooldown": 600 + "Cooldown": 600, + "Attributes": { + "Sell": true, + "Buy": true, + "Consume": false, + "Drop": true + } }, { "Id": 7, "Name": "Encryption", "Type": 1, - "Cost": 50, - "Power": 50, - "Cooldown": 600 + "Price": 5000, + "Power": 80, + "Cooldown": 600, + "Attributes": { + "Sell": true, + "Buy": true, + "Consume": false, + "Drop": true + } } ] diff --git a/Bot/Thief.fs b/Bot/Thief.fs index a2b0c7a..bfb6971 100644 --- a/Bot/Thief.fs +++ b/Bot/Thief.fs @@ -168,8 +168,6 @@ let handleSteal (ctx : IDiscordContext) = let stole = { ItemId = -1 ; Type = PlayerEventType.Steal ; Result = PlayerEventResult.Positive ; Adversary = dp ; Timestamp = DateTime.UtcNow } let actions = player |> Player.removeExpiredActions false |> fun p -> Array.append [| stole |] p.Events do! DbService.updatePlayer { player with Bank = player.Bank + prize ; XP = player.XP + xp ; Events = actions } - let newLevel = XP.getLevel (player.XP + xp) -// if XP.getLevel player.XP < newLevel then do! Async.Sleep 2000 do! ctx.FollowUp (XP.getRewardsEmbed 1 player) |> Async.AwaitTask | false , false -> diff --git a/Bot/Trainer.fs b/Bot/Trainer.fs index 18be935..2502a3e 100644 --- a/Bot/Trainer.fs +++ b/Bot/Trainer.fs @@ -1,16 +1,29 @@ module Degenz.Trainer open System.Text +open System.Threading.Tasks open DSharpPlus open DSharpPlus.Entities -open DSharpPlus.EventArgs open Degenz.Types open Degenz.Messaging let trainerAchievement = "FINISHED_TRAINER" - +let Sensei = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" } let defaultHack = Armory.battleItems |> Array.find (fun i -> i.Id = int HackId.Virus) -let defaultShield = Armory.battleItems |> Array.find (fun i -> i.Id = int ShieldId.Encryption) +let defaultShield = Armory.battleItems |> Array.find (fun i -> i.Id = int ShieldId.Firewall) + +let TrainerEvents = [| + { PlayerEvent.Timestamp = System.DateTime.UtcNow + PlayerEvent.Adversary = Sensei + PlayerEvent.Type = PlayerEventType.Hacking + PlayerEvent.Result = PlayerEventResult.Positive + PlayerEvent.ItemId = defaultHack.Id } + { PlayerEvent.Timestamp = System.DateTime.UtcNow + PlayerEvent.Adversary = DiscordPlayer.empty + PlayerEvent.Type = PlayerEventType.Shielding + PlayerEvent.Result = PlayerEventResult.Positive + PlayerEvent.ItemId = defaultShield.Id } +|] let sendInitialEmbed (client : DiscordClient) = async { @@ -31,42 +44,31 @@ let sendInitialEmbed (client : DiscordClient) = let handleTrainerStep1 (ctx : IDiscordContext) = Game.executePlayerAction ctx (fun player -> async { - let shieldMessage , weaponName = - if Player.getShields player |> Array.isEmpty - then $"You do not have any Shields in your arsenal, take the `{defaultShield.Name}` shield, you can use it for now.\n\n" , defaultShield.Name - else - let name = Player.getShields player |> Array.tryHead |> Option.defaultValue defaultShield |> fun w -> w.Name - $"Looks like you have `{name}` in your arsenal… 👀\n\n" , name - let role = ctx.GetGuild().GetRole(GuildEnvironment.roleTrainee) do! ctx.GetDiscordMember().GrantRoleAsync(role) |> Async.AwaitTask do! sendFollowUpMessage ctx ("Beautopia© is a dangerous place... quick, put up a SHIELD 🛡 before another Degen hacks you, and steals your 💰$GBT.\n\n" - + shieldMessage + "To enable it, you need to run the `/shield` slash command.\n\n" - + $"Type the `/shield` command now, then select - `{weaponName}`\n") + + $"Type the `/shield` command now, then select - `{defaultShield.Name}`\n") }) let defend (ctx : IDiscordContext) = - Game.executePlayerAction ctx (fun player -> async { - let playerWithShields = - match Player.getShields player with - | [||] -> { player with Inventory = [| defaultShield |] } - | _ -> player - - let embed = Embeds.pickDefense "Trainer-2" playerWithShields true - + async { + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- "Content" + do! ctx.Respond InteractionResponseType.DeferredChannelMessageWithSource builder |> Async.AwaitTask + let embed = Embeds.pickDefense "Trainer-2" { PlayerData.empty with Inventory = [| defaultShield |] } true do! ctx.FollowUp(embed) |> Async.AwaitTask - }) + } |> Async.StartAsTask :> Task -let handleDefenseMsg shieldId hackId = { +let handleDefenseMsg hackId = { ButtonId = "Trainer-3" ButtonText = "Got it" - Message = "🎉 Congratulations you successfully defended my hack!\n\n" - + $"The `{shieldId}` shield is strong against the `{hackId}` hack, so make sure to have multiple shields up to protect from multiple attacks..." - + "Shields only protect you for a LIMITED TIME, so remember to keep them mounted at all times, or risk getting hacked!" + Message = $"🎉 Congratulations you successfully defended my {hackId} hack!\n\n" + + "Shields only protect you for a LIMITED TIME, so remember to keep them mounted at all times, or risk getting hacked!" } let handleDefense (ctx : IDiscordContext) = @@ -78,41 +80,32 @@ let handleDefense (ctx : IDiscordContext) = let embed = Embeds.responseCreatedShield shield do! ctx.FollowUp embed |> Async.AwaitTask do! Async.Sleep 4000 - do! sendMessage' $"Ok, good, let me make sure that worked.\n\nI'll try to **hack** you now with **VIRUS**" + do! sendMessage' $"Ok, good, let me make sure that worked.\n\nI'll try to **hack** you now with **{defaultHack.Name}**" do! Async.Sleep 5000 - do! sendMessage' $"❌ HACKING FAILED!\n\n{player.Name} defended hack from <@{GuildEnvironment.botIdHackerBattle}>!" + do! sendMessage' $"❌ HACKING FAILED!\n\n{player.Name} defended hack from <@{Sensei.Id}>!" do! Async.Sleep 4000 - do! sendFollowUpMessageWithButton ctx (handleDefenseMsg shieldId "VIRUS") - + do! sendFollowUpMessageWithButton ctx (handleDefenseMsg defaultHack.Name) }) + let handleTrainerStep3 (ctx : IDiscordContext) = - Game.executePlayerAction ctx (fun player -> async { - let hackMessage , weaponName = - if Player.getHacks player |> Array.isEmpty - then $"You do not have any Hacks in your arsenal, take this `{defaultHack.Name}`, you can use it for now.\n\n" , defaultHack.Name - else - let name = Player.getHacks player |> Array.tryHead |> Option.defaultValue defaultHack |> fun w -> w.Name - $"Looks like you have `{name}` in your arsenal...\n\n" , name - + async { + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- "Content" + do! ctx.Respond InteractionResponseType.DeferredChannelMessageWithSource builder |> Async.AwaitTask do! sendFollowUpMessage ctx - ("Now let’s **HACK!** 💻\n\n" - + "I want you to **HACK ME**...\n\n" - + hackMessage - + "To deploy it, you need to run the `/hack` slash command.\n" - + $"Type the `/hack` command now, then choose me - <@{GuildEnvironment.botIdHackerBattle}> as your target, and select `{weaponName}`") - }) + ( "Now let’s **HACK** 💻... I want you to **HACK ME**!\n\n" + + "To **hack**, you need to run the `/hack` slash command.\n" + + $"Type the `/hack` command now, then choose me - <@{Sensei.Id}> as your target, and select `{defaultHack.Name}`") + } |> Async.StartAsTask :> Task -let attack (target : DiscordUser) (ctx : IDiscordContext) = +let hack (target : DiscordUser) (ctx : IDiscordContext) = Game.executePlayerAction ctx (fun player -> async { - let isRightTarget = target.Id = GuildEnvironment.botIdHackerBattle + let isRightTarget = target.Id = Sensei.Id match isRightTarget with | true -> - let playerWithAttacks = - match Player.getHacks player with - | [||] -> { player with Inventory = [| defaultHack |] } - | _ -> player - let bot = { player with DiscordId = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" } - let embed = Embeds.pickHack "Trainer-4" playerWithAttacks bot true + let bot = { PlayerData.empty with DiscordId = Sensei.Id ; Name = Sensei.Name } + let embed = Embeds.pickHack "Trainer-4" { player with Inventory = [| defaultHack |] } bot true do! ctx.FollowUp(embed) |> Async.AwaitTask | false -> @@ -124,12 +117,11 @@ let attack (target : DiscordUser) (ctx : IDiscordContext) = do! ctx.FollowUp(builder) |> Async.AwaitTask }) -let handleAttack (ctx : IDiscordContext) = +let handleHack (ctx : IDiscordContext) = Game.executePlayerAction ctx (fun player -> async { let sendMessage' = sendFollowUpMessage ctx do! Async.Sleep 1000 - let hack = Player.getHacks player |> Array.tryHead |> Option.defaultValue defaultHack - let embed = Embeds.responseSuccessfulHack false GuildEnvironment.botIdHackerBattle hack + let embed = Embeds.responseSuccessfulHack false Sensei.Id defaultHack do! ctx.FollowUp(embed) |> Async.AwaitTask do! Async.Sleep 5000 do! sendMessage' @@ -146,53 +138,14 @@ let handleAttack (ctx : IDiscordContext) = if isFirstTrainer then do! DbService.addAchievement player.DiscordId trainerAchievement - let hasHacks = Player.getHacks player |> Array.isEmpty |> not - let hasShields = Player.getShields player |> Array.isEmpty |> not - - let rand = System.Random(System.Guid.NewGuid().GetHashCode()) - let freeHack = Armory.hacks.[rand.Next(0, 3)] - let freeShield = Armory.shields.[rand.Next(0, 3)] - let hackMoney = if hasHacks then defaultHack.Price else 0 - let shieldMoney = if hasShields then defaultShield.Price else 0 - - let giftMsg = - match hasHacks , hasShields with - | true , true -> $"I'm going to give you these {hackMoney + shieldMoney} 💰$GBT" - | false , true -> $"I'm going to gift you a hack, `{freeHack.Name}` and {defaultHack.Price} 💰$GBT" - | true , false -> $"I'm going to gift you a shield, `{freeShield.Name}` and {defaultHack.Price} 💰$GBT" - | false , false -> $"I'm going to gift you a hack,`{freeHack.Name}` and a shield, `{freeShield.Name}`" - - sb.Append(giftMsg) |> ignore + sb.Append($"I'm going to gift you a hack,`{defaultHack.Name}` and a shield, `{defaultShield.Name}`") |> ignore sb.Append(", you'll need em to survive\n\n") |> ignore sb.AppendLine("To finish your training and collect the loot, type the `/arsenal` command **NOW**") |> ignore do! Async.Sleep 1000 - let updatedPlayer = { - player with - Bank = player.Bank + 100 - Events = [ - { PlayerEvent.Timestamp = System.DateTime.UtcNow - PlayerEvent.Adversary = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" } - PlayerEvent.Type = PlayerEventType.Shielding - PlayerEvent.Result = PlayerEventResult.Positive - PlayerEvent.ItemId = defaultHack.Id } - if not hasShields && Array.exists (fun act -> act.ItemId = freeShield.Id) player.Events |> not then { - PlayerEvent.Timestamp = System.DateTime.UtcNow - PlayerEvent.Adversary = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" } - PlayerEvent.Type = PlayerEventType.Shielding - PlayerEvent.Result = PlayerEventResult.Positive - PlayerEvent.ItemId = defaultHack.Id } - ] |> Seq.toArray - |> Array.append player.Events - Inventory = [ - if not hasHacks then freeHack - if not hasShields then freeShield - ] |> Seq.toArray - |> Array.append player.Inventory - } - do! DbService.updatePlayer updatedPlayer + do! sendFollowUpMessage ctx (sb.ToString()) else - do! sendFollowUpMessage ctx ($"Your training is now complete. If you want to buy more **HACKS & SHIELDS**, go to the <#{GuildEnvironment.channelArmory}> **NOW** and type the `/buy-hack` and `/buy-shield` commands! 😱") + do! sendFollowUpMessage ctx ($"Your training is now complete. If you want to buy more **HACKS & SHIELDS**, go to the <#{GuildEnvironment.channelArmory}> and type the `/buy-hack` and `/buy-shield` commands!") let role = ctx.GetGuild().GetRole(GuildEnvironment.roleTrainee) do! ctx.GetDiscordMember().RevokeRoleAsync(role) |> Async.AwaitTask @@ -200,12 +153,28 @@ let handleAttack (ctx : IDiscordContext) = let handleArsenal (ctx : IDiscordContext) = Game.executePlayerAction ctx (fun player -> async { + let hasStockWeapons = Player.getHacks player |> Array.exists (fun item -> item.Id = defaultHack.Id) let updatedPlayer = Player.removeExpiredActions false player - let embed = Embeds.getArsenalEmbed updatedPlayer - do! ctx.FollowUp(embed) |> Async.AwaitTask - do! Async.Sleep 3000 - let embed = Embeds.getAchievementEmbed "You completed the Training Dojo and collected loot." trainerAchievement + let newPlayer = + if not hasStockWeapons then { + updatedPlayer with + Events = if not (player.Events |> Array.exists (fun act -> act.Adversary = Sensei)) + then TrainerEvents + else [||] + |> Array.append player.Events + Inventory = if not hasStockWeapons then [| defaultHack ; defaultShield |] else Array.empty + |> Array.append player.Inventory + } + else + updatedPlayer + if not hasStockWeapons then + do! DbService.updatePlayer newPlayer + let embed = Embeds.getArsenalEmbed newPlayer do! ctx.FollowUp(embed) |> Async.AwaitTask + if not (player.Achievements |> Array.contains trainerAchievement) then + do! Async.Sleep 3000 + let embed = Embeds.getAchievementEmbed "You completed the Training Dojo and collected loot." trainerAchievement + do! ctx.FollowUp(embed) |> Async.AwaitTask do! Async.Sleep 2000 let role = ctx.GetGuild().GetRole(GuildEnvironment.roleTrainee) do! ctx.GetDiscordMember().RevokeRoleAsync(role) |> Async.AwaitTask @@ -220,7 +189,7 @@ let handleButtonEvent (ctx : IDiscordContext) = | 1 -> do! handleTrainerStep1 ctx |> Async.AwaitTask | 2 -> do! handleDefense ctx |> Async.AwaitTask | 3 -> do! handleTrainerStep3 ctx |> Async.AwaitTask - | 4 -> do! handleAttack ctx |> Async.AwaitTask + | 4 -> do! handleHack ctx |> Async.AwaitTask | _ -> do! sendFollowUpMessage ctx "No action found" } diff --git a/DbService/DbService.fs b/DbService/DbService.fs index 6cf7fa4..f4bf795 100644 --- a/DbService/DbService.fs +++ b/DbService/DbService.fs @@ -69,6 +69,6 @@ let updatePlayer (player : PlayerData) = let addAchievement (id : uint64) (achievement : string) = async { let filter = Builders.Filter.Eq("Player.DiscordId", id) - let update = Builders.Update.Push("Achievements", achievement) + let update = Builders.Update.Push("Player.Achievements", achievement) return! players.UpdateOneAsync(filter, update) |> Async.AwaitTask |> Async.Ignore } \ No newline at end of file diff --git a/Shared/Shared.fs b/Shared/Shared.fs index 2d9f4fa..fe64861 100644 --- a/Shared/Shared.fs +++ b/Shared/Shared.fs @@ -49,9 +49,17 @@ module Types = type ItemType = | Hack = 0 | Shield = 1 - | Consumable = 1 + | Food = 1 + + type ItemAttributes = { + Sell : bool + Buy : bool + Consume : bool + Drop : bool + } + with static member empty = { Sell = false ; Buy = false ; Consume = false ; Drop = false } + - [] type Item = { Id : int Name : string @@ -59,6 +67,7 @@ module Types = Type : ItemType Power : int Cooldown : int + Attributes : ItemAttributes } type HackResult = @@ -109,6 +118,15 @@ module Types = Bank : int } with member this.basicPlayer = { Id = this.DiscordId ; Name = this.Name } + static member empty = + { DiscordId = 0uL + Name = "None" + Inventory = [||] + Events = [||] + Traits = PlayerTraits.empty + Achievements = [||] + XP = 0 + Bank = 0 } module Armory = let battleItems =