New store flow
This commit is contained in:
		
							parent
							
								
									36471195aa
								
							
						
					
					
						commit
						dcf0bdb174
					
				
							
								
								
									
										37
									
								
								Bot/Bot.fs
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								Bot/Bot.fs
									
									
									
									
									
								
							@ -4,7 +4,6 @@ open System.Threading.Tasks
 | 
			
		||||
open DSharpPlus
 | 
			
		||||
open DSharpPlus.SlashCommands
 | 
			
		||||
open Degenz
 | 
			
		||||
open Degenz.PlayerInteractions
 | 
			
		||||
open Degenz.HackerBattle
 | 
			
		||||
open Degenz.Store
 | 
			
		||||
open Emzi0767.Utilities
 | 
			
		||||
@ -12,46 +11,41 @@ open Emzi0767.Utilities
 | 
			
		||||
 | 
			
		||||
type EmptyGlobalCommandToAvoidFamousDuplicateSlashCommandsBug() = inherit ApplicationCommandModule ()
 | 
			
		||||
 | 
			
		||||
let playerInteractionsConfig = DiscordConfiguration()
 | 
			
		||||
let guild = GuildEnvironment.guildId
 | 
			
		||||
 | 
			
		||||
let hackerBattleConfig = DiscordConfiguration()
 | 
			
		||||
let storeConfig = DiscordConfiguration()
 | 
			
		||||
//let slotMachineConfig = DiscordConfiguration()
 | 
			
		||||
//hackerBattleConfig.MinimumLogLevel <- Microsoft.Extensions.Logging.LogLevel.Trace
 | 
			
		||||
 | 
			
		||||
//let configs = [| playerInteractionsConfig ; hackerBattleConfig ; storeConfig ; slotMachineConfig ; |]
 | 
			
		||||
let configs = [| playerInteractionsConfig ; hackerBattleConfig ; storeConfig |]
 | 
			
		||||
//let configs = [| hackerBattleConfig ; storeConfig ; slotMachineConfig ; |]
 | 
			
		||||
let configs = [ hackerBattleConfig ; storeConfig ]
 | 
			
		||||
 | 
			
		||||
for conf in configs do
 | 
			
		||||
    conf.TokenType <- TokenType.Bot
 | 
			
		||||
    conf.Intents <- DiscordIntents.All
 | 
			
		||||
let guild = GuildEnvironment.guildId
 | 
			
		||||
 | 
			
		||||
playerInteractionsConfig.Token <- GuildEnvironment.tokenPlayerInteractions
 | 
			
		||||
hackerBattleConfig.Token <- GuildEnvironment.tokenHackerBattle
 | 
			
		||||
storeConfig.Token <- GuildEnvironment.tokenStore
 | 
			
		||||
//slotMachineConfig.Token <- Environment.GetEnvironmentVariable("BOT_SLOT_MACHINE")
 | 
			
		||||
 | 
			
		||||
//let playerInteractionsBot = new DiscordClient(playerInteractionsConfig)
 | 
			
		||||
let hackerBattleBot = new DiscordClient(hackerBattleConfig)
 | 
			
		||||
let storeBot = new DiscordClient(storeConfig)
 | 
			
		||||
//let slotMachineBot = new DiscordClient(slotMachineConfig)
 | 
			
		||||
 | 
			
		||||
//let clients = [| storeBot  ; trainerBot ; hackerBattleBot ; playerInteractionsBot ; slotMachineBot |]
 | 
			
		||||
//let clients = [| hackerBattleBot ; storeBot ; playerInteractionsBot |]
 | 
			
		||||
let clients = [| hackerBattleBot ; storeBot |]
 | 
			
		||||
//let clients = [| hackerBattleBot ; storeBot ; slotMachineBot |]
 | 
			
		||||
let clients = [ hackerBattleBot ; storeBot ]
 | 
			
		||||
 | 
			
		||||
//let sc1 = playerInteractionsBot.UseSlashCommands()
 | 
			
		||||
let sc3 = hackerBattleBot.UseSlashCommands()
 | 
			
		||||
let sc4 = storeBot.UseSlashCommands()
 | 
			
		||||
//let sc5 = slotMachineBot.UseSlashCommands()
 | 
			
		||||
let sc1 = hackerBattleBot.UseSlashCommands()
 | 
			
		||||
let sc2 = storeBot.UseSlashCommands()
 | 
			
		||||
//let sc3 = slotMachineBot.UseSlashCommands()
 | 
			
		||||
 | 
			
		||||
//sc1.RegisterCommands<PlayerInteractions>(guild);
 | 
			
		||||
sc3.RegisterCommands<HackerGame>(guild);
 | 
			
		||||
sc4.RegisterCommands<Store>(guild);
 | 
			
		||||
//sc5.RegisterCommands<SlotMachine>(guild);
 | 
			
		||||
sc1.RegisterCommands<HackerGame>(guild);
 | 
			
		||||
sc2.RegisterCommands<Store>(guild);
 | 
			
		||||
//sc3.RegisterCommands<SlotMachine>(guild);
 | 
			
		||||
 | 
			
		||||
hackerBattleBot.add_ComponentInteractionCreated(AsyncEventHandler(HackerBattle.handleButtonEvent))
 | 
			
		||||
storeBot.add_ComponentInteractionCreated(AsyncEventHandler(Store.handleSellButtonEvents))
 | 
			
		||||
storeBot.add_ComponentInteractionCreated(AsyncEventHandler(Store.handleStoreEvents))
 | 
			
		||||
 | 
			
		||||
