From ed512bd39463ebcf7041db9ee18e0f16446bb651 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Thu, 10 Mar 2022 14:36:12 +0700 Subject: [PATCH] Gift GBT when completing training, don't let new players be hacked --- Bot/GameTypes.fs | 9 +++ Bot/Games/HackerBattle.fs | 25 +++++--- Bot/Games/Trainer.fs | 121 ++++++++++++++++++----------------- Bot/Items.json | 131 +++++++++++++++++++++++++++++++++----- Bot/Prelude.fs | 2 +- 5 files changed, 202 insertions(+), 86 deletions(-) diff --git a/Bot/GameTypes.fs b/Bot/GameTypes.fs index 90fe63a..17896fe 100644 --- a/Bot/GameTypes.fs +++ b/Bot/GameTypes.fs @@ -89,10 +89,19 @@ type ItemType = | Food | Accessory +type ItemAttributes = { + CanBuy : bool + CanSell : bool + CanConsume : bool + CanTrade : bool + CanDrop : bool +} + type Item = { Id : int Name : string Price : int + Attributes : ItemAttributes } type HackItem = { diff --git a/Bot/Games/HackerBattle.fs b/Bot/Games/HackerBattle.fs index 7c5bf30..3be1264 100644 --- a/Bot/Games/HackerBattle.fs +++ b/Bot/Games/HackerBattle.fs @@ -41,12 +41,12 @@ let checkWeaponHasCooldown (weapon : Item) attacker = |> function | Some event -> let cooldown = getTimeText true (TimeSpan.FromMinutes(int event.Cooldown)) event.Timestamp - Error $"{weapon.Name} is still active, it will expire in {cooldown}." + Error $"{weapon.Name} was recently used, wait another {cooldown} to use it again." | None -> Ok attacker -let checkHasEmptyHacks attacker = +let checkHasEmptyHacks (attacker : PlayerData) = match Inventory.getHacks attacker.Inventory with - | [] -> Error $"You currently do not have any Hacks to take 💰$GBT from others. Please go to the <#{GuildEnvironment.channelArmory}> and purchase one." + | [] -> Error $"You currently do not have any Hacks to take 💰$GBT from others. Please go to the <#{GuildEnvironment.channelTraining}> and complete the training." | _ -> Ok attacker let checkPlayerOwnsWeapon (item : Item) player = @@ -69,16 +69,21 @@ let checkTargetHasFunds target player = | true -> Error $"Looks like the poor bastard has no $GBT... pick a different victim." | false -> Ok player +let checkTargetCompletedTraining target (player : PlayerData) = async { + let! targetCompletedTraining = DbService.checkHasAchievement target.DiscordId Trainer.TrainerAchievement + if targetCompletedTraining || not target.Inventory.IsEmpty + then return Ok player + else return Error $"Looks like they haven't completed training with Sensei yet, you can't hack noobs..." +} + let runHackerBattle defender (hack : HackItem) = defender |> Player.removeExpiredActions |> fun p -> p.Events - |> List.choose (fun event -> + |> List.exists (fun event -> match event.Type with - | Shielding id -> defender.Inventory |> Inventory.getShields |> List.tryFind (fun item -> item.Item.Id = id) - | _ -> None) - |> List.map (fun shield -> if hack.Class = shield.Class then Weak else Strong) - |> List.contains Weak + | Shielding id -> hack.Class = (Inventory.findShieldById id Armory.weapons).Class + | _ -> false) let updateCombatants successfulHack (attacker : PlayerData) (defender : PlayerData) (hack : HackItem) prize = let updatePlayer amount attack p = @@ -134,9 +139,11 @@ let failedHack (ctx : IDiscordContext) attacker defender (hack : HackItem) = let hack (target : DiscordUser) (ctx : IDiscordContext) = executePlayerActionWithTarget target ctx (fun attacker defender -> async { + let! result = checkTargetCompletedTraining defender attacker do! attacker |> Player.removeExpiredActions |> checkAlreadyHackedTarget defender + >>= fun p -> match result with Ok _ -> Ok p | Error e -> Error e >>= checkTargetHasFunds defender >>= checkHasEmptyHacks >>= checkPlayerIsAttackingThemselves defender @@ -180,7 +187,7 @@ let defend (ctx : IDiscordContext) = let embed = Embeds.pickDefense "Defend" p false do! ctx.FollowUp embed |> Async.AwaitTask else - let msg = $"You currently do not have any Shields to protect yourself from hacks. Please go to the <#{GuildEnvironment.channelArmory}> and purchase one." + let msg = $"You currently do not have any Shields to protect yourself from hacks. Please go to the <#{GuildEnvironment.channelTraining}> and purchase one." do! Messaging.sendFollowUpMessage ctx msg }) diff --git a/Bot/Games/Trainer.fs b/Bot/Games/Trainer.fs index 2459bdd..8572f60 100644 --- a/Bot/Games/Trainer.fs +++ b/Bot/Games/Trainer.fs @@ -7,10 +7,12 @@ open DSharpPlus.Entities open Degenz.Types open Degenz.Messaging -let trainerAchievement = "FINISHED_TRAINER" +let TrainerAchievement = "FINISHED_TRAINER" let Sensei = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" } let defaultHack = Armory.weapons |> Inventory.findHackById (int ItemId.Virus) let defaultShield = Armory.weapons |> Inventory.findShieldById (int ItemId.Firewall) +let CurrencyGift = 250 +let BeginnerProtectionHours = 24 let HackEvent () = { Timestamp = System.DateTime.UtcNow @@ -22,11 +24,17 @@ let HackEvent () = { HackId = defaultHack.Item.Id } } -let ShieldEvent () = { - Timestamp = System.DateTime.UtcNow - Cooldown = defaultShield.Cooldown - Type = Shielding defaultShield.Item.Id -} +let ShieldEvents () = [ + { Timestamp = System.DateTime.UtcNow + Cooldown = BeginnerProtectionHours * 60 + Type = Shielding (int ItemId.Firewall) } + { Timestamp = System.DateTime.UtcNow + Cooldown = BeginnerProtectionHours * 60 + Type = Shielding (int ItemId.Encryption) } + { Timestamp = System.DateTime.UtcNow + Cooldown = BeginnerProtectionHours * 60 + Type = Shielding (int ItemId.Cypher) } +] let sendInitialEmbed (client : DiscordClient) = async { @@ -149,77 +157,70 @@ let handleHack (ctx : IDiscordContext) = let sb = StringBuilder("Here, ") - let! completed = DbService.checkHasAchievement player.DiscordId trainerAchievement + let! completed = DbService.checkHasAchievement player.DiscordId TrainerAchievement if not completed then - do! DbService.addAchievement player.DiscordId trainerAchievement - |> Async.Ignore - - sb.Append($"I'm going to gift you a hack,`{defaultHack.Item.Name}` and a shield, `{defaultShield.Item.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 + sb.Append($"I'm going to gift you a hack,`{defaultHack.Item.Name}`, a shield, `{defaultShield.Item.Name}`, and `{CurrencyGift} 💰$GBT`") |> ignore + sb.Append(", you'll need this to survive\n\n") |> ignore + sb.Append($"I'm also going to give you some **TEMPORARY SHIELDS** to protect you for the next **{BeginnerProtectionHours} hours**\n\n") |> ignore + sb.AppendLine("To finish your training and collect your prizes, type the `/arsenal` command **NOW**") |> ignore do! Async.Sleep 1000 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}> and type the `/buy-hack` and `/buy-shield` commands!") - let role = ctx.GetGuild().GetRole(GuildEnvironment.roleTrainee) - do! ctx.GetDiscordMember().RevokeRoleAsync(role) - |> Async.AwaitTask }) -let handleArsenal (ctx : IDiscordContext) = - PlayerInteractions.executePlayerAction ctx (fun player -> async { - let hack = - if player.Inventory |> List.exists (fun i -> i.Id = defaultHack.Item.Id) - then [] - else [ Hack defaultHack ] - let shield = - if player.Inventory |> List.exists (fun i -> i.Id = defaultShield.Item.Id) - then [] - else [ Shield defaultShield ] - let shieldEvent = - let hasShield = - player - |> Player.removeExpiredActions - |> fun p -> p.Events - |> List.exists (fun e -> match e.Type with Shielding shieldId -> shieldId = defaultShield.Item.Id | _ -> false) - if hasShield - then [] - else [ ShieldEvent() ] +let handleArsenal (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction ctx (fun player -> async { + let! completed = DbService.checkHasAchievement player.DiscordId TrainerAchievement + if not completed then + do! DbService.addAchievement player.DiscordId TrainerAchievement |> Async.Ignore + let addIfDoesntExist (weapon : ItemDetails) (inventory : Inventory) = + if inventory |> List.exists (fun i -> i.Id = weapon.Id) + then inventory + else weapon::inventory let updatedPlayer = { - Player.removeExpiredActions player with - Events = shieldEvent @ player.Events - Inventory = hack @ shield @ player.Inventory - } - if not (List.isEmpty hack) || not (List.isEmpty shield) then - do! DbService.updatePlayer updatedPlayer |> Async.Ignore - if not (List.isEmpty shieldEvent) then - try - do! DbService.addPlayerEvent player.DiscordId (List.head shieldEvent) |> Async.Ignore - with ex -> - printfn "%s" ex.Message - () - - let playerForEmbed = { player with - Events = [ HackEvent() ; ShieldEvent() ] - Inventory = hack @ shield @ player.Inventory + Events = + player + |> Player.removeExpiredActions + |> fun p -> p.Events + |> List.filter (fun e -> match e.Type with Shielding _ -> false | _ -> true) + |> List.append (ShieldEvents()) + |> List.consTo (HackEvent()) + Inventory = + player.Inventory + |> addIfDoesntExist (Hack defaultHack) + |> addIfDoesntExist (Shield defaultShield) + Bank = player.Bank + CurrencyGift } - let embed = Embeds.getArsenalEmbed playerForEmbed + do! DbService.updatePlayer updatedPlayer |> Async.Ignore + try + do! ShieldEvents () + |> List.map (DbService.addPlayerEvent player.DiscordId) + |> Async.Parallel + |> Async.Ignore + with ex -> + printfn "%s" ex.Message + () + + let embed = Embeds.getArsenalEmbed updatedPlayer do! ctx.FollowUp(embed) |> Async.AwaitTask - let! completed = DbService.checkHasAchievement player.DiscordId trainerAchievement - if not completed then - do! Async.Sleep 3000 - let rewards = [ $"{defaultHack.Item.Name} Hack" ; $"{defaultShield.Item.Name} Shield" ] - let embed = Embeds.getAchievementEmbed rewards "You completed the Training Dojo and collected loot." trainerAchievement - do! ctx.FollowUp(embed) |> Async.AwaitTask do! Async.Sleep 2000 + let rewards = [ $"{defaultHack.Item.Name} Hack" ; $"{defaultShield.Item.Name} Shield" ; $"{CurrencyGift} 💰$GBT"] + let embed = Embeds.getAchievementEmbed rewards "You completed the Training Dojo and collected some prizes." TrainerAchievement + do! ctx.FollowUp(embed) |> Async.AwaitTask + let role = ctx.GetGuild().GetRole(GuildEnvironment.roleTrainee) - do! ctx.GetDiscordMember().RevokeRoleAsync(role) |> Async.AwaitTask + do! ctx.GetDiscordMember().RevokeRoleAsync(role) + |> Async.AwaitTask + + do! Async.Sleep 2000 do! sendFollowUpMessage ctx $"Now get out of there and go hack other Degenz in the <#{GuildEnvironment.channelBattle}> channel!" - }) + else + 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 handleButtonEvent (ctx : IDiscordContext) = async { diff --git a/Bot/Items.json b/Bot/Items.json index 092ef8c..05880e4 100644 --- a/Bot/Items.json +++ b/Bot/Items.json @@ -9,7 +9,14 @@ "Item": { "Id": 0, "Name": "Virus", - "Price": 0 + "Price": 0, + "Attributes": { + "CanBuy" : true, + "CanSell" : true, + "CanConsume" : false, + "CanTrade" : false, + "CanDrop" : false + } } } ] @@ -20,11 +27,18 @@ { "Power": 50, "Class": 1, - "Cooldown": 3, + "Cooldown": 5, "Item": { "Id": 1, "Name": "Remote Access", - "Price": 500 + "Price": 500, + "Attributes": { + "CanBuy" : true, + "CanSell" : true, + "CanConsume" : false, + "CanTrade" : false, + "CanDrop" : false + } } } ] @@ -35,11 +49,18 @@ { "Power": 100, "Class": 2, - "Cooldown": 5, + "Cooldown": 10, "Item": { "Id": 2, "Name": "Worm", - "Price": 5000 + "Price": 5000, + "Attributes": { + "CanBuy" : true, + "CanSell" : true, + "CanConsume" : false, + "CanTrade" : false, + "CanDrop" : false + } } } ] @@ -53,7 +74,14 @@ "Item": { "Id": 6, "Name": "Firewall", - "Price": 0 + "Price": 0, + "Attributes": { + "CanBuy" : true, + "CanSell" : true, + "CanConsume" : false, + "CanTrade" : false, + "CanDrop" : false + } } } ] @@ -67,7 +95,14 @@ "Item": { "Id": 7, "Name": "Encryption", - "Price": 500 + "Price": 500, + "Attributes": { + "CanBuy" : true, + "CanSell" : true, + "CanConsume" : false, + "CanTrade" : false, + "CanDrop" : false + } } } ] @@ -81,7 +116,14 @@ "Item": { "Id": 8, "Name": "Cypher", - "Price": 5000 + "Price": 5000, + "Attributes": { + "CanBuy" : true, + "CanSell" : true, + "CanConsume" : false, + "CanTrade" : false, + "CanDrop" : false + } } } ] @@ -95,7 +137,14 @@ "Item": { "Id": 12, "Name": "Protein Powder", - "Price": 50 + "Price": 50, + "Attributes": { + "CanBuy" : true, + "CanSell" : false, + "CanConsume" : true, + "CanTrade" : false, + "CanDrop" : false + } } } ] @@ -109,7 +158,14 @@ "Item": { "Id": 13, "Name": "Toro Loco", - "Price": 50 + "Price": 50, + "Attributes": { + "CanBuy" : true, + "CanSell" : false, + "CanConsume" : true, + "CanTrade" : false, + "CanDrop" : false + } } } ] @@ -123,7 +179,14 @@ "Item": { "Id": 14, "Name": "Oldports Cigs", - "Price": 50 + "Price": 50, + "Attributes": { + "CanBuy" : true, + "CanSell" : false, + "CanConsume" : true, + "CanTrade" : false, + "CanDrop" : false + } } } ] @@ -137,7 +200,14 @@ "Item": { "Id": 15, "Name": "Moon Pie", - "Price": 50 + "Price": 50, + "Attributes": { + "CanBuy" : true, + "CanSell" : false, + "CanConsume" : true, + "CanTrade" : false, + "CanDrop" : false + } } } ] @@ -152,7 +222,14 @@ "Item": { "Id": 20, "Name": "Kettlebell", - "Price": 250 + "Price": 250, + "Attributes": { + "CanBuy" : true, + "CanSell" : true, + "CanConsume" : false, + "CanTrade" : false, + "CanDrop" : false + } } } ] @@ -167,7 +244,14 @@ "Item": { "Id": 21, "Name": "Headphones", - "Price": 250 + "Price": 250, + "Attributes": { + "CanBuy" : true, + "CanSell" : true, + "CanConsume" : false, + "CanTrade" : false, + "CanDrop" : false + } } } ] @@ -182,7 +266,14 @@ "Item": { "Id": 22, "Name": "Rolox Watch", - "Price": 250 + "Price": 250, + "Attributes": { + "CanBuy" : true, + "CanSell" : true, + "CanConsume" : false, + "CanTrade" : false, + "CanDrop" : false + } } } ] @@ -197,9 +288,17 @@ "Item": { "Id": 23, "Name": "Buddha Keychain", - "Price": 250 + "Price": 250, + "Attributes": { + "CanBuy" : true, + "CanSell" : true, + "CanConsume" : false, + "CanTrade" : false, + "CanDrop" : false + } } } ] } ] + diff --git a/Bot/Prelude.fs b/Bot/Prelude.fs index e224ddf..329a920 100644 --- a/Bot/Prelude.fs +++ b/Bot/Prelude.fs @@ -5,8 +5,8 @@ module ResultHelpers = let (>>=) x f = Result.bind f x let () x f = Result.map f x -[] [] +[] module List = let cons xs x = x :: xs let consTo x xs = x :: xs