New store flow

This commit is contained in:
Joseph Ferano 2022-02-07 21:53:20 +07:00
parent 36471195aa
commit dcf0bdb174
8 changed files with 316 additions and 309 deletions

View File

@ -4,7 +4,6 @@ open System.Threading.Tasks
open DSharpPlus open DSharpPlus
open DSharpPlus.SlashCommands open DSharpPlus.SlashCommands
open Degenz open Degenz
open Degenz.PlayerInteractions
open Degenz.HackerBattle open Degenz.HackerBattle
open Degenz.Store open Degenz.Store
open Emzi0767.Utilities open Emzi0767.Utilities
@ -12,46 +11,41 @@ open Emzi0767.Utilities
type EmptyGlobalCommandToAvoidFamousDuplicateSlashCommandsBug() = inherit ApplicationCommandModule () type EmptyGlobalCommandToAvoidFamousDuplicateSlashCommandsBug() = inherit ApplicationCommandModule ()
let playerInteractionsConfig = DiscordConfiguration() let guild = GuildEnvironment.guildId
let hackerBattleConfig = DiscordConfiguration() let hackerBattleConfig = DiscordConfiguration()
let storeConfig = DiscordConfiguration() let storeConfig = DiscordConfiguration()
//let slotMachineConfig = DiscordConfiguration() //let slotMachineConfig = DiscordConfiguration()
//hackerBattleConfig.MinimumLogLevel <- Microsoft.Extensions.Logging.LogLevel.Trace //hackerBattleConfig.MinimumLogLevel <- Microsoft.Extensions.Logging.LogLevel.Trace
//let configs = [| playerInteractionsConfig ; hackerBattleConfig ; storeConfig ; slotMachineConfig ; |] //let configs = [| hackerBattleConfig ; storeConfig ; slotMachineConfig ; |]
let configs = [| playerInteractionsConfig ; hackerBattleConfig ; storeConfig |] let configs = [ hackerBattleConfig ; storeConfig ]
for conf in configs do for conf in configs do
conf.TokenType <- TokenType.Bot conf.TokenType <- TokenType.Bot
conf.Intents <- DiscordIntents.All conf.Intents <- DiscordIntents.All
let guild = GuildEnvironment.guildId
playerInteractionsConfig.Token <- GuildEnvironment.tokenPlayerInteractions
hackerBattleConfig.Token <- GuildEnvironment.tokenHackerBattle hackerBattleConfig.Token <- GuildEnvironment.tokenHackerBattle
storeConfig.Token <- GuildEnvironment.tokenStore storeConfig.Token <- GuildEnvironment.tokenStore
//slotMachineConfig.Token <- Environment.GetEnvironmentVariable("BOT_SLOT_MACHINE") //slotMachineConfig.Token <- Environment.GetEnvironmentVariable("BOT_SLOT_MACHINE")
//let playerInteractionsBot = new DiscordClient(playerInteractionsConfig)
let hackerBattleBot = new DiscordClient(hackerBattleConfig) let hackerBattleBot = new DiscordClient(hackerBattleConfig)
let storeBot = new DiscordClient(storeConfig) let storeBot = new DiscordClient(storeConfig)
//let slotMachineBot = new DiscordClient(slotMachineConfig) //let slotMachineBot = new DiscordClient(slotMachineConfig)
//let clients = [| storeBot ; trainerBot ; hackerBattleBot ; playerInteractionsBot ; slotMachineBot |] //let clients = [| hackerBattleBot ; storeBot ; slotMachineBot |]
//let clients = [| hackerBattleBot ; storeBot ; playerInteractionsBot |] let clients = [ hackerBattleBot ; storeBot ]
let clients = [| hackerBattleBot ; storeBot |]
//let sc1 = playerInteractionsBot.UseSlashCommands() let sc1 = hackerBattleBot.UseSlashCommands()
let sc3 = hackerBattleBot.UseSlashCommands() let sc2 = storeBot.UseSlashCommands()
let sc4 = storeBot.UseSlashCommands() //let sc3 = slotMachineBot.UseSlashCommands()
//let sc5 = slotMachineBot.UseSlashCommands()
//sc1.RegisterCommands<PlayerInteractions>(guild); sc1.RegisterCommands<HackerGame>(guild);
sc3.RegisterCommands<HackerGame>(guild); sc2.RegisterCommands<Store>(guild);
sc4.RegisterCommands<Store>(guild); //sc3.RegisterCommands<SlotMachine>(guild);
//sc5.RegisterCommands<SlotMachine>(guild);
hackerBattleBot.add_ComponentInteractionCreated(AsyncEventHandler(HackerBattle.handleButtonEvent)) 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) = let asdf (_ : DiscordClient) (event : DSharpPlus.EventArgs.InteractionCreateEventArgs) =
async { async {
@ -74,11 +68,10 @@ let run (client : DiscordClient) =
do! client.ConnectAsync () |> Async.AwaitTask do! client.ConnectAsync () |> Async.AwaitTask
} }
Trainer.sendInitialEmbed hackerBattleBot //Trainer.sendInitialEmbed hackerBattleBot
clients clients
|> Array.map run |> List.map run
|> Array.toSeq
|> Async.Sequential |> Async.Sequential
|> Async.RunSynchronously |> Async.RunSynchronously
|> ignore |> ignore