let asdf (_ : DiscordClient) (event : DSharpPlus.EventArgs.InteractionCreateEventArgs) =
 | 
			
		||||
    async {
 | 
			
		||||
@ -74,11 +68,10 @@ let run (client : DiscordClient) =
 | 
			
		||||
        do! client.ConnectAsync () |> Async.AwaitTask
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
Trainer.sendInitialEmbed hackerBattleBot
 | 
			
		||||
//Trainer.sendInitialEmbed hackerBattleBot
 | 
			
		||||
 | 
			
		||||
clients
 | 
			
		||||
|> Array.map run
 | 
			
		||||
|> Array.toSeq
 | 
			
		||||
|> List.map run
 | 
			
		||||
|> Async.Sequential
 | 
			
		||||
|> Async.RunSynchronously
 | 
			
		||||
|> ignore
 | 
			
		||||
 | 
			
		||||
@ -1,28 +1,28 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
  <PropertyGroup>
 | 
			
		||||
    <OutputType>Exe</OutputType>
 | 
			
		||||
    <TargetFramework>net6.0</TargetFramework>
 | 
			
		||||
    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
 | 
			
		||||
    <RootNamespace>Degenz</RootNamespace>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <Content Include="Items.json">
 | 
			
		||||
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 | 
			
		||||
    </Content>
 | 
			
		||||
    <Content Include="paket.references" />
 | 
			
		||||
    <Compile Include="GuildEnvironment.fs" />
 | 
			
		||||
    <Compile Include="Game.fs" />
 | 
			
		||||
    <Compile Include="Embeds.fs" />
 | 
			
		||||
    <Compile Include="Store.fs" />
 | 
			
		||||
    <Compile Include="Trainer.fs" />
 | 
			
		||||
    <Compile Include="HackerBattle.fs" />
 | 
			
		||||
    <Compile Include="SlotMachine.fs" />
 | 
			
		||||
    <Compile Include="PlayerInteractions.fs" />
 | 
			
		||||
    <Compile Include="Bot.fs" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\DbService\DbService.fsproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <Import Project="..\.paket\Paket.Restore.targets" />
 | 
			
		||||
    <PropertyGroup>
 | 
			
		||||
        <OutputType>Exe</OutputType>
 | 
			
		||||
        <TargetFramework>net6.0</TargetFramework>
 | 
			
		||||
        <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
 | 
			
		||||
        <RootNamespace>Degenz</RootNamespace>
 | 
			
		||||
    </PropertyGroup>
 | 
			
		||||
    <ItemGroup>
 | 
			
		||||
        <Content Include="Items.json">
 | 
			
		||||
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 | 
			
		||||
        </Content>
 | 
			
		||||
        <Content Include="paket.references"/>
 | 
			
		||||
        <Compile Include="GuildEnvironment.fs"/>
 | 
			
		||||
        <Compile Include="Game.fs"/>
 | 
			
		||||
        <Compile Include="PlayerInteractions.fs"/>
 | 
			
		||||
        <Compile Include="Embeds.fs"/>
 | 
			
		||||
        <Compile Include="Store.fs"/>
 | 
			
		||||
        <Compile Include="Trainer.fs"/>
 | 
			
		||||
        <Compile Include="HackerBattle.fs"/>
 | 
			
		||||
        <Compile Include="SlotMachine.fs"/>
 | 
			
		||||
        <Compile Include="Bot.fs"/>
 | 
			
		||||
    </ItemGroup>
 | 
			
		||||
    <ItemGroup>
 | 
			
		||||
        <ProjectReference Include="..\DbService\DbService.fsproj"/>
 | 
			
		||||
    </ItemGroup>
 | 
			
		||||
    <Import Project="..\.paket\Paket.Restore.targets"/>
 | 
			
		||||
</Project>
 | 
			
		||||
							
								
								
									
										107
									
								
								Bot/Embeds.fs
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								Bot/Embeds.fs
									
									
									
									
									
								
							@ -4,35 +4,22 @@ open System
 | 
			
		||||
open DSharpPlus.EventArgs
 | 
			
		||||
open Degenz.Types
 | 
			
		||||
open DSharpPlus.Entities
 | 
			
		||||
open AsciiTableFormatter
 | 
			
		||||
 | 
			
		||||
let hackGif = "https://s10.gifyu.com/images/Hacker-Degenz-V20ce8eb832734aa62-min.gif"
 | 
			
		||||
let shieldGif = "https://s10.gifyu.com/images/Defense-Degenz-V2-min.gif"
 | 
			
		||||
 | 
			
		||||
let getHackGif = function
 | 
			
		||||
    | HackId.Virus -> "https://s10.gifyu.com/images/Attack-DegenZ-1.gif"
 | 
			
		||||
    | HackId.RemoteAccess -> "https://s10.gifyu.com/images/Mind-Control-Degenz-V2-min.gif"
 | 
			
		||||
    | HackId.Worm -> "https://s10.gifyu.com/images/WormBugAttack_Degenz-min.gif"
 | 
			
		||||
    | HackId.Virus -> "https://s10.gifyu.com/images/Virus-icon.jpg"
 | 
			
		||||
    | HackId.RemoteAccess -> "https://s10.gifyu.com/images/Mind-Control-Degenz-V2-min.jpg"
 | 
			
		||||
    | HackId.Worm -> "https://s10.gifyu.com/images/WormBugAttack_Degenz-min.jpg"
 | 
			
		||||
    | _ -> hackGif
 | 
			
		||||
 | 
			
		||||
let getShieldGif = function
 | 
			
		||||
    | ShieldId.Firewall -> "https://s10.gifyu.com/images/Defense-GIF-1-Degenz-min.gif"
 | 
			
		||||
    | ShieldId.Encryption -> "https://s10.gifyu.com/images/Encryption-Degenz-V2-1-min.gif"
 | 
			
		||||
    | ShieldId.Cypher -> "https://s10.gifyu.com/images/Cypher-Smaller.gif"
 | 
			
		||||
    | ShieldId.Firewall -> "https://s10.gifyu.com/images/Defense-GIF-1-Degenz-1.jpg"
 | 
			
		||||
    | ShieldId.Encryption -> "https://s10.gifyu.com/images/Encryption-Degenz-V2-1-min.jpg"
 | 
			
		||||
    | ShieldId.Cypher -> "https://s10.gifyu.com/images/Cypher-Smaller.jpg"
 | 
			
		||||
    | _ -> shieldGif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
let constructEmbed message =
 | 
			
		||||
    let builder = DiscordEmbedBuilder()
 | 
			
		||||
    builder.Color <- Optional(DiscordColor.Blurple)
 | 
			
		||||
    builder.Description <- message
 | 
			
		||||
    let author = DiscordEmbedBuilder.EmbedAuthor()
 | 
			
		||||
    author.Name <- "Degenz Hacker Game"
 | 
			
		||||
    author.Url <- "https://twitter.com/degenzgame"
 | 
			
		||||
    author.IconUrl <- "https://pbs.twimg.com/profile_images/1473192843359309825/cqjm0VQ4_400x400.jpg"
 | 
			
		||||
    builder.Author <- author
 | 
			
		||||
    builder.Build()
 | 
			
		||||
 | 
			
		||||
let pickDefense actionId player =
 | 
			
		||||
    let buttons =
 | 
			
		||||
        Messaging.constructButtons actionId (string player.DiscordId) (Player.shields player)
 | 
			
		||||
@ -85,43 +72,73 @@ let responseCreatedShieldTrainer (shield : BattleItem) =
 | 
			
		||||
    DiscordFollowupMessageBuilder()
 | 
			
		||||
        .AddEmbed(DiscordEmbedBuilder().WithImageUrl(getShieldGif (enum<ShieldId>(shield.Id))))
 | 
			
		||||
        .AsEphemeral(true)
 | 
			
		||||
        .WithContent($"Mounted a {shield.Name} defense for 6 hours")
 | 
			
		||||
        .WithContent($"Mounted a {shield.Name} defense for {TimeSpan.FromMinutes(int shield.Cooldown).Hours} 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(hackGif)
 | 
			
		||||
//
 | 
			
		||||
    DiscordMessageBuilder()
 | 
			
		||||
        .WithContent($"{event.User.Username} successfully hacked <@{targetId}> for a total of {prize} GoodBoyTokenz")
 | 
			
		||||
 | 
			
		||||
[<CLIMutable>]
 | 
			
		||||
type Table = {
 | 
			
		||||
    Name : string
 | 
			
		||||
    Cost : string
 | 
			
		||||
    Class : string
 | 
			
		||||
}
 | 
			
		||||
let getGoodAgainst = function
 | 
			
		||||
    | BattleClass.Network     -> ( ShieldId.Firewall   , HackId.Virus        )
 | 
			
		||||
    | BattleClass.Penetration -> ( ShieldId.Cypher     , HackId.RemoteAccess )
 | 
			
		||||
    | BattleClass.Exploit     -> ( ShieldId.Encryption , HackId.Worm         )
 | 
			
		||||
 | 
			
		||||
let storeListing store =
 | 
			
		||||
    let embeds =
 | 
			
		||||
let getBuyItemsEmbed (itemType : ItemType) (store : BattleItem array) =
 | 
			
		||||
    let embeds , buttons =
 | 
			
		||||
        store
 | 
			
		||||
        |> Array.groupBy (fun (bi : BattleItem) -> bi.Type)
 | 
			
		||||
        |> Array.map (fun ( itemType , items ) ->
 | 
			
		||||
            let msg =
 | 
			
		||||
                items
 | 
			
		||||
                |> Array.map (fun item -> { Name = item.Name ; Cost = string item.Cost ; Class = string item.Class })
 | 
			
		||||
                |> Formatter.Format
 | 
			
		||||
                |> sprintf "**%As**\n``` %s ```" itemType
 | 
			
		||||
            DiscordEmbedBuilder()
 | 
			
		||||
                .WithDescription(msg)
 | 
			
		||||
                .Build())
 | 
			
		||||
        |> Array.filter (fun i -> i.Type = itemType)
 | 
			
		||||
        |> Array.map (fun item ->
 | 
			
		||||
            let embed = DiscordEmbedBuilder()
 | 
			
		||||
            match item.Type with
 | 
			
		||||
            | Hack ->
 | 
			
		||||
                embed
 | 
			
		||||
                    .AddField($"Weak Against  |", getGoodAgainst item.Class |> fst |> string , true)
 | 
			
		||||
                    .AddField("Cooldown  |", $"{TimeSpan.FromMinutes(int item.Cooldown).Minutes} minutes", true)
 | 
			
		||||
                    .WithThumbnail(getHackGif (enum<HackId>(item.Id)))
 | 
			
		||||
                    |> ignore
 | 
			
		||||
            | Shield ->
 | 
			
		||||
                embed
 | 
			
		||||
                    .AddField($"Strong Against  |", getGoodAgainst item.Class |> snd |> string , true)
 | 
			
		||||
                    .AddField("Active For  |", $"{TimeSpan.FromMinutes(int item.Cooldown).Hours} hours", true)
 | 
			
		||||
                    .WithThumbnail(getShieldGif (enum<ShieldId>(item.Id)))
 | 
			
		||||
                    |> ignore
 | 
			
		||||
            embed
 | 
			
		||||
              .AddField("Cost 💰", $"{item.Cost} $GBT", true)
 | 
			
		||||
              .WithColor(Game.getClassEmbedColor item.Class)
 | 
			
		||||
              .WithTitle($"{item.Name}")
 | 
			
		||||
              |> ignore
 | 
			
		||||
            let button = DiscordButtonComponent(Game.getClassButtonColor item.Class, $"Buy-{item.Id}", $"Buy {item.Name}")
 | 
			
		||||
            embed.Build() , button :> DiscordComponent)
 | 
			
		||||
        |> Array.unzip
 | 
			
		||||
 | 
			
		||||
    DiscordInteractionResponseBuilder()
 | 
			
		||||
    DiscordFollowupMessageBuilder()
 | 
			
		||||
        .AddEmbeds(embeds)
 | 
			
		||||
        .AddComponents(buttons)
 | 
			
		||||
        .AsEphemeral(true)
 | 
			
		||||
 | 
			
		||||
let getSellItemsEmbed (itemType : ItemType) (player : PlayerData) =
 | 
			
		||||
    let embeds , buttons =
 | 
			
		||||
        player.Arsenal
 | 
			
		||||
        |> Array.filter (fun i -> i.Type = itemType)
 | 
			
		||||
        |> Array.map (fun item ->
 | 
			
		||||
            let embed = DiscordEmbedBuilder()
 | 
			
		||||
            match item.Type with
 | 
			
		||||
            | Hack -> embed.WithThumbnail(getHackGif (enum<HackId>(item.Id))) |> ignore
 | 
			
		||||
            | Shield -> embed.WithThumbnail(getShieldGif (enum<ShieldId>(item.Id))) |> ignore
 | 
			
		||||
            embed
 | 
			
		||||
              .AddField("Sell For 💰", $"{item.Cost} $GBT", true)
 | 
			
		||||
              .WithColor(Game.getClassEmbedColor item.Class)
 | 
			
		||||
              .WithTitle($"{item.Name}")
 | 
			
		||||
              |> ignore
 | 
			
		||||
            let button = DiscordButtonComponent(Game.getClassButtonColor item.Class, $"Sell-{item.Id}", $"Sell {item.Name}")
 | 
			
		||||
            embed.Build() , button :> DiscordComponent)
 | 
			
		||||
        |> Array.unzip
 | 
			
		||||
 | 
			
		||||
    DiscordFollowupMessageBuilder()
 | 
			
		||||
        .AddEmbeds(embeds)
 | 
			
		||||
        .AddComponents(buttons)
 | 
			
		||||
        .AsEphemeral(true)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										99
									
								
								Bot/Game.fs
									
									
									
									
									
								
							
							
						
						
									
										99
									
								
								Bot/Game.fs
									
									
									
									
									
								
							@ -1,43 +1,78 @@
 | 
			
		||||
module Degenz.Game
 | 
			
		||||
namespace Degenz
 | 
			
		||||
 | 
			
		||||
open System
 | 
			
		||||
open System.Threading.Tasks
 | 
			
		||||
open DSharpPlus
 | 
			
		||||
open DSharpPlus.Entities
 | 
			
		||||
open DSharpPlus.EventArgs
 | 
			
		||||
open DSharpPlus.SlashCommands
 | 
			
		||||
open Degenz.DbService
 | 
			
		||||
open Microsoft.VisualBasic
 | 
			
		||||
 | 
			
		||||
let HackPrize = 10<GBT>
 | 
			
		||||
let ShieldPrize = 5<GBT>
 | 
			
		||||
module Game =
 | 
			
		||||
    let HackPrize = 10<GBT>
 | 
			
		||||
    let ShieldPrize = 5<GBT>
 | 
			
		||||
 | 
			
		||||
let executePlayerInteraction (ctx : InteractionContext) (dispatch : PlayerData -> Async<unit>) =
 | 
			
		||||
    async {
 | 
			
		||||
        let builder = DiscordInteractionResponseBuilder()
 | 
			
		||||
        builder.IsEphemeral <- true
 | 
			
		||||
        builder.Content <- "Content"
 | 
			
		||||
        do! ctx.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, builder)
 | 
			
		||||
            |> Async.AwaitTask
 | 
			
		||||
        let! playerResult = tryFindPlayer ctx.Member.Id
 | 
			
		||||
        match playerResult with
 | 
			
		||||
        | Some player -> do! dispatch player
 | 
			
		||||
        | None -> do! Messaging.sendSimpleResponse ctx "You are currently not a hacker, first use the /redpill command to become one"
 | 
			
		||||
    } |> Async.StartAsTask
 | 
			
		||||
      :> Task
 | 
			
		||||
    let SameTargetAttackCooldown = System.TimeSpan.FromHours(6)
 | 
			
		||||
 | 
			
		||||
// TODO: Create an abstraction for these two helper functions
 | 
			
		||||
let executePlayerEvent (event : ComponentInteractionCreateEventArgs) (dispatch : PlayerData -> Async<unit>) =
 | 
			
		||||
    async {
 | 
			
		||||
        let builder = DiscordInteractionResponseBuilder()
 | 
			
		||||
        builder.IsEphemeral <- true
 | 
			
		||||
        builder.Content <- "Content"
 | 
			
		||||
        do! event.Interaction.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, builder)
 | 
			
		||||
            |> Async.AwaitTask
 | 
			
		||||
        let! playerResult = tryFindPlayer event.User.Id
 | 
			
		||||
        match playerResult with
 | 
			
		||||
        | Some player -> do! dispatch player
 | 
			
		||||
        | None -> do! Messaging.sendInteractionEvent event "You are currently not a hacker, first use the /redpill command to become one"
 | 
			
		||||
    } |> Async.StartAsTask
 | 
			
		||||
      :> Task
 | 
			
		||||
    let getClassButtonColor = function
 | 
			
		||||
        | Network -> ButtonStyle.Danger
 | 
			
		||||
        | Exploit -> ButtonStyle.Success
 | 
			
		||||
        | Penetration -> ButtonStyle.Primary
 | 
			
		||||
 | 
			
		||||
    let getClassEmbedColor = function
 | 
			
		||||
        | Network -> DiscordColor.Red
 | 
			
		||||
        | Penetration -> DiscordColor.Blurple
 | 
			
		||||
        | Exploit -> DiscordColor.Green
 | 
			
		||||
 | 
			
		||||
    let executePlayerInteraction (ctx : InteractionContext) (dispatch : PlayerData -> Async<unit>) =
 | 
			
		||||
        async {
 | 
			
		||||
            let builder = DiscordInteractionResponseBuilder()
 | 
			
		||||
            builder.IsEphemeral <- true
 | 
			
		||||
            builder.Content <- "Content"
 | 
			
		||||
            do! ctx.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, builder)
 | 
			
		||||
                |> Async.AwaitTask
 | 
			
		||||
            let! playerResult = tryFindPlayer ctx.Member.Id
 | 
			
		||||
            match playerResult with
 | 
			
		||||
            | Some player -> do! dispatch player
 | 
			
		||||
            | None -> do! Messaging.sendSimpleResponse ctx "You are currently not a hacker, first use the /redpill command to become one"
 | 
			
		||||
        } |> Async.StartAsTask
 | 
			
		||||
          :> Task
 | 
			
		||||
 | 
			
		||||
    // TODO: Create an abstraction for these two helper functions
 | 
			
		||||
    let executePlayerEvent (event : ComponentInteractionCreateEventArgs) (dispatch : PlayerData -> Async<unit>) =
 | 
			
		||||
        async {
 | 
			
		||||
            let builder = DiscordInteractionResponseBuilder()
 | 
			
		||||
            builder.IsEphemeral <- true
 | 
			
		||||
            builder.Content <- "Content"
 | 
			
		||||
            do! event.Interaction.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, builder)
 | 
			
		||||
                |> Async.AwaitTask
 | 
			
		||||
            let! playerResult = tryFindPlayer event.User.Id
 | 
			
		||||
            match playerResult with
 | 
			
		||||
            | Some player -> do! dispatch player
 | 
			
		||||
            | None -> do! Messaging.sendInteractionEvent event "You are currently not a hacker, first use the /redpill command to become one"
 | 
			
		||||
        } |> Async.StartAsTask
 | 
			
		||||
          :> Task
 | 
			
		||||
 | 
			
		||||
module Player =
 | 
			
		||||
    let hacks (player : PlayerData) = player.Arsenal |> Array.filter (fun bi -> bi.Type = Hack)
 | 
			
		||||
    let shields (player : PlayerData) = player.Arsenal |> Array.filter (fun bi -> bi.Type = Shield)
 | 
			
		||||
    let attacks player =
 | 
			
		||||
        player.Actions
 | 
			
		||||
        |> 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)
 | 
			
		||||
 | 
			
		||||
    let removeExpiredActions player =
 | 
			
		||||
        let actions =
 | 
			
		||||
            player.Actions
 | 
			
		||||
            |> Array.filter (fun (act : Action) ->
 | 
			
		||||
                match act.Type with
 | 
			
		||||
                // So the player doesnt attack the same player so many times in a row
 | 
			
		||||
                | Attack _ -> System.DateTime.UtcNow - act.Timestamp < Game.SameTargetAttackCooldown
 | 
			
		||||
                | Defense ->
 | 
			
		||||
                    let item = Armory.getItem act.ActionId
 | 
			
		||||
                    System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(int item.Cooldown))
 | 
			
		||||
        { player with Actions = actions }
 | 
			
		||||
 | 
			
		||||
    let modifyBank (player : PlayerData) amount = { player with Bank = max (player.Bank + amount) 0<GBT> }
 | 
			
		||||
 | 
			
		||||
    let getAttacksFlat actions = actions |> Array.choose (fun act -> match act.Type with Attack ar -> Some (act,ar.Target,ar.Result) | Defense -> None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,24 +9,22 @@ open DSharpPlus.SlashCommands
 | 
			
		||||
open Degenz
 | 
			
		||||
open Degenz.Messaging
 | 
			
		||||
 | 
			
		||||
let (>>=) x f = Result.bind f x
 | 
			
		||||
 | 
			
		||||
let checkIfPlayerIsAttackingThemselves defender attacker  =
 | 
			
		||||
let checkPlayerIsAttackingThemselves defender attacker =
 | 
			
		||||
    match attacker.DiscordId = defender.DiscordId with
 | 
			
		||||
    | true -> Error "You think you're clever? You can't hack yourself, pal."
 | 
			
		||||
    | false -> Ok attacker
 | 
			
		||||
 | 
			
		||||
let checkForExistingTarget defenderId attacker =
 | 
			
		||||
let checkAlreadyHackedTarget defenderId attacker =
 | 
			
		||||
    attacker.Actions
 | 
			
		||||
    |> Player.getAttacksFlat
 | 
			
		||||
    |> Array.tryFind (fun (_,t,_) -> t.Id = defenderId)
 | 
			
		||||
    |> function
 | 
			
		||||
       | Some ( atk , target , _ ) ->
 | 
			
		||||
           let cooldown = getTimeTillCooldownFinishes Player.SameTargetAttackCooldown atk.Timestamp
 | 
			
		||||
           Error $"You can only hack the same target once every {Player.SameTargetAttackCooldown.Hours} hours, wait {cooldown} to attempt another hack on {target.Name}."
 | 
			
		||||
           let cooldown = getTimeTillCooldownFinishes Game.SameTargetAttackCooldown atk.Timestamp
 | 
			
		||||
           Error $"You can only hack the same target once every {Game.SameTargetAttackCooldown.Hours} hours, wait {cooldown} to attempt another hack on {target.Name}."
 | 
			
		||||
       | None -> Ok attacker
 | 
			
		||||
 | 
			
		||||
let checkIfHackHasCooldown hackId attacker =
 | 
			
		||||
let checkHackHasCooldown hackId attacker =
 | 
			
		||||
    let mostRecentHackAttack =
 | 
			
		||||
        attacker.Actions
 | 
			
		||||
        |> Array.tryFind (fun a -> a.ActionId = hackId)
 | 
			
		||||
@ -41,12 +39,12 @@ let checkIfHackHasCooldown hackId attacker =
 | 
			
		||||
       let item = Armory.battleItems |> Array.find (fun i -> i.Id = hackId)
 | 
			
		||||
       Error $"{item.Name} is currently on cooldown, wait {cooldown} to use it again."
 | 
			
		||||
 | 
			
		||||
let checkIfHasEmptyHacks attacker =
 | 
			
		||||
let checkHasEmptyHacks attacker =
 | 
			
		||||
    match Player.hacks attacker with
 | 
			
		||||
    | [||] -> 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 =
 | 
			
		||||
let checkTargetHasMoney (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
 | 
			
		||||
@ -116,24 +114,17 @@ let attack (target : DiscordUser) (ctx : InteractionContext) =
 | 
			
		||||
        | Some defender ->
 | 
			
		||||
          do! attacker
 | 
			
		||||
              |> Player.removeExpiredActions
 | 
			
		||||
              |> checkForExistingTarget defender.DiscordId
 | 
			
		||||
              >>= checkIfHasEmptyHacks
 | 
			
		||||
              >>= checkIfTargetHasMoney defender
 | 
			
		||||
              >>= checkIfPlayerIsAttackingThemselves defender
 | 
			
		||||
              |> checkAlreadyHackedTarget defender.DiscordId
 | 
			
		||||
              >>= checkHasEmptyHacks
 | 
			
		||||
              >>= checkTargetHasMoney defender
 | 
			
		||||
              >>= checkPlayerIsAttackingThemselves defender
 | 
			
		||||
              |> function
 | 
			
		||||
                 | Ok _ ->
 | 
			
		||||
                     let embed = Embeds.pickHack "Attack" attacker defender
 | 
			
		||||
                     ctx.FollowUpAsync(embed)
 | 
			
		||||
                     |> Async.AwaitTask
 | 
			
		||||
                     |> Async.Ignore
 | 
			
		||||
                 | Error msg ->
 | 
			
		||||
                     let builder =
 | 
			
		||||
                         DiscordFollowupMessageBuilder()
 | 
			
		||||
                             .WithContent(msg)
 | 
			
		||||
                             .AsEphemeral(true)
 | 
			
		||||
                     ctx.FollowUpAsync(builder)
 | 
			
		||||
                     |> Async.AwaitTask
 | 
			
		||||
                     |> Async.Ignore
 | 
			
		||||
                 | Error msg -> sendFollowUpMessageFromCtx ctx msg
 | 
			
		||||
        | None -> do! sendFollowUpMessageFromCtx ctx "Your target is not connected to the network, they must join first by using the /redpill command"
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
@ -148,8 +139,8 @@ let handleAttack (event : ComponentInteractionCreateEventArgs) =
 | 
			
		||||
        | Some defender , true , true ->
 | 
			
		||||
            do! attacker
 | 
			
		||||
                |> Player.removeExpiredActions
 | 
			
		||||
                |> checkForExistingTarget defender.DiscordId
 | 
			
		||||
                >>= (checkIfHackHasCooldown (int hack))
 | 
			
		||||
                |> checkAlreadyHackedTarget defender.DiscordId
 | 
			
		||||
                >>= (checkHackHasCooldown (int hack))
 | 
			
		||||
                |> function
 | 
			
		||||
                   | Ok _ ->
 | 
			
		||||
                       runHackerBattle defender (Armory.getItem (int hack))
 | 
			
		||||
@ -175,17 +166,18 @@ let defend (ctx : InteractionContext) =
 | 
			
		||||
let handleDefense (event : ComponentInteractionCreateEventArgs) =
 | 
			
		||||
    Game.executePlayerEvent event (fun player -> async {
 | 
			
		||||
        let split = event.Id.Split("-")
 | 
			
		||||
        let shieldId = enum<ShieldId>(int split.[1])
 | 
			
		||||
        let shieldId = int split.[1]
 | 
			
		||||
        let shield = Armory.getItem shieldId
 | 
			
		||||
        let updatedDefenses = player |> Player.removeExpiredActions |> Player.defenses
 | 
			
		||||
        let alreadyUsedShield = updatedDefenses |> Array.exists (fun d -> d.ActionId = int shieldId)
 | 
			
		||||
        let alreadyUsedShield = updatedDefenses |> Array.exists (fun d -> d.ActionId = shieldId)
 | 
			
		||||
 | 
			
		||||
        match alreadyUsedShield , updatedDefenses.Length < 2 with
 | 
			
		||||
        | false , true ->
 | 
			
		||||
            let embed = Embeds.responseCreatedShield (Armory.getItem (int shieldId))
 | 
			
		||||
            let embed = Embeds.responseCreatedShield (Armory.getItem shieldId)
 | 
			
		||||
            do! event.Interaction.CreateFollowupMessageAsync(embed)
 | 
			
		||||
                |> Async.AwaitTask
 | 
			
		||||
                |> Async.Ignore
 | 
			
		||||
            let defense = { ActionId = int shieldId ; Type = Defense ; Timestamp = DateTime.UtcNow }
 | 
			
		||||
            let defense = { ActionId = shieldId ; 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
 | 
			
		||||
@ -194,24 +186,14 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) =
 | 
			
		||||
                |> Async.AwaitTask
 | 
			
		||||
                |> Async.Ignore
 | 
			
		||||
        | _ , false ->
 | 
			
		||||
            let builder = DiscordFollowupMessageBuilder()
 | 
			
		||||
            builder.IsEphemeral <- true
 | 
			
		||||
            let timestamp = updatedDefenses |> Array.rev |> Array.head |> fun a -> a.Timestamp // This should be the next expiring timestamp
 | 
			
		||||
            let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromHours(6)) timestamp
 | 
			
		||||
            builder.Content <- $"You are only allowed two shields at a time. Wait {cooldown} minutes to add another shield"
 | 
			
		||||
            do! event.Interaction.CreateFollowupMessageAsync(builder)
 | 
			
		||||
                |> Async.AwaitTask
 | 
			
		||||
                |> Async.Ignore
 | 
			
		||||
            let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromMinutes(int shield.Cooldown)) timestamp
 | 
			
		||||
            do! sendFollowUpMessage event $"You are only allowed two shields at a time. Wait {cooldown} minutes to add another shield"
 | 
			
		||||
            do! DbService.updatePlayer <| { player with Actions = updatedDefenses }
 | 
			
		||||
        | true , _ ->
 | 
			
		||||
            let builder = DiscordFollowupMessageBuilder()
 | 
			
		||||
            builder.IsEphemeral <- true
 | 
			
		||||
            let timestamp = updatedDefenses |> Array.find (fun d -> d.ActionId = int shieldId) |> fun a -> a.Timestamp
 | 
			
		||||
            let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromHours(6)) timestamp
 | 
			
		||||
            builder.Content <- $"{shieldId} shield is already in use. Wait {cooldown} minutes to use this shield again"
 | 
			
		||||
            do! event.Interaction.CreateFollowupMessageAsync(builder)
 | 
			
		||||
                |> Async.AwaitTask
 | 
			
		||||
                |> Async.Ignore
 | 
			
		||||
            let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromMinutes(int shield.Cooldown)) timestamp
 | 
			
		||||
            do! sendFollowUpMessage event $"{shieldId} shield is already in use. Wait {cooldown} minutes to use this shield again"
 | 
			
		||||
            do! DbService.updatePlayer <| { player with Actions = updatedDefenses }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@
 | 
			
		||||
    "Class": {
 | 
			
		||||
      "Case": "Network"
 | 
			
		||||
    },
 | 
			
		||||
    "Cost": 100,
 | 
			
		||||
    "Cost": 50,
 | 
			
		||||
    "Power": 50,
 | 
			
		||||
    "Cooldown": 2
 | 
			
		||||
  },
 | 
			
		||||
@ -21,7 +21,7 @@
 | 
			
		||||
    "Class": {
 | 
			
		||||
      "Case": "Penetration"
 | 
			
		||||
    },
 | 
			
		||||
    "Cost": 100,
 | 
			
		||||
    "Cost": 50,
 | 
			
		||||
    "Power": 50,
 | 
			
		||||
    "Cooldown": 2
 | 
			
		||||
  },
 | 
			
		||||
@ -34,7 +34,7 @@
 | 
			
		||||
    "Class": {
 | 
			
		||||
      "Case": "Exploit"
 | 
			
		||||
    },
 | 
			
		||||
    "Cost": 100,
 | 
			
		||||
    "Cost": 50,
 | 
			
		||||
    "Power": 50,
 | 
			
		||||
    "Cooldown": 2
 | 
			
		||||
  },
 | 
			
		||||
@ -47,20 +47,7 @@
 | 
			
		||||
    "Class": {
 | 
			
		||||
      "Case": "Network"
 | 
			
		||||
    },
 | 
			
		||||
    "Cost": 100,
 | 
			
		||||
    "Power": 50,
 | 
			
		||||
    "Cooldown": 600
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Id": 7,
 | 
			
		||||
    "Name": "Encryption",
 | 
			
		||||
    "Type": {
 | 
			
		||||
      "Case": "Shield"
 | 
			
		||||
    },
 | 
			
		||||
    "Class": {
 | 
			
		||||
      "Case": "Exploit"
 | 
			
		||||
    },
 | 
			
		||||
    "Cost": 100,
 | 
			
		||||
    "Cost": 50,
 | 
			
		||||
    "Power": 50,
 | 
			
		||||
    "Cooldown": 600
 | 
			
		||||
  },
 | 
			
		||||
@ -73,7 +60,20 @@
 | 
			
		||||
    "Class": {
 | 
			
		||||
      "Case": "Penetration"
 | 
			
		||||
    },
 | 
			
		||||
    "Cost": 100,
 | 
			
		||||
    "Cost": 50,
 | 
			
		||||
    "Power": 50,
 | 
			
		||||
    "Cooldown": 600
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Id": 7,
 | 
			
		||||
    "Name": "Encryption",
 | 
			
		||||
    "Type": {
 | 
			
		||||
      "Case": "Shield"
 | 
			
		||||
    },
 | 
			
		||||
    "Class": {
 | 
			
		||||
      "Case": "Exploit"
 | 
			
		||||
    },
 | 
			
		||||
    "Cost": 50,
 | 
			
		||||
    "Power": 50,
 | 
			
		||||
    "Cooldown": 600
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										184
									
								
								Bot/Store.fs
									
									
									
									
									
								
							
							
						
						
									
										184
									
								
								Bot/Store.fs
									
									
									
									
									
								
							@ -9,90 +9,48 @@ open Degenz
 | 
			
		||||
open Degenz.Embeds
 | 
			
		||||
open Degenz.Messaging
 | 
			
		||||
 | 
			
		||||
let viewStore (ctx : InteractionContext) =
 | 
			
		||||
    async {
 | 
			
		||||
        do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, Embeds.storeListing Armory.battleItems)
 | 
			
		||||
            |> Async.AwaitTask
 | 
			
		||||
    } |> Async.StartAsTask
 | 
			
		||||
      :> Task
 | 
			
		||||
let handleResultWithResponse ctx fn (player : Result<PlayerData, string>) =
 | 
			
		||||
   match player with
 | 
			
		||||
   | Ok p -> fn p
 | 
			
		||||
   | Error e -> async { do! sendFollowUpMessageFromCtx ctx e }
 | 
			
		||||
 | 
			
		||||
let buyItem (ctx : InteractionContext) itemId =
 | 
			
		||||
    Game.executePlayerInteraction ctx (fun player -> async {
 | 
			
		||||
        let item = Armory.getItem itemId
 | 
			
		||||
        let newBalance = player.Bank - item.Cost
 | 
			
		||||
        if newBalance >= 0<GBT> 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! sendFollowUpMessageFromCtx ctx $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining"
 | 
			
		||||
            else
 | 
			
		||||
                do! sendFollowUpMessageFromCtx ctx $"You already own this item!"
 | 
			
		||||
        else
 | 
			
		||||
                do! sendFollowUpMessageFromCtx ctx $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT"
 | 
			
		||||
    })
 | 
			
		||||
let handleResultWithResponseFromEvent event fn (player : Result<PlayerData, string>) =
 | 
			
		||||
   match player with
 | 
			
		||||
   | Ok p -> fn p
 | 
			
		||||
   | Error e -> async { do! sendFollowUpMessage event e }
 | 
			
		||||
 | 
			
		||||
let checkHasSufficientFunds (item : BattleItem) player =
 | 
			
		||||
    if player.Bank - item.Cost >= 0<GBT>
 | 
			
		||||
       then Ok player
 | 
			
		||||
       else Error $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT"
 | 
			
		||||
 | 
			
		||||
let sell (ctx : InteractionContext) =
 | 
			
		||||
    Game.executePlayerInteraction ctx (fun player -> async {
 | 
			
		||||
        let hasInventoryToSell = Array.length player.Arsenal > 0
 | 
			
		||||
        if hasInventoryToSell then
 | 
			
		||||
            let builder = DiscordFollowupMessageBuilder()
 | 
			
		||||
            builder.AddEmbed (constructEmbed "Pick the item you wish to sell.") |> ignore
 | 
			
		||||
let checkAlreadyOwnsItem (item : BattleItem) player =
 | 
			
		||||
    if player.Arsenal |> Array.exists (fun w -> item.Id = w.Id)
 | 
			
		||||
        then Error $"You already own {item.Name}!"
 | 
			
		||||
        else Ok player
 | 
			
		||||
 | 
			
		||||
            Array.chunkBySize 5 player.Arsenal
 | 
			
		||||
            |> Array.iter
 | 
			
		||||
                (fun wps ->
 | 
			
		||||
                    wps
 | 
			
		||||
                    |> Array.map (fun w -> DiscordButtonComponent(ButtonStyle.Primary, $"{w.Type}-{w.Id}", $"{w.Name}"))
 | 
			
		||||
                    |> Seq.cast<DiscordComponent>
 | 
			
		||||
                    |> builder.AddComponents
 | 
			
		||||
                    |> ignore)
 | 
			
		||||
            builder.AsEphemeral true |> ignore
 | 
			
		||||
let checkSoldItemAlready (item : BattleItem) player =
 | 
			
		||||
    if player.Arsenal |> Array.exists (fun w -> item.Id = w.Id)
 | 
			
		||||
        then Ok player
 | 
			
		||||
        else Error $"{item.Name} not found in your arsenal! Looks like you sold it already."
 | 
			
		||||
 | 
			
		||||
            do! ctx.FollowUpAsync(builder)
 | 
			
		||||
                |> Async.AwaitTask
 | 
			
		||||
                |> Async.Ignore
 | 
			
		||||
        else
 | 
			
		||||
            do! sendFollowUpMessageFromCtx ctx "You currently have no inventory to sell"
 | 
			
		||||
    })
 | 
			
		||||
let checkHasItemsInArsenal itemType player =
 | 
			
		||||
    if player.Arsenal |> Array.filter (fun i -> i.Type = itemType ) |> Array.length > 0
 | 
			
		||||
        then Ok player
 | 
			
		||||
        else Error $"You currently have no {itemType}s in your arsenal to sell!"
 | 
			
		||||
 | 
			
		||||
let handleBuyItem (ctx : InteractionContext) itemId =
 | 
			
		||||
    Game.executePlayerInteraction ctx (fun player -> async {
 | 
			
		||||
        let item = Armory.battleItems |> Array.find (fun w -> w.Id = itemId)
 | 
			
		||||
        let newBalance = player.Bank - item.Cost
 | 
			
		||||
        if newBalance >= 0<GBT> 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! sendFollowUpMessageFromCtx ctx $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining"
 | 
			
		||||
            else
 | 
			
		||||
                do! sendFollowUpMessageFromCtx ctx $"You already own this item!"
 | 
			
		||||
        else
 | 
			
		||||
            do! sendFollowUpMessageFromCtx ctx $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT"
 | 
			
		||||
    })
 | 
			
		||||
let statusFormat p =
 | 
			
		||||
    $"**Hacks:** {Player.hacks p |> battleItemFormat}\n
 | 
			
		||||
**Shields:** {Player.shields p |> battleItemFormat}\n
 | 
			
		||||
**Hack Attacks:**\n{Player.attacks p |> actionFormat}\n
 | 
			
		||||
**Active Shields:**\n{Player.defenses p |> actionFormat}"
 | 
			
		||||
 | 
			
		||||
let handleSellButtonEvents (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) =
 | 
			
		||||
    Game.executePlayerEvent event (fun player -> async {
 | 
			
		||||
        let itemId = int <| event.Id.Split("-").[1]
 | 
			
		||||
        let item = Armory.getItem 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 = DiscordFollowupMessageBuilder()
 | 
			
		||||
        builder.IsEphemeral <- true
 | 
			
		||||
        builder.Content <- $"Sold {item.Type} {item.Name} for {item.Cost}! Current Balance: {updatedPlayer.Bank}"
 | 
			
		||||
        do! event.Interaction.CreateFollowupMessageAsync(builder)
 | 
			
		||||
            |> Async.AwaitTask
 | 
			
		||||
            |> Async.Ignore
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
let status (ctx : InteractionContext) =
 | 
			
		||||
let arsenal (ctx : InteractionContext) =
 | 
			
		||||
    Game.executePlayerInteraction ctx (fun player -> async {
 | 
			
		||||
        let updatedPlayer = Player.removeExpiredActions player
 | 
			
		||||
        let builder = DiscordFollowupMessageBuilder()
 | 
			
		||||
        let embed = DiscordEmbedBuilder()
 | 
			
		||||
        embed.AddField("Arsenal", Messaging.statusFormat updatedPlayer) |> ignore
 | 
			
		||||
        embed.AddField("Arsenal", statusFormat updatedPlayer) |> ignore
 | 
			
		||||
        builder.AddEmbed(embed) |> ignore
 | 
			
		||||
        builder.IsEphemeral <- true
 | 
			
		||||
        do! ctx.FollowUpAsync(builder)
 | 
			
		||||
@ -101,23 +59,83 @@ let status (ctx : InteractionContext) =
 | 
			
		||||
        do! DbService.updatePlayer updatedPlayer
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
let buy (ctx : InteractionContext) itemType =
 | 
			
		||||
    Game.executePlayerInteraction ctx (fun player -> async {
 | 
			
		||||
        let itemStore = Embeds.getBuyItemsEmbed itemType Armory.battleItems
 | 
			
		||||
        do! ctx.Interaction.CreateFollowupMessageAsync(itemStore)
 | 
			
		||||
            |> Async.AwaitTask
 | 
			
		||||
            |> Async.Ignore
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
// TODO: Remove active shield when selling
 | 
			
		||||
let sell (ctx : InteractionContext) itemType =
 | 
			
		||||
    Game.executePlayerInteraction ctx (fun player -> async {
 | 
			
		||||
        match checkHasItemsInArsenal itemType player with
 | 
			
		||||
        | Ok _ ->
 | 
			
		||||
            let itemStore = Embeds.getSellItemsEmbed itemType player
 | 
			
		||||
 | 
			
		||||
            do! ctx.FollowUpAsync(itemStore)
 | 
			
		||||
                |> Async.AwaitTask
 | 
			
		||||
                |> Async.Ignore
 | 
			
		||||
        | Error e -> do! sendFollowUpMessageFromCtx ctx e
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
// TODO: When you buy a shield, prompt the user to activate it
 | 
			
		||||
let handleBuyItem (event : ComponentInteractionCreateEventArgs) itemId =
 | 
			
		||||
    Game.executePlayerEvent event (fun player -> async {
 | 
			
		||||
        let item = Armory.battleItems |> Array.find (fun w -> w.Id = itemId)
 | 
			
		||||
        do! player
 | 
			
		||||
            |> checkHasSufficientFunds item
 | 
			
		||||
            >>= checkAlreadyOwnsItem item
 | 
			
		||||
            |> handleResultWithResponseFromEvent event (fun player -> async {
 | 
			
		||||
                let newBalance = player.Bank - item.Cost
 | 
			
		||||
                let p = { player with Bank = newBalance ; Arsenal = Array.append [| item |] player.Arsenal }
 | 
			
		||||
                do! DbService.updatePlayer p
 | 
			
		||||
                do! sendFollowUpMessage event $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining"
 | 
			
		||||
            })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
let handleSell (event : ComponentInteractionCreateEventArgs) itemId =
 | 
			
		||||
    Game.executePlayerEvent event (fun player -> async {
 | 
			
		||||
        let item = Armory.getItem itemId
 | 
			
		||||
        do! player
 | 
			
		||||
            |> checkSoldItemAlready item
 | 
			
		||||
            |> handleResultWithResponseFromEvent event (fun player -> async {
 | 
			
		||||
                let updatedPlayer = { player with Bank = player.Bank + item.Cost ; Arsenal = player.Arsenal |> Array.filter (fun w -> w.Id <> itemId) }
 | 
			
		||||
                do! DbService.updatePlayer updatedPlayer
 | 
			
		||||
                do! sendFollowUpMessage event $"Sold {item.Type} {item.Name} for {item.Cost}! Current Balance: {updatedPlayer.Bank}"
 | 
			
		||||
            })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
let handleStoreEvents (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) =
 | 
			
		||||
    let itemId = int <| event.Id.Split("-").[1]
 | 
			
		||||
    match event.Id with
 | 
			
		||||
    | id when id.StartsWith("Buy") -> handleBuyItem event itemId
 | 
			
		||||
    | id when id.StartsWith("Sell") -> handleSell event itemId
 | 
			
		||||
    | _ ->
 | 
			
		||||
        task {
 | 
			
		||||
            let builder = DiscordInteractionResponseBuilder()
 | 
			
		||||
            builder.IsEphemeral <- true
 | 
			
		||||
            builder.Content <- $"Incorrect Action identifier {event.Id}"
 | 
			
		||||
            do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder)
 | 
			
		||||
                |> Async.AwaitTask
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
type Store() =
 | 
			
		||||
    inherit ApplicationCommandModule ()
 | 
			
		||||
 | 
			
		||||
    [<SlashCommand("armory", "View Hacks & Shields available for purchase")>]
 | 
			
		||||
    member _.Armory (ctx : InteractionContext) = viewStore ctx
 | 
			
		||||
 | 
			
		||||
    [<SlashCommand("arsenal", "Get the Hacks and Shields you own, and which ones are active")>]
 | 
			
		||||
    member this.Arsenal (ctx : InteractionContext) = status ctx
 | 
			
		||||
    member this.Arsenal (ctx : InteractionContext) = arsenal ctx
 | 
			
		||||
 | 
			
		||||
    [<SlashCommand("buy-hack", "Purchase a hack attack you can use to earn GoodBoyTokenz")>]
 | 
			
		||||
    member _.BuyHack (ctx : InteractionContext, [<Option("hack-id", "The ID of the hack you wish to purchase")>] hackId : HackId) =
 | 
			
		||||
        buyItem ctx (int hackId)
 | 
			
		||||
    member _.BuyHack (ctx : InteractionContext) = buy ctx ItemType.Hack
 | 
			
		||||
 | 
			
		||||
    [<SlashCommand("buy-shield", "Purchase a hack shield so you can protect your GoodBoyTokenz")>]
 | 
			
		||||
    member this.BuyShield (ctx : InteractionContext, [<Option("shield-id", "The ID of the shield you wish to purchase")>] shieldId : ShieldId) =
 | 
			
		||||
        buyItem ctx (int shieldId)
 | 
			
		||||
    member this.BuyShield (ctx : InteractionContext) = buy ctx ItemType.Shield
 | 
			
		||||
 | 
			
		||||
    [<SlashCommand("sell", "Sell an item in your inventory for GoodBoyTokenz")>]
 | 
			
		||||
    member this.SellItem (ctx : InteractionContext) = sell ctx
 | 
			
		||||
    [<SlashCommand("sell-hack", "Sell a hack for GoodBoyTokenz")>]
 | 
			
		||||
    member this.SellHack (ctx : InteractionContext) = sell ctx ItemType.Hack
 | 
			
		||||
 | 
			
		||||
    [<SlashCommand("sell-shield", "Sell a shield for GoodBoyTokenz")>]
 | 
			
		||||
    member this.SellShield (ctx : InteractionContext) = sell ctx ItemType.Shield
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,10 @@ open DSharpPlus.EventArgs
 | 
			
		||||
open DSharpPlus.SlashCommands
 | 
			
		||||
open Newtonsoft.Json
 | 
			
		||||
 | 
			
		||||
[<Microsoft.FSharp.Core.AutoOpen>]
 | 
			
		||||
module ResultHelpers =
 | 
			
		||||
    let (>>=) x f = Result.bind f x
 | 
			
		||||
 | 
			
		||||
[<Microsoft.FSharp.Core.AutoOpen>]
 | 
			
		||||
module Types =
 | 
			
		||||
 | 
			
		||||
@ -83,32 +87,6 @@ module Armory =
 | 
			
		||||
 | 
			
		||||
    let getItem itemId = battleItems |> Array.find (fun w -> w.Id = itemId)
 | 
			
		||||
 | 
			
		||||
module Player =
 | 
			
		||||
    let SameTargetAttackCooldown = TimeSpan.FromHours(2)
 | 
			
		||||
    let hacks 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.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)
 | 
			
		||||
 | 
			
		||||
    let removeExpiredActions player =
 | 
			
		||||
        let actions =
 | 
			
		||||
            player.Actions
 | 
			
		||||
            |> Array.filter (fun (act : Action) ->
 | 
			
		||||
                match act.Type with
 | 
			
		||||
                // So the player doesnt attack the same player so many times in a row
 | 
			
		||||
                | Attack _ -> DateTime.UtcNow - act.Timestamp < SameTargetAttackCooldown
 | 
			
		||||
                | Defense ->
 | 
			
		||||
                    let item = Armory.getItem act.ActionId
 | 
			
		||||
                    DateTime.UtcNow - act.Timestamp < TimeSpan.FromMinutes(int item.Cooldown))
 | 
			
		||||
        { player with Actions = actions }
 | 
			
		||||
 | 
			
		||||
    let modifyBank player amount = { player with Bank = max (player.Bank + amount) 0<GBT> }
 | 
			
		||||
 | 
			
		||||
    let getAttacksFlat actions = actions |> Array.choose (fun act -> match act.Type with Attack ar -> Some (act,ar.Target,ar.Result) | Defense -> None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
module Messaging =
 | 
			
		||||
    type InteractiveMessage = {
 | 
			
		||||
        ButtonId : string
 | 
			
		||||
@ -140,16 +118,10 @@ module Messaging =
 | 
			
		||||
                | Attack atk -> $"Hacked {atk.Target.Name} at {act.Timestamp.ToShortTimeString()}"
 | 
			
		||||
                | Defense ->
 | 
			
		||||
                    let item = Armory.getItem act.ActionId
 | 
			
		||||
                    let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromMinutes(int item.Cooldown)) act.Timestamp
 | 
			
		||||
                    let cooldown = getTimeTillCooldownFinishes (System.TimeSpan.FromMinutes(int item.Cooldown)) act.Timestamp
 | 
			
		||||
                    $"{item.Name} active for {cooldown}")
 | 
			
		||||
            |> String.concat "\n"
 | 
			
		||||
 | 
			
		||||
    let statusFormat p =
 | 
			
		||||
        $"**Hacks:** {Player.hacks p |> battleItemFormat}\n
 | 
			
		||||
**Shields:** {Player.shields p |> battleItemFormat}\n
 | 
			
		||||
**Hack Attacks:**\n{Player.attacks p |> actionFormat}\n
 | 
			
		||||
**Active Shields:** {Player.defenses p |> actionFormat}"
 | 
			
		||||
 | 
			
		||||
    let constructButtons (actionType: string) (playerInfo: string) (weapons: BattleItem array) =
 | 
			
		||||
        weapons
 | 
			
		||||
        |> Array.map (fun w -> DiscordButtonComponent(ButtonStyle.Success, $"{actionType}-{w.Id}-{playerInfo}", $"{w.Name}"))
 | 
			
		||||
@ -226,13 +198,3 @@ module Messaging =
 | 
			
		||||
            do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    let sendInteractionEventWithButton (event : ComponentInteractionCreateEventArgs) buttonId msg =
 | 
			
		||||
        async {
 | 
			
		||||
            let builder = DiscordInteractionResponseBuilder()
 | 
			
		||||
            let button = DiscordButtonComponent(ButtonStyle.Success, buttonId, "Got it") :> DiscordComponent
 | 
			
		||||
            builder.AddComponents [| button |] |> ignore
 | 
			
		||||
            builder.IsEphemeral <- true
 | 
			
		||||
            builder.Content <- msg
 | 
			
		||||
            do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user