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
|
||||
|
@ -10,19 +10,19 @@
|
||||
<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" />
|
||||
<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" />
|
||||
<ProjectReference Include="..\DbService\DbService.fsproj"/>
|
||||
</ItemGroup>
|
||||
<Import Project="..\.paket\Paket.Restore.targets" />
|
||||
<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)
|
||||
|
51
Bot/Game.fs
51
Bot/Game.fs
@ -1,18 +1,29 @@
|
||||
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>) =
|
||||
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>) =
|
||||
async {
|
||||
let builder = DiscordInteractionResponseBuilder()
|
||||
builder.IsEphemeral <- true
|
||||
@ -26,8 +37,8 @@ let executePlayerInteraction (ctx : InteractionContext) (dispatch : PlayerData -
|
||||
} |> Async.StartAsTask
|
||||
:> Task
|
||||
|
||||
// TODO: Create an abstraction for these two helper functions
|
||||
let executePlayerEvent (event : ComponentInteractionCreateEventArgs) (dispatch : PlayerData -> Async<unit>) =
|
||||
// TODO: Create an abstraction for these two helper functions
|
||||
let executePlayerEvent (event : ComponentInteractionCreateEventArgs) (dispatch : PlayerData -> Async<unit>) =
|
||||
async {
|
||||
let builder = DiscordInteractionResponseBuilder()
|
||||
builder.IsEphemeral <- true
|
||||
@ -41,3 +52,27 @@ let executePlayerEvent (event : ComponentInteractionCreateEventArgs) (dispatch :
|
||||
} |> 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