View File

@ -13,12 +13,12 @@
<Content Include="paket.references"/> <Content Include="paket.references"/>
<Compile Include="GuildEnvironment.fs"/> <Compile Include="GuildEnvironment.fs"/>
<Compile Include="Game.fs"/> <Compile Include="Game.fs"/>
<Compile Include="PlayerInteractions.fs"/>
<Compile Include="Embeds.fs"/> <Compile Include="Embeds.fs"/>
<Compile Include="Store.fs"/> <Compile Include="Store.fs"/>
<Compile Include="Trainer.fs"/> <Compile Include="Trainer.fs"/>
<Compile Include="HackerBattle.fs"/> <Compile Include="HackerBattle.fs"/>
<Compile Include="SlotMachine.fs"/> <Compile Include="SlotMachine.fs"/>
<Compile Include="PlayerInteractions.fs" />
<Compile Include="Bot.fs"/> <Compile Include="Bot.fs"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -4,35 +4,22 @@ open System
open DSharpPlus.EventArgs open DSharpPlus.EventArgs
open Degenz.Types open Degenz.Types
open DSharpPlus.Entities open DSharpPlus.Entities
open AsciiTableFormatter
let hackGif = "https://s10.gifyu.com/images/Hacker-Degenz-V20ce8eb832734aa62-min.gif" 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 shieldGif = "https://s10.gifyu.com/images/Defense-Degenz-V2-min.gif"
let getHackGif = function let getHackGif = function
| HackId.Virus -> "https://s10.gifyu.com/images/Attack-DegenZ-1.gif" | HackId.Virus -> "https://s10.gifyu.com/images/Virus-icon.jpg"
| HackId.RemoteAccess -> "https://s10.gifyu.com/images/Mind-Control-Degenz-V2-min.gif" | HackId.RemoteAccess -> "https://s10.gifyu.com/images/Mind-Control-Degenz-V2-min.jpg"
| HackId.Worm -> "https://s10.gifyu.com/images/WormBugAttack_Degenz-min.gif" | HackId.Worm -> "https://s10.gifyu.com/images/WormBugAttack_Degenz-min.jpg"
| _ -> hackGif | _ -> hackGif
let getShieldGif = function let getShieldGif = function
| ShieldId.Firewall -> "https://s10.gifyu.com/images/Defense-GIF-1-Degenz-min.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.gif" | ShieldId.Encryption -> "https://s10.gifyu.com/images/Encryption-Degenz-V2-1-min.jpg"
| ShieldId.Cypher -> "https://s10.gifyu.com/images/Cypher-Smaller.gif" | ShieldId.Cypher -> "https://s10.gifyu.com/images/Cypher-Smaller.jpg"
| _ -> shieldGif | _ -> 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 pickDefense actionId player =
let buttons = let buttons =
Messaging.constructButtons actionId (string player.DiscordId) (Player.shields player) Messaging.constructButtons actionId (string player.DiscordId) (Player.shields player)
@ -85,43 +72,73 @@ let responseCreatedShieldTrainer (shield : BattleItem) =
DiscordFollowupMessageBuilder() DiscordFollowupMessageBuilder()
.AddEmbed(DiscordEmbedBuilder().WithImageUrl(getShieldGif (enum<ShieldId>(shield.Id)))) .AddEmbed(DiscordEmbedBuilder().WithImageUrl(getShieldGif (enum<ShieldId>(shield.Id))))
.AsEphemeral(true) .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 = let eventSuccessfulHack (event : ComponentInteractionCreateEventArgs) targetId prize =
DiscordMessageBuilder() DiscordMessageBuilder()
.WithContent($"{event.User.Username} successfully hacked <@{targetId}> for a total of {prize} GoodBoyTokenz") .WithContent($"{event.User.Username} successfully hacked <@{targetId}> for a total of {prize} GoodBoyTokenz")
let eventFailedHack (event : ComponentInteractionCreateEventArgs) targetId prize = let eventFailedHack (event : ComponentInteractionCreateEventArgs) targetId prize =
// let embed =
// DiscordEmbedBuilder()
// .WithColor(DiscordColor.Blurple)
// .WithDescription("Pick the hack that you want to use")
// .WithImageUrl(hackGif)
//
DiscordMessageBuilder() DiscordMessageBuilder()
.WithContent($"{event.User.Username} successfully hacked <@{targetId}> for a total of {prize} GoodBoyTokenz") .WithContent($"{event.User.Username} successfully hacked <@{targetId}> for a total of {prize} GoodBoyTokenz")
[<CLIMutable>] let getGoodAgainst = function
type Table = { | BattleClass.Network -> ( ShieldId.Firewall , HackId.Virus )
Name : string | BattleClass.Penetration -> ( ShieldId.Cypher , HackId.RemoteAccess )
Cost : string | BattleClass.Exploit -> ( ShieldId.Encryption , HackId.Worm )
Class : string
}
let storeListing store = let getBuyItemsEmbed (itemType : ItemType) (store : BattleItem array) =
let embeds = let embeds , buttons =
store store
|> Array.groupBy (fun (bi : BattleItem) -> bi.Type) |> Array.filter (fun i -> i.Type = itemType)
|> Array.map (fun ( itemType , items ) -> |> Array.map (fun item ->
let msg = let embed = DiscordEmbedBuilder()
items match item.Type with
|> Array.map (fun item -> { Name = item.Name ; Cost = string item.Cost ; Class = string item.Class }) | Hack ->
|> Formatter.Format embed
|> sprintf "**%As**\n``` %s ```" itemType .AddField($"Weak Against |", getGoodAgainst item.Class |> fst |> string , true)
DiscordEmbedBuilder() .AddField("Cooldown |", $"{TimeSpan.FromMinutes(int item.Cooldown).Minutes} minutes", true)
.WithDescription(msg) .WithThumbnail(getHackGif (enum<HackId>(item.Id)))
.Build()) |> 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) .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) .AsEphemeral(true)

View File

@ -1,17 +1,28 @@
module Degenz.Game namespace Degenz
open System
open System.Threading.Tasks open System.Threading.Tasks
open DSharpPlus open DSharpPlus
open DSharpPlus.Entities open DSharpPlus.Entities
open DSharpPlus.EventArgs open DSharpPlus.EventArgs
open DSharpPlus.SlashCommands open DSharpPlus.SlashCommands
open Degenz.DbService open Degenz.DbService
open Microsoft.VisualBasic
module Game =
let HackPrize = 10<GBT> let HackPrize = 10<GBT>
let ShieldPrize = 5<GBT> let ShieldPrize = 5<GBT>
let SameTargetAttackCooldown = System.TimeSpan.FromHours(6)
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>) = let executePlayerInteraction (ctx : InteractionContext) (dispatch : PlayerData -> Async<unit>) =
async { async {
let builder = DiscordInteractionResponseBuilder() let builder = DiscordInteractionResponseBuilder()
@ -41,3 +52,27 @@ let executePlayerEvent (event : ComponentInteractionCreateEventArgs) (dispatch :
} |> Async.StartAsTask } |> Async.StartAsTask
:> Task :> 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)

View File

@ -9,24 +9,22 @@ open DSharpPlus.SlashCommands
open Degenz open Degenz
open Degenz.Messaging open Degenz.Messaging
let (>>=) x f = Result.bind f x let checkPlayerIsAttackingThemselves defender attacker =
let checkIfPlayerIsAttackingThemselves defender attacker =
match attacker.DiscordId = defender.DiscordId with match attacker.DiscordId = defender.DiscordId with
| true -> Error "You think you're clever? You can't hack yourself, pal." | true -> Error "You think you're clever? You can't hack yourself, pal."
| false -> Ok attacker | false -> Ok attacker
let checkForExistingTarget defenderId attacker = let checkAlreadyHackedTarget defenderId attacker =
attacker.Actions attacker.Actions
|> Player.getAttacksFlat |> Player.getAttacksFlat
|> Array.tryFind (fun (_,t,_) -> t.Id = defenderId) |> Array.tryFind (fun (_,t,_) -> t.Id = defenderId)
|> function |> function
| Some ( atk , target , _ ) -> | Some ( atk , target , _ ) ->
let cooldown = getTimeTillCooldownFinishes Player.SameTargetAttackCooldown atk.Timestamp let cooldown = getTimeTillCooldownFinishes Game.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}." 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 | None -> Ok attacker
let checkIfHackHasCooldown hackId attacker = let checkHackHasCooldown hackId attacker =
let mostRecentHackAttack = let mostRecentHackAttack =
attacker.Actions attacker.Actions
|> Array.tryFind (fun a -> a.ActionId = hackId) |> 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) let item = Armory.battleItems |> Array.find (fun i -> i.Id = hackId)
Error $"{item.Name} is currently on cooldown, wait {cooldown} to use it again." Error $"{item.Name} is currently on cooldown, wait {cooldown} to use it again."
let checkIfHasEmptyHacks attacker = let checkHasEmptyHacks attacker =
match Player.hacks attacker with 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." | [||] -> Error $"You currently do not have any Hacks to steal 💰$GBT from others. Please go to the <#{GuildEnvironment.channelArmory}> and purchase one."
| _ -> Ok attacker | _ -> Ok attacker
let checkIfTargetHasMoney (target : PlayerData) attacker = let checkTargetHasMoney (target : PlayerData) attacker =
if target.Bank < Game.HackPrize if target.Bank < Game.HackPrize
then Error $"{target.Name} does not have enough 💰$GBT to steal from, the broke loser. Pick a different target." then Error $"{target.Name} does not have enough 💰$GBT to steal from, the broke loser. Pick a different target."
else Ok attacker else Ok attacker
@ -116,24 +114,17 @@ let attack (target : DiscordUser) (ctx : InteractionContext) =
| Some defender -> | Some defender ->
do! attacker do! attacker
|> Player.removeExpiredActions |> Player.removeExpiredActions
|> checkForExistingTarget defender.DiscordId |> checkAlreadyHackedTarget defender.DiscordId
>>= checkIfHasEmptyHacks >>= checkHasEmptyHacks
>>= checkIfTargetHasMoney defender >>= checkTargetHasMoney defender
>>= checkIfPlayerIsAttackingThemselves defender >>= checkPlayerIsAttackingThemselves defender
|> function |> function
| Ok _ -> | Ok _ ->
let embed = Embeds.pickHack "Attack" attacker defender let embed = Embeds.pickHack "Attack" attacker defender
ctx.FollowUpAsync(embed) ctx.FollowUpAsync(embed)
|> Async.AwaitTask |> Async.AwaitTask
|> Async.Ignore |> Async.Ignore
| Error msg -> | Error msg -> sendFollowUpMessageFromCtx ctx msg
let builder =
DiscordFollowupMessageBuilder()
.WithContent(msg)
.AsEphemeral(true)
ctx.FollowUpAsync(builder)
|> Async.AwaitTask
|> Async.Ignore
| None -> do! sendFollowUpMessageFromCtx ctx "Your target is not connected to the network, they must join first by using the /redpill command" | 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 -> | Some defender , true , true ->
do! attacker do! attacker
|> Player.removeExpiredActions |> Player.removeExpiredActions
|> checkForExistingTarget defender.DiscordId |> checkAlreadyHackedTarget defender.DiscordId
>>= (checkIfHackHasCooldown (int hack)) >>= (checkHackHasCooldown (int hack))
|> function |> function
| Ok _ -> | Ok _ ->
runHackerBattle defender (Armory.getItem (int hack)) runHackerBattle defender (Armory.getItem (int hack))
@ -175,17 +166,18 @@ let defend (ctx : InteractionContext) =
let handleDefense (event : ComponentInteractionCreateEventArgs) = let handleDefense (event : ComponentInteractionCreateEventArgs) =
Game.executePlayerEvent event (fun player -> async { Game.executePlayerEvent event (fun player -> async {
let split = event.Id.Split("-") 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 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 match alreadyUsedShield , updatedDefenses.Length < 2 with
| false , true -> | false , true ->
let embed = Embeds.responseCreatedShield (Armory.getItem (int shieldId)) let embed = Embeds.responseCreatedShield (Armory.getItem shieldId)
do! event.Interaction.CreateFollowupMessageAsync(embed) do! event.Interaction.CreateFollowupMessageAsync(embed)
|> Async.AwaitTask |> Async.AwaitTask
|> Async.Ignore |> 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 } do! DbService.updatePlayer <| { player with Actions = Array.append [| defense |] player.Actions }
let builder = DiscordMessageBuilder() let builder = DiscordMessageBuilder()
builder.WithContent($"{event.User.Username} has protected their system!") |> ignore builder.WithContent($"{event.User.Username} has protected their system!") |> ignore
@ -194,24 +186,14 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) =
|> Async.AwaitTask |> Async.AwaitTask
|> Async.Ignore |> Async.Ignore
| _ , false -> | _ , 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 timestamp = updatedDefenses |> Array.rev |> Array.head |> fun a -> a.Timestamp // This should be the next expiring timestamp
let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromHours(6)) timestamp let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromMinutes(int shield.Cooldown)) timestamp
builder.Content <- $"You are only allowed two shields at a time. Wait {cooldown} minutes to add another shield" do! sendFollowUpMessage event $"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
do! DbService.updatePlayer <| { player with Actions = updatedDefenses } do! DbService.updatePlayer <| { player with Actions = updatedDefenses }
| true , _ -> | true , _ ->
let builder = DiscordFollowupMessageBuilder()
builder.IsEphemeral <- true
let timestamp = updatedDefenses |> Array.find (fun d -> d.ActionId = int shieldId) |> fun a -> a.Timestamp let timestamp = updatedDefenses |> Array.find (fun d -> d.ActionId = int shieldId) |> fun a -> a.Timestamp
let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromHours(6)) timestamp let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromMinutes(int shield.Cooldown)) timestamp
builder.Content <- $"{shieldId} shield is already in use. Wait {cooldown} minutes to use this shield again" do! sendFollowUpMessage event $"{shieldId} shield is already in use. Wait {cooldown} minutes to use this shield again"
do! event.Interaction.CreateFollowupMessageAsync(builder)
|> Async.AwaitTask
|> Async.Ignore
do! DbService.updatePlayer <| { player with Actions = updatedDefenses } do! DbService.updatePlayer <| { player with Actions = updatedDefenses }
}) })

View File

@ -8,7 +8,7 @@
"Class": { "Class": {
"Case": "Network" "Case": "Network"
}, },
"Cost": 100, "Cost": 50,
"Power": 50, "Power": 50,
"Cooldown": 2 "Cooldown": 2
}, },
@ -21,7 +21,7 @@
"Class": { "Class": {
"Case": "Penetration" "Case": "Penetration"
}, },
"Cost": 100, "Cost": 50,
"Power": 50, "Power": 50,
"Cooldown": 2 "Cooldown": 2
}, },
@ -34,7 +34,7 @@
"Class": { "Class": {
"Case": "Exploit" "Case": "Exploit"
}, },
"Cost": 100, "Cost": 50,
"Power": 50, "Power": 50,
"Cooldown": 2 "Cooldown": 2
}, },
@ -47,20 +47,7 @@
"Class": { "Class": {
"Case": "Network" "Case": "Network"
}, },
"Cost": 100, "Cost": 50,
"Power": 50,
"Cooldown": 600
},
{
"Id": 7,
"Name": "Encryption",
"Type": {
"Case": "Shield"
},
"Class": {
"Case": "Exploit"
},
"Cost": 100,
"Power": 50, "Power": 50,
"Cooldown": 600 "Cooldown": 600
}, },
@ -73,7 +60,20 @@
"Class": { "Class": {
"Case": "Penetration" "Case": "Penetration"
}, },
"Cost": 100, "Cost": 50,
"Power": 50,
"Cooldown": 600
},
{
"Id": 7,
"Name": "Encryption",
"Type": {
"Case": "Shield"
},
"Class": {
"Case": "Exploit"
},
"Cost": 50,
"Power": 50, "Power": 50,
"Cooldown": 600 "Cooldown": 600
} }

