diff --git a/Bot/HackerBattle.fs b/Bot/HackerBattle.fs index ea2305d..3bcc148 100644 --- a/Bot/HackerBattle.fs +++ b/Bot/HackerBattle.fs @@ -9,14 +9,7 @@ open DSharpPlus.SlashCommands open Degenz open Degenz.Messaging -let getTimeTillCooldownFinishes (timespan : TimeSpan) timestamp = - let timeRemaining = timespan - (DateTime.UtcNow - timestamp) - if timeRemaining.Hours > 0 then - $"{timeRemaining.Hours} hours" - elif timeRemaining.Minutes > 0 then - $"{timeRemaining.Minutes} minutes" - else - $"{timeRemaining.Seconds} seconds" +let (>>=) x f = Result.bind f x let checkIfPlayerIsAttackingThemselves defender attacker = match attacker.DiscordId = defender.DiscordId with @@ -30,10 +23,9 @@ let checkForExistingHack defenderId attacker = |> Array.tryFind (fun (_,t,_) -> t.Id = defenderId) |> function | 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 cooldown = getTimeTillCooldownFinishes (TimeSpan.FromHours(2)) atk.Timestamp + Error $"You can only hack the same target once every 2 hours, wait {cooldown} to attempt another hack on {target.Name}." + | None -> Ok attacker let checkIfHackHasCooldown hackId attacker = let mostRecentHackAttack = @@ -54,6 +46,11 @@ let checkIfInventoryIsEmpty attacker = | [||] -> Error $"You currently do not have any Hacks to steal 💰$GBT from others. Please go to the <#{GuildEnvironment.channelArmory}> and purchase one." | _ -> Ok attacker +let checkIfTargetHasMoney (target : PlayerData) attacker = + if target.Bank < Game.HackPrize + then Error $"{target.Name} does not have enough 💰$GBT to steal from, the broke loser. Pick a different target." + else Ok attacker + let calculateDamage (hack : BattleItem) (shield : BattleItem) = if hack.Class = shield.Class then Strong @@ -115,22 +112,22 @@ let attack (ctx : InteractionContext) (target : DiscordUser) = let! defender = DbService.tryFindPlayer target.Id match defender with | Some defender -> - let hackAttempt = - checkForExistingHack defender.DiscordId attacker - |> Result.bind checkIfInventoryIsEmpty - |> Result.bind (checkIfPlayerIsAttackingThemselves defender) - match hackAttempt with - | Ok _ -> - let embed = Embeds.pickHack "Attack" attacker defender - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, embed) - |> Async.AwaitTask - | Error msg -> - let builder = - DiscordInteractionResponseBuilder() - .WithContent(msg) - .AsEphemeral(true) - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask + do! checkForExistingHack defender.DiscordId attacker + >>= checkIfInventoryIsEmpty + >>= (checkIfTargetHasMoney defender) + >>= (checkIfPlayerIsAttackingThemselves defender) + |> function + | Ok _ -> + let embed = Embeds.pickHack "Attack" attacker defender + ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, embed) + |> Async.AwaitTask + | Error msg -> + let builder = + DiscordInteractionResponseBuilder() + .WithContent(msg) + .AsEphemeral(true) + ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask | None -> do! sendSimpleResponse ctx "Your target is not connected to the network, they must join first by using the /redpill command" }) @@ -260,7 +257,7 @@ type HackerGame() = else attack ctx target - [] + [] member this.ShieldCommand (ctx : InteractionContext) = if ctx.Channel.Id = GuildEnvironment.channelTraining then Trainer.defend ctx diff --git a/Bot/PlayerInteractions.fs b/Bot/PlayerInteractions.fs index a4e4e0a..32ac95f 100644 --- a/Bot/PlayerInteractions.fs +++ b/Bot/PlayerInteractions.fs @@ -71,27 +71,12 @@ module Commands = // } |> Async.StartAsTask // :> Task - let status (ctx : InteractionContext) = - Game.executePlayerInteraction ctx (fun player -> async { - let updatedActions = Player.removeExpiredActions player.Actions - let updatedPlayer = { player with Actions = updatedActions } - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- Messaging.statusFormat updatedPlayer - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - do! DbService.updatePlayer updatedPlayer - }) - type PlayerInteractions() = inherit ApplicationCommandModule () [] member _.AddHackerRole (ctx : InteractionContext) = Commands.addHackerRole ctx - [] - member this.Status (ctx : InteractionContext) = Commands.status ctx - // [] // member this.Leaderboard (ctx : InteractionContext) = Commands.leaderboard ctx diff --git a/Bot/Store.fs b/Bot/Store.fs index 476038a..2978a45 100644 --- a/Bot/Store.fs +++ b/Bot/Store.fs @@ -94,12 +94,29 @@ let handleSellButtonEvents (_ : DiscordClient) (event : ComponentInteractionCrea } |> Async.StartAsTask :> Task +let status (ctx : InteractionContext) = + Game.executePlayerInteraction ctx (fun player -> async { + let updatedActions = Player.removeExpiredActions player.Actions + let updatedPlayer = { player with Actions = updatedActions } + let builder = DiscordInteractionResponseBuilder() + let embed = DiscordEmbedBuilder() + embed.AddField("Arsenal", Messaging.statusFormat updatedPlayer) |> ignore + builder.AddEmbed(embed) |> ignore + builder.IsEphemeral <- true + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + do! DbService.updatePlayer updatedPlayer + }) + type Store() = inherit ApplicationCommandModule () // [] // member _.ViewStore (ctx : InteractionContext) = viewStore ctx + [] + member this.Arsenal (ctx : InteractionContext) = status ctx + [] member _.BuyHack (ctx : InteractionContext) = buyItem ctx Hack diff --git a/Bot/Trainer.fs b/Bot/Trainer.fs index ba06668..4695722 100644 --- a/Bot/Trainer.fs +++ b/Bot/Trainer.fs @@ -1,7 +1,7 @@ module Degenz.Trainer -open System.Threading.Tasks open DSharpPlus +open System.Threading.Tasks open DSharpPlus.Entities open DSharpPlus.EventArgs open DSharpPlus.SlashCommands @@ -39,7 +39,7 @@ let handleTrainerStep1 (event : ComponentInteractionCreateEventArgs) = do! sendInteractionEvent event ("Beautopia© is a dangerous place...\n" - + "Quick, put up a DEFENSE 🛡 before another Degen hacks you, and steals your 💰$GBT.\n" + + "Quick, put up a SHIELD 🛡 before another Degen hacks you, and steals your 💰$GBT.\n" + shieldMessage + "To enable it, you need to run the `/shield` slash command.\n\n" + $"Type the `/shield` command now, then select - `{weaponName}`\n") @@ -60,7 +60,7 @@ let defend (ctx : InteractionContext) = let handleDefenseMsg = { ButtonId = "Trainer-3" ButtonText = "Got it" - Message = "🎉 Congratulations 🎉\n\n" + Message = "🎉 Congratulations\n\n" + "You successfully defended my hack!\n\n" + "Because I tried hacking you when you had your defense up, you stole money from me...\n" + "Defenses only protect you for a LIMITED TIME, so remember to keep at least 1 defense up at all times, or risk getting hacked!" @@ -77,7 +77,7 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) = do! Async.Sleep 2000 do! sendMessage' "Ok, good, let me make sure that worked.\n\nI'll try to **hack** you now" do! Async.Sleep 4000 - do! sendMessage' $"❌ HACKING FAILED! ❌\n\n{player.Name} defended the hack from <@{GuildEnvironment.botHackerBattle}>, and stole 💰$GBT {Game.ShieldPrize} from them!" + do! sendMessage' $"❌ HACKING FAILED!\n\n{player.Name} defended the hack from <@{GuildEnvironment.botHackerBattle}>, and stole 💰$GBT {Game.ShieldPrize} from them!" do! Async.Sleep 3000 do! sendFollowUpMessageWithButton event handleDefenseMsg }) @@ -133,15 +133,17 @@ let handleAttack (event : ComponentInteractionCreateEventArgs) = let embed = Embeds.responseSuccessfulHackTrainer hack do! event.Interaction.CreateFollowupMessageAsync(embed) |> Async.AwaitTask |> Async.Ignore do! Async.Sleep 3000 - do! sendMessage' ("🎉 **Congratulations** 🎉\n\n" - + "You successfully **HACKED** me, and are now an **Elite Haxor!**\n\n" - + "When you **HACK** other Degenz, you **STEAL** their 💰**$GBT.**\n" - + "But remember, hacks take time to recover, so use them wisely.") + do! sendMessage' + ("🎉 **Congratulations**\n\n" + + "You successfully **HACKED** me, and are now an **Elite Haxor!**\n\n" + + "When you **HACK** other Degenz, you **STEAL** their 💰$GBT.\n" + + "But remember, hacks take time to recover, so use them wisely.") do! Async.Sleep 7000 - do! sendFollowUpMessage event ("Your training is **complete!**\n\n" - + "But, you’re going to need more **HACKS & DEFENSES** 🛡 to survive in **Beautopia©...**\n" - + $"You can purchase them from <@{GuildEnvironment.botArmory}>, at <#{GuildEnvironment.channelArmory}> anytime you want...\n\n" - + "Go there **NOW** to buy **HACKS** & **SHIELDS** before you get hacked! 😱") + do! sendFollowUpMessage event + ("Your training is **complete!**\n\n" + + "But, you’re going to need more **HACKS & SHIELDS** 🛡 to survive in **Beautopia©...**\n" + + $"You can purchase them from <@{GuildEnvironment.botArmory}> anytime you want...\n\n" + + $"Go to the <#{GuildEnvironment.channelArmory}> **NOW** and type the `/buy-shield` slash command! 😱") }) let handleButtonEvent (event : ComponentInteractionCreateEventArgs) = diff --git a/Shared/Shared.fs b/Shared/Shared.fs index c2156a8..bd6d6a7 100644 --- a/Shared/Shared.fs +++ b/Shared/Shared.fs @@ -76,7 +76,6 @@ module Types = Actions : Action array Bank : int } - module Armory = let battleItems = let file = System.IO.File.ReadAllText("Items.json") @@ -89,9 +88,10 @@ module Player = 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 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) + |> Array.filter (fun act -> match act.Type with Attack _ -> true | _ -> false) + let defenses player = player.Actions |> Array.filter (fun act -> match act.Type with Defense -> true | _ -> false) + // TODO: I have to fix this, this will remove hacks before the single target cooldown expires let removeExpiredActions actions = actions |> Array.filter (fun (act : Action) -> @@ -110,12 +110,39 @@ module Messaging = Message : string } + let getTimeTillCooldownFinishes (timespan : TimeSpan) timestamp = + let timeRemaining = timespan - (DateTime.UtcNow - timestamp) + if timeRemaining.Hours > 0 then + $"{timeRemaining.Hours} hours" + elif timeRemaining.Minutes > 0 then + $"{timeRemaining.Minutes} minutes" + else + $"{timeRemaining.Seconds} seconds" + + let battleItemFormat (items : BattleItem array) = + match items with + | [||] -> "None" + | _ -> items |> Array.toList |> List.map (fun i -> i.Name) |> String.concat ", " + + let actionFormat (actions : Action array) = + match actions with + | [||] -> "None" + | _ -> + actions + |> Array.map (fun act -> + match act.Type with + | Attack atk -> $"Hacked {atk.Target} at {act.Timestamp.ToLongDateString()}" + | Defense -> + let item = Armory.getItem act.ActionId + let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromMinutes(int item.Cooldown)) + $"{item.Name} active for {cooldown}") + |> String.concat "\n" + let statusFormat p = - $"Hacks: {Player.hacks 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}" + $"**Hacks:** {Player.hacks p |> battleItemFormat}\n +**Shields:** {Player.shields p |> battleItemFormat}\n +**Hack Attacks:** {Player.attacks p |> actionFormat}\n +**Active Shields:** {Player.defenses p |> actionFormat}" let constructButtons (actionType: string) (playerInfo: string) (weapons: BattleItem array) = weapons