View File

@ -9,90 +9,48 @@ open Degenz
open Degenz.Embeds open Degenz.Embeds
open Degenz.Messaging open Degenz.Messaging
let viewStore (ctx : InteractionContext) = let handleResultWithResponse ctx fn (player : Result<PlayerData, string>) =
async { match player with
do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, Embeds.storeListing Armory.battleItems) | Ok p -> fn p
|> Async.AwaitTask | Error e -> async { do! sendFollowUpMessageFromCtx ctx e }
} |> Async.StartAsTask
:> Task
let buyItem (ctx : InteractionContext) itemId = let handleResultWithResponseFromEvent event fn (player : Result<PlayerData, string>) =
Game.executePlayerInteraction ctx (fun player -> async { match player with
let item = Armory.getItem itemId | Ok p -> fn p
let newBalance = player.Bank - item.Cost | Error e -> async { do! sendFollowUpMessage event e }
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 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) = let checkAlreadyOwnsItem (item : BattleItem) player =
Game.executePlayerInteraction ctx (fun player -> async { if player.Arsenal |> Array.exists (fun w -> item.Id = w.Id)
let hasInventoryToSell = Array.length player.Arsenal > 0 then Error $"You already own {item.Name}!"
if hasInventoryToSell then else Ok player
let builder = DiscordFollowupMessageBuilder()
builder.AddEmbed (constructEmbed "Pick the item you wish to sell.") |> ignore
Array.chunkBySize 5 player.Arsenal let checkSoldItemAlready (item : BattleItem) player =
|> Array.iter if player.Arsenal |> Array.exists (fun w -> item.Id = w.Id)
(fun wps -> then Ok player
wps else Error $"{item.Name} not found in your arsenal! Looks like you sold it already."
|> Array.map (fun w -> DiscordButtonComponent(ButtonStyle.Primary, $"{w.Type}-{w.Id}", $"{w.Name}"))
|> Seq.cast<DiscordComponent>
|> builder.AddComponents
|> ignore)
builder.AsEphemeral true |> ignore
do! ctx.FollowUpAsync(builder) let checkHasItemsInArsenal itemType player =
|> Async.AwaitTask if player.Arsenal |> Array.filter (fun i -> i.Type = itemType ) |> Array.length > 0
|> Async.Ignore then Ok player
else else Error $"You currently have no {itemType}s in your arsenal to sell!"
do! sendFollowUpMessageFromCtx ctx "You currently have no inventory to sell"
})
let handleBuyItem (ctx : InteractionContext) itemId = let statusFormat p =
Game.executePlayerInteraction ctx (fun player -> async { $"**Hacks:** {Player.hacks p |> battleItemFormat}\n
let item = Armory.battleItems |> Array.find (fun w -> w.Id = itemId) **Shields:** {Player.shields p |> battleItemFormat}\n
let newBalance = player.Bank - item.Cost **Hack Attacks:**\n{Player.attacks p |> actionFormat}\n
if newBalance >= 0<GBT> then **Active Shields:**\n{Player.defenses p |> actionFormat}"
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 handleSellButtonEvents (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) = let arsenal (ctx : InteractionContext) =
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) =
Game.executePlayerInteraction ctx (fun player -> async { Game.executePlayerInteraction ctx (fun player -> async {
let updatedPlayer = Player.removeExpiredActions player let updatedPlayer = Player.removeExpiredActions player
let builder = DiscordFollowupMessageBuilder() let builder = DiscordFollowupMessageBuilder()
let embed = DiscordEmbedBuilder() let embed = DiscordEmbedBuilder()
embed.AddField("Arsenal", Messaging.statusFormat updatedPlayer) |> ignore embed.AddField("Arsenal", statusFormat updatedPlayer) |> ignore
builder.AddEmbed(embed) |> ignore builder.AddEmbed(embed) |> ignore
builder.IsEphemeral <- true builder.IsEphemeral <- true
do! ctx.FollowUpAsync(builder) do! ctx.FollowUpAsync(builder)
@ -101,23 +59,83 @@ let status (ctx : InteractionContext) =
do! DbService.updatePlayer updatedPlayer 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() = type Store() =
inherit ApplicationCommandModule () 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")>] [<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")>] [<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) = member _.BuyHack (ctx : InteractionContext) = buy ctx ItemType.Hack
buyItem ctx (int hackId)
[<SlashCommand("buy-shield", "Purchase a hack shield so you can protect your GoodBoyTokenz")>] [<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) = member this.BuyShield (ctx : InteractionContext) = buy ctx ItemType.Shield
buyItem ctx (int shieldId)
[<SlashCommand("sell", "Sell an item in your inventory for GoodBoyTokenz")>] [<SlashCommand("sell-hack", "Sell a hack for GoodBoyTokenz")>]
member this.SellItem (ctx : InteractionContext) = sell ctx 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

View File

@ -7,6 +7,10 @@ open DSharpPlus.EventArgs
open DSharpPlus.SlashCommands open DSharpPlus.SlashCommands
open Newtonsoft.Json open Newtonsoft.Json
[<Microsoft.FSharp.Core.AutoOpen>]
module ResultHelpers =
let (>>=) x f = Result.bind f x
[<Microsoft.FSharp.Core.AutoOpen>] [<Microsoft.FSharp.Core.AutoOpen>]
module Types = module Types =
@ -83,32 +87,6 @@ module Armory =
let getItem itemId = battleItems |> Array.find (fun w -> w.Id = itemId) 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 = module Messaging =
type InteractiveMessage = { type InteractiveMessage = {
ButtonId : string ButtonId : string
@ -140,16 +118,10 @@ module Messaging =
| Attack atk -> $"Hacked {atk.Target.Name} at {act.Timestamp.ToShortTimeString()}" | Attack atk -> $"Hacked {atk.Target.Name} at {act.Timestamp.ToShortTimeString()}"
| Defense -> | Defense ->
let item = Armory.getItem act.ActionId 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}") $"{item.Name} active for {cooldown}")
|> String.concat "\n" |> 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) = let constructButtons (actionType: string) (playerInfo: string) (weapons: BattleItem array) =
weapons weapons
|> Array.map (fun w -> DiscordButtonComponent(ButtonStyle.Success, $"{actionType}-{w.Id}-{playerInfo}", $"{w.Name}")) |> 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 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
}