diff --git a/Bot/Bot.fsproj b/Bot/Bot.fsproj
index 6d23b5c..2def2ea 100644
--- a/Bot/Bot.fsproj
+++ b/Bot/Bot.fsproj
@@ -11,10 +11,12 @@
PreserveNewest
+
-
+
+
@@ -24,8 +26,5 @@
-
-
-
\ No newline at end of file
diff --git a/DbService/DbService.fs b/Bot/DbService.fs
similarity index 100%
rename from DbService/DbService.fs
rename to Bot/DbService.fs
diff --git a/Bot/Game.fs b/Bot/Game.fs
index 9ccd028..0b85933 100644
--- a/Bot/Game.fs
+++ b/Bot/Game.fs
@@ -1,10 +1,268 @@
namespace Degenz
+open System
open System.Threading.Tasks
open DSharpPlus
open DSharpPlus.Entities
-open Degenz.DbService
-open Degenz.Messaging
+open DSharpPlus.EventArgs
+open DSharpPlus.SlashCommands
+open Newtonsoft.Json
+
+[]
+module Types =
+
+ []
+ type mins
+
+ []
+ type GBT
+
+ type HackId =
+ | Virus = 0
+ | RemoteAccess = 1
+ | Worm = 2
+
+ type ShieldId =
+ | Firewall = 6
+ | Encryption = 7
+ | Cypher = 8
+
+ type ItemType =
+ | Hack = 0
+ | Shield = 1
+ | Food = 1
+
+ type ItemAttributes = {
+ Sell : bool
+ Buy : bool
+ Consume : bool
+ Drop : bool
+ }
+ with static member empty = { Sell = false ; Buy = false ; Consume = false ; Drop = false }
+
+ type Item = {
+ Id : int
+ Name : string
+ Price : int
+ Type : ItemType
+ Power : int
+ Class : int
+ Cooldown : int
+ Attributes : ItemAttributes
+ }
+ with static member empty =
+ { Id = -1
+ Name = "None"
+ Price = 0
+ Type = ItemType.Hack
+ Power = 0
+ Class = -1
+ Cooldown = 0
+ Attributes = ItemAttributes.empty }
+
+ type HackResult =
+ | Strong
+ | Weak
+
+ []
+ type DiscordPlayer = { Id: uint64; Name: string }
+ with static member empty = { Id = 0uL ; Name = "None" }
+
+ type HackEvent = {
+ IsInstigator : bool
+ Adversary : DiscordPlayer
+ Success : bool
+ HackId : int
+ }
+
+ type PlayerEventType =
+ | Hacking of HackEvent
+ | Shielding of shieldId : int
+ | Stealing of instigator : bool * adversary : DiscordPlayer
+ | Imprison
+
+ type PlayerEvent =
+ { Type : PlayerEventType
+ Cooldown : int
+ Timestamp : DateTime }
+
+ type PlayerTraits = {
+ Strength : int
+ Focus : int
+ Luck : int
+ Charisma : int
+ }
+ with static member empty = { Strength = 0 ; Focus = 0 ; Luck = 0 ; Charisma = 0 }
+
+ []
+ type PlayerData = {
+ DiscordId : uint64
+ Name : string
+ Inventory : Item array
+ Events : PlayerEvent array
+ Traits : PlayerTraits
+ Bank : int
+ }
+// Achievements : string array
+// XP : int
+ with member this.basicPlayer = { Id = this.DiscordId ; Name = this.Name }
+ static member empty =
+ { DiscordId = 0uL
+ Name = "None"
+ Inventory = [||]
+ Events = [||]
+ Traits = PlayerTraits.empty
+// Achievements = [||]
+// XP = 0
+ Bank = 0 }
+
+module Armory =
+ let battleItems =
+ let file = System.IO.File.ReadAllText("Items.json")
+// let file = System.IO.File.ReadAllText("Bot/Items.json")
+ JsonConvert.DeserializeObject- (file)
+
+ let hacks = battleItems |> Array.filter (fun bi -> match bi.Type with ItemType.Hack -> true | _ -> false)
+ let shields = battleItems |> Array.filter (fun bi -> match bi.Type with ItemType.Shield -> true | _ -> false)
+
+ let getItem itemId = battleItems |> Array.find (fun w -> w.Id = itemId)
+
+module Messaging =
+ type InteractiveMessage = {
+ ButtonId : string
+ ButtonText : string
+ Message : string
+ }
+
+ type DiscordContext =
+ | Interaction of InteractionContext
+ | Event of ComponentInteractionCreateEventArgs
+
+ type IDiscordContext =
+ abstract member Respond : InteractionResponseType -> Task
+ abstract member Respond : InteractionResponseType * DiscordInteractionResponseBuilder -> Task
+ abstract member FollowUp : DiscordFollowupMessageBuilder -> Task
+ abstract member GetDiscordMember : unit -> DiscordMember
+ abstract member GetGuild : unit -> DiscordGuild
+ abstract member GetInteractionId : unit -> string
+ abstract member GetChannel : unit -> DiscordChannel
+ abstract member GetContext : unit -> obj
+
+ type DiscordInteractionContext(ctx : InteractionContext) =
+ interface IDiscordContext with
+ member this.Respond responseType =
+ async {
+ do! ctx.Interaction.CreateResponseAsync(responseType) |> Async.AwaitTask
+ } |> Async.StartAsTask :> Task
+ member this.Respond (responseType, builder) =
+ async {
+ do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask
+ } |> Async.StartAsTask :> Task
+ member this.FollowUp(builder) =
+ async {
+ do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore
+ } |> Async.StartAsTask :> Task
+ member this.GetDiscordMember() = ctx.Member
+ member this.GetGuild() = ctx.Guild
+ member this.GetInteractionId() = string ctx.InteractionId
+ member this.GetChannel() = ctx.Channel
+ member this.GetContext() = ctx
+
+ type DiscordEventContext(ctx : ComponentInteractionCreateEventArgs) =
+ interface IDiscordContext with
+ member this.Respond responseType =
+ async {
+ do! ctx.Interaction.CreateResponseAsync(responseType) |> Async.AwaitTask
+ } |> Async.StartAsTask :> Task
+ member this.Respond (responseType, builder) =
+ async {
+ do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask
+ } |> Async.StartAsTask :> Task
+ member this.FollowUp(builder) =
+ async {
+ do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore
+ } |> Async.StartAsTask :> Task
+ member this.GetDiscordMember() = ctx.User :?> DiscordMember
+ member this.GetGuild() = ctx.Guild
+ member this.GetInteractionId() = ctx.Id
+ member this.GetChannel() = ctx.Channel
+ member this.GetContext() = ctx
+
+ let getTimeText isCooldown (timespan : TimeSpan) timestamp =
+ let span =
+ if isCooldown
+ then timespan - (DateTime.UtcNow - timestamp)
+ else (DateTime.UtcNow - timestamp)
+ let plural amount = if amount = 1 then "" else "s"
+ let ``and`` = if span.Hours > 0 then "and " else ""
+ let hours = if span.Hours > 0 then $"{span.Hours} hour{plural span.Hours} {``and``}" else String.Empty
+ let totalMins = span.Minutes
+ let minutes = if totalMins > 0 then $"{totalMins} minute{plural totalMins}" else "1 minute"
+ $"{hours}{minutes}"
+
+ let getShortTimeText (timespan : TimeSpan) timestamp =
+ let remaining = timespan - (DateTime.UtcNow - timestamp)
+ let hours = if remaining.Hours > 0 then $"{remaining.Hours}h " else String.Empty
+ let minutesRemaining = if remaining.Hours = 0 then remaining.Minutes + 1 else remaining.Minutes
+ $"{hours}{minutesRemaining}min"
+
+ let defer (ctx: IDiscordContext) = async {
+ let builder = DiscordInteractionResponseBuilder()
+ builder.IsEphemeral <- true
+ do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
+ }
+
+ let sendSimpleResponse (ctx: IDiscordContext) msg =
+ async {
+ let builder = DiscordInteractionResponseBuilder()
+ builder.Content <- msg
+ builder.AsEphemeral true |> ignore
+ do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
+ }
+
+ let sendFollowUpMessage (ctx : IDiscordContext) msg =
+ async {
+ let builder = DiscordFollowupMessageBuilder()
+ builder.IsEphemeral <- true
+ builder.Content <- msg
+ do! ctx.FollowUp(builder) |> Async.AwaitTask
+ }
+
+ let sendFollowUpEmbed (ctx : IDiscordContext) embed =
+ async {
+ let builder =
+ DiscordFollowupMessageBuilder()
+ .AsEphemeral(true)
+ .AddEmbed(embed)
+ do! ctx.FollowUp(builder) |> Async.AwaitTask
+ }
+
+ let sendFollowUpMessageWithButton (ctx : IDiscordContext) interactiveMessage =
+ async {
+ let builder = DiscordFollowupMessageBuilder()
+ let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText) :> DiscordComponent
+ builder.AddComponents [| button |] |> ignore
+ builder.IsEphemeral <- true
+ builder.Content <- interactiveMessage.Message
+ do! ctx.FollowUp(builder) |> Async.AwaitTask
+ }
+
+ let updateMessageWithGreyedOutButtons (ctx : IDiscordContext) interactiveMessage =
+ async {
+ let builder = DiscordInteractionResponseBuilder()
+ let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText, true) :> DiscordComponent
+ builder.AddComponents [| button |] |> ignore
+ builder.IsEphemeral <- true
+ builder.Content <- interactiveMessage.Message
+ do! ctx.Respond(InteractionResponseType.UpdateMessage, builder) |> Async.AwaitTask
+ }
+
+ let handleResultWithResponse ctx fn (player : Result) =
+ match player with
+ | Ok p -> fn p
+ | Error e -> async { do! sendFollowUpMessage ctx e }
+
+open Messaging
module Game =
let SameTargetAttackCooldown = System.TimeSpan.FromHours(1)
@@ -24,52 +282,6 @@ module Game =
| 1 -> ( ShieldId.Encryption , HackId.RemoteAccess )
| _ -> ( ShieldId.Cypher , HackId.Worm )
- let executePlayerAction (ctx : IDiscordContext) (dispatch : PlayerData -> Async) =
- async {
- let builder = DiscordInteractionResponseBuilder().AsEphemeral(true)
- do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
- let! playerResult = tryFindPlayer GuildEnvironment.pgDb (ctx.GetDiscordMember().Id)
- match playerResult with
- | Some player -> do! dispatch player
- | None -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
- } |> Async.StartAsTask :> Task
-
- let executePlayerActionWithTarget (targetPlayer : DiscordUser) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async) =
- async {
- let builder = DiscordInteractionResponseBuilder()
- builder.IsEphemeral <- true
- builder.Content <- "Content"
- do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
- let! players =
- [ tryFindPlayer GuildEnvironment.pgDb (ctx.GetDiscordMember().Id)
- tryFindPlayer GuildEnvironment.pgDb targetPlayer.Id ]
- |> Async.Parallel
- match players.[0] , players.[1] with
- | Some player , Some target -> do! dispatch player target
- | None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
- | _ , None ->
- if targetPlayer.IsBot
- then do! Messaging.sendFollowUpMessage ctx $"{targetPlayer.Username} is a bot, pick a real human to hack"
- else do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command"
- } |> Async.StartAsTask :> Task
-
- let executePlayerActionWithTargetId defer (targetId : uint64) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async) =
- async {
- let builder = DiscordInteractionResponseBuilder()
- builder.IsEphemeral <- true
- builder.Content <- "Content"
- if defer then
- do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
- let! players =
- [ tryFindPlayer GuildEnvironment.pgDb (ctx.GetDiscordMember().Id)
- tryFindPlayer GuildEnvironment.pgDb targetId ]
- |> Async.Parallel
- match players.[0] , players.[1] with
- | Some player , Some target -> do! dispatch player target
- | None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
- | _ , None -> do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command"
- } |> Async.StartAsTask :> Task
-
module Player =
let getItems itemType (player : PlayerData) = player.Inventory |> Array.filter (fun i -> i.Type = itemType)
let getHacks (player : PlayerData) = getItems ItemType.Hack player
diff --git a/Bot/HackerBattle.fs b/Bot/HackerBattle.fs
index 27b870f..f58dd28 100644
--- a/Bot/HackerBattle.fs
+++ b/Bot/HackerBattle.fs
@@ -136,7 +136,7 @@ let failedHack (ctx : IDiscordContext) attacker defender hack =
}
let hack (target : DiscordUser) (ctx : IDiscordContext) =
- Game.executePlayerActionWithTarget target ctx (fun attacker defender -> async {
+ PlayerInteractions.executePlayerActionWithTarget target ctx (fun attacker defender -> async {
do! attacker
|> Player.removeExpiredActions
|> checkAlreadyHackedTarget defender
@@ -151,7 +151,7 @@ let hack (target : DiscordUser) (ctx : IDiscordContext) =
})
let handleAttack (ctx : IDiscordContext) =
- Game.executePlayerAction ctx (fun attacker -> async {
+ PlayerInteractions.executePlayerAction ctx (fun attacker -> async {
let tokens = ctx.GetInteractionId().Split("-")
let hackId = int tokens.[1]
let hack = Armory.getItem hackId
@@ -176,7 +176,7 @@ let handleAttack (ctx : IDiscordContext) =
})
let defend (ctx : IDiscordContext) =
- Game.executePlayerAction ctx (fun player -> async {
+ PlayerInteractions.executePlayerAction ctx (fun player -> async {
if Player.getShields player |> Array.length > 0 then
let p = Player.removeExpiredActions player
let embed = Embeds.pickDefense "Defend" p false
@@ -187,7 +187,7 @@ let defend (ctx : IDiscordContext) =
})
let handleDefense (ctx : IDiscordContext) =
- Game.executePlayerAction ctx (fun player -> async {
+ PlayerInteractions.executePlayerAction ctx (fun player -> async {
let tokens = ctx.GetInteractionId().Split("-")
let shieldId = int tokens.[1]
let shield = Armory.getItem shieldId
@@ -218,7 +218,7 @@ let handleDefense (ctx : IDiscordContext) =
})
let arsenal (ctx : IDiscordContext) =
- Game.executePlayerAction ctx (fun player -> async {
+ PlayerInteractions.executePlayerAction ctx (fun player -> async {
let updatedPlayer = Player.removeExpiredActions player
let builder = DiscordFollowupMessageBuilder()
let embed = DiscordEmbedBuilder()
diff --git a/Bot/PlayerInteractions.fs b/Bot/PlayerInteractions.fs
index 817006c..3122c8c 100644
--- a/Bot/PlayerInteractions.fs
+++ b/Bot/PlayerInteractions.fs
@@ -1,8 +1,57 @@
module Degenz.PlayerInteractions
open System.Threading.Tasks
+open DSharpPlus
+open DSharpPlus.Entities
open DSharpPlus.SlashCommands
-open Degenz.Types
+open Degenz.Messaging
+open Degenz.DbService
+
+let executePlayerAction (ctx : IDiscordContext) (dispatch : PlayerData -> Async) =
+ async {
+ let builder = DiscordInteractionResponseBuilder().AsEphemeral(true)
+ do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
+ let! playerResult = tryFindPlayer GuildEnvironment.pgDb (ctx.GetDiscordMember().Id)
+ match playerResult with
+ | Some player -> do! dispatch player
+ | None -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
+ } |> Async.StartAsTask :> Task
+
+let executePlayerActionWithTarget (targetPlayer : DiscordUser) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async) =
+ async {
+ let builder = DiscordInteractionResponseBuilder()
+ builder.IsEphemeral <- true
+ builder.Content <- "Content"
+ do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
+ let! players =
+ [ tryFindPlayer GuildEnvironment.pgDb (ctx.GetDiscordMember().Id)
+ tryFindPlayer GuildEnvironment.pgDb targetPlayer.Id ]
+ |> Async.Parallel
+ match players.[0] , players.[1] with
+ | Some player , Some target -> do! dispatch player target
+ | None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
+ | _ , None ->
+ if targetPlayer.IsBot
+ then do! Messaging.sendFollowUpMessage ctx $"{targetPlayer.Username} is a bot, pick a real human to hack"
+ else do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command"
+ } |> Async.StartAsTask :> Task
+
+let executePlayerActionWithTargetId defer (targetId : uint64) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async) =
+ async {
+ let builder = DiscordInteractionResponseBuilder()
+ builder.IsEphemeral <- true
+ builder.Content <- "Content"
+ if defer then
+ do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
+ let! players =
+ [ tryFindPlayer GuildEnvironment.pgDb (ctx.GetDiscordMember().Id)
+ tryFindPlayer GuildEnvironment.pgDb targetId ]
+ |> Async.Parallel
+ match players.[0] , players.[1] with
+ | Some player , Some target -> do! dispatch player target
+ | None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
+ | _ , None -> do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command"
+ } |> Async.StartAsTask :> Task
module Commands =
let newPlayer nickname (membr : uint64) =
@@ -42,27 +91,6 @@ module Commands =
Name : string
}
-// let leaderboard (ctx : InteractionContext) =
-// async {
-// do! ctx.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource) |> Async.AwaitTask
-//
-// let builder = DiscordFollowupMessageBuilder()
-// builder.IsEphemeral <- true
-//
-// let! leaders = DbService.getTopPlayers 10
-// let content =
-// leaders
-// |> Seq.toArray
-// |> Array.sortByDescending (fun p -> p.Bank)
-// |> Array.mapi (fun i p -> { Position = string (i + 1) ; Amount = string p.Bank ; Name = p.Name })
-// |> Formatter.Format
-// builder.Content <- if not <| String.IsNullOrEmpty content then $"```{content}```" else "There are no active hackers"
-// do! ctx.Interaction.CreateFollowupMessageAsync(builder)
-// |> Async.AwaitTask
-// |> Async.Ignore
-// } |> Async.StartAsTask
-// :> Task
-
type PlayerInteractions() =
inherit ApplicationCommandModule ()
diff --git a/Bot/Prelude.fs b/Bot/Prelude.fs
new file mode 100644
index 0000000..e224ddf
--- /dev/null
+++ b/Bot/Prelude.fs
@@ -0,0 +1,22 @@
+namespace Degenz
+
+[]
+module ResultHelpers =
+ let (>>=) x f = Result.bind f x
+ let () x f = Result.map f x
+
+[]
+[]
+module List =
+ let cons xs x = x :: xs
+ let consTo x xs = x :: xs
+
+ let rec foldk f (acc:'TState) xs =
+ match xs with
+ | [] -> acc
+ | x::xs -> f acc x (fun lacc -> foldk f lacc xs)
+
+ let foldi (f : int -> 'Acc -> 'elem -> 'Acc) (acc : 'Acc) xs =
+ let f' ( i , st ) acc = ( i + 1 , f i st acc )
+ List.fold f' ( 0 , acc ) xs |> snd
+
diff --git a/Bot/RockPaperScissors.fs b/Bot/RockPaperScissors.fs
index 38e9487..7b93835 100644
--- a/Bot/RockPaperScissors.fs
+++ b/Bot/RockPaperScissors.fs
@@ -85,7 +85,7 @@ let matchResultsEmbed winner move1 move2 player1 player2 =
[ firstEmbed ; secondEmbed ; thirdEmbed ]
let playRPS target ctx =
- Game.executePlayerActionWithTarget target ctx (fun _ defender -> async {
+ PlayerInteractions.executePlayerActionWithTarget target ctx (fun _ defender -> async {
let buttons , embed = rpsEmbed false None defender
let builder =
DiscordFollowupMessageBuilder()
@@ -100,7 +100,7 @@ let handleRPS (ctx : IDiscordContext) =
let move = tokens.[1]
let targetId = uint64 tokens.[2]
let isResponse = tokens.[4] = "True"
- Game.executePlayerActionWithTargetId false targetId ctx (fun attacker defender -> async {
+ PlayerInteractions.executePlayerActionWithTargetId false targetId ctx (fun attacker defender -> async {
if isResponse then
let eventCtx = ctx.GetContext() :?> ComponentInteractionCreateEventArgs
let buttons , embed = rpsEmbed true None attacker
diff --git a/Bot/SlotMachine.fs b/Bot/SlotMachine.fs
index 5c1a2be..b401381 100644
--- a/Bot/SlotMachine.fs
+++ b/Bot/SlotMachine.fs
@@ -15,7 +15,7 @@ type SlotMachine() =
[]
member this.Spin (ctx : InteractionContext) =
- Game.executePlayerAction (DiscordInteractionContext ctx) (fun player -> async {
+ PlayerInteractions.executePlayerAction (DiscordInteractionContext ctx) (fun player -> async {
let sleepTime = 1000
let random = Random(System.Guid.NewGuid().GetHashCode())
let results = [ random.Next(0, 3) ; random.Next(0, 3) ; random.Next(0, 3)]
diff --git a/Bot/Store.fs b/Bot/Store.fs
index 6a4a219..3e2efb4 100644
--- a/Bot/Store.fs
+++ b/Bot/Store.fs
@@ -29,13 +29,13 @@ let checkHasItemsInArsenal itemType player =
else Error $"You currently have no {itemType}s in your arsenal to sell!"
let buy itemType (ctx : IDiscordContext) =
- Game.executePlayerAction ctx (fun player -> async {
+ PlayerInteractions.executePlayerAction ctx (fun player -> async {
let itemStore = Embeds.getBuyItemsEmbed player itemType Armory.battleItems
do! ctx.FollowUp itemStore |> Async.AwaitTask
})
let sell itemType (ctx : IDiscordContext) =
- Game.executePlayerAction ctx (fun player -> async {
+ PlayerInteractions.executePlayerAction ctx (fun player -> async {
match checkHasItemsInArsenal itemType player with
| Ok _ -> let itemStore = Embeds.getSellEmbed itemType player
do! ctx.FollowUp(itemStore) |> Async.AwaitTask
@@ -44,7 +44,7 @@ let sell itemType (ctx : IDiscordContext) =
// TODO: When you buy a shield, prompt the user to activate it
let handleBuyItem (ctx : IDiscordContext) itemId =
- Game.executePlayerAction ctx (fun player -> async {
+ PlayerInteractions.executePlayerAction ctx (fun player -> async {
let item = Armory.battleItems |> Array.find (fun w -> w.Id = itemId)
do! player
|> checkHasSufficientFunds item
@@ -58,7 +58,7 @@ let handleBuyItem (ctx : IDiscordContext) itemId =
})
let handleSell (ctx : IDiscordContext) itemId =
- Game.executePlayerAction ctx (fun player -> async {
+ PlayerInteractions.executePlayerAction ctx (fun player -> async {
let item = Armory.getItem itemId
do!
player
diff --git a/Bot/Thief.fs b/Bot/Thief.fs
index 12e5975..5cdb0fa 100644
--- a/Bot/Thief.fs
+++ b/Bot/Thief.fs
@@ -119,7 +119,7 @@ let calculateWinPercentage amountRequested bank attackerStrength defenderStrengt
//calculateWinPercentage 50 200 100 85
let steal target amount (ctx : IDiscordContext) =
- Game.executePlayerActionWithTarget target ctx (fun thief victim -> async { do!
+ PlayerInteractions.executePlayerActionWithTarget target ctx (fun thief victim -> async { do!
thief
|> checkPlayerIsAttackingThemselves victim
// |> checkVictimStealingCooldown victim
@@ -219,7 +219,7 @@ let handleSteal (ctx : IDiscordContext) =
}
if answer = "yes" then
let targetId = uint64 tokens.[2]
- Game.executePlayerActionWithTargetId true targetId ctx (fun attacker defender -> async {
+ PlayerInteractions.executePlayerActionWithTargetId true targetId ctx (fun attacker defender -> async {
do! attacker
|> Player.removeExpiredActions
// |> checkVictimStealingCooldown defender
diff --git a/Bot/Trainer.fs b/Bot/Trainer.fs
index a2dcc91..781bf57 100644
--- a/Bot/Trainer.fs
+++ b/Bot/Trainer.fs
@@ -131,7 +131,7 @@ let hack (target : DiscordUser) (ctx : IDiscordContext) =
} |> Async.StartAsTask :> Task
let handleHack (ctx : IDiscordContext) =
- Game.executePlayerAction ctx (fun player -> async {
+ PlayerInteractions.executePlayerAction ctx (fun player -> async {
let sendMessage' = sendFollowUpMessage ctx
do! Async.Sleep 1000
let embed = Embeds.responseSuccessfulHack false Sensei.Id defaultHack.Power defaultHack
@@ -166,7 +166,7 @@ let handleHack (ctx : IDiscordContext) =
})
let handleArsenal (ctx : IDiscordContext) =
- Game.executePlayerAction ctx (fun player -> async {
+ PlayerInteractions.executePlayerAction ctx (fun player -> async {
let hasStockWeapons = Player.getHacks player |> Array.exists (fun item -> item.Id = defaultHack.Id)
let updatedPlayer =
if not hasStockWeapons then {
diff --git a/DbService/DbService.fsproj b/DbService/DbService.fsproj
index 45a74ae..e7786d7 100644
--- a/DbService/DbService.fsproj
+++ b/DbService/DbService.fsproj
@@ -4,9 +4,6 @@
net6.0
true
-
-
-
diff --git a/DegenzGame.sln b/DegenzGame.sln
index 84b9a1a..c77e294 100644
--- a/DegenzGame.sln
+++ b/DegenzGame.sln
@@ -5,10 +5,6 @@ VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Bot", "Bot\Bot.fsproj", "{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}"
EndProject
-Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "DbService", "DbService\DbService.fsproj", "{B1D3E1CC-451C-42D4-B054-D64E75E1A3B9}"
-EndProject
-Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Shared", "Shared\Shared.fsproj", "{BD80E85E-87C8-4F5F-941E-7DCAF9D69838}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -22,13 +18,5 @@ Global
{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}.Release|Any CPU.Build.0 = Release|Any CPU
- {B1D3E1CC-451C-42D4-B054-D64E75E1A3B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B1D3E1CC-451C-42D4-B054-D64E75E1A3B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B1D3E1CC-451C-42D4-B054-D64E75E1A3B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B1D3E1CC-451C-42D4-B054-D64E75E1A3B9}.Release|Any CPU.Build.0 = Release|Any CPU
- {BD80E85E-87C8-4F5F-941E-7DCAF9D69838}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BD80E85E-87C8-4F5F-941E-7DCAF9D69838}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BD80E85E-87C8-4F5F-941E-7DCAF9D69838}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BD80E85E-87C8-4F5F-941E-7DCAF9D69838}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/Shared/Shared.fs b/Shared/Shared.fs
index 21611f1..8148165 100644
--- a/Shared/Shared.fs
+++ b/Shared/Shared.fs
@@ -7,277 +7,3 @@ open DSharpPlus.Entities
open DSharpPlus.EventArgs
open DSharpPlus.SlashCommands
open Newtonsoft.Json
-
-[]
-module ResultHelpers =
- let (>>=) x f = Result.bind f x
- let () x f = Result.map f x
-
-[]
-module List =
- let cons xs x = x :: xs
- let consTo x xs = x :: xs
-
- let rec foldk f (acc:'TState) xs =
- match xs with
- | [] -> acc
- | x::xs -> f acc x (fun lacc -> foldk f lacc xs)
-
- let foldi (f : int -> 'Acc -> 'elem -> 'Acc) (acc : 'Acc) xs =
- let f' ( i , st ) acc = ( i + 1 , f i st acc )
- List.fold f' ( 0 , acc ) xs |> snd
-
-[]
-module Types =
-
- []
- type mins
-
- []
- type GBT
-
- type HackId =
- | Virus = 0
- | RemoteAccess = 1
- | Worm = 2
-
- type ShieldId =
- | Firewall = 6
- | Encryption = 7
- | Cypher = 8
-
- type ItemType =
- | Hack = 0
- | Shield = 1
- | Food = 1
-
- type ItemAttributes = {
- Sell : bool
- Buy : bool
- Consume : bool
- Drop : bool
- }
- with static member empty = { Sell = false ; Buy = false ; Consume = false ; Drop = false }
-
- type Item = {
- Id : int
- Name : string
- Price : int
- Type : ItemType
- Power : int
- Class : int
- Cooldown : int
- Attributes : ItemAttributes
- }
- with static member empty =
- { Id = -1
- Name = "None"
- Price = 0
- Type = ItemType.Hack
- Power = 0
- Class = -1
- Cooldown = 0
- Attributes = ItemAttributes.empty }
-
- type HackResult =
- | Strong
- | Weak
-
- []
- type DiscordPlayer = { Id: uint64; Name: string }
- with static member empty = { Id = 0uL ; Name = "None" }
-
- type HackEvent = {
- IsInstigator : bool
- Adversary : DiscordPlayer
- Success : bool
- HackId : int
- }
-
- type PlayerEventType =
- | Hacking of HackEvent
- | Shielding of shieldId : int
- | Stealing of instigator : bool * adversary : DiscordPlayer
- | Imprison
-
- type PlayerEvent =
- { Type : PlayerEventType
- Cooldown : int
- Timestamp : DateTime }
-
- type PlayerTraits = {
- Strength : int
- Focus : int
- Luck : int
- Charisma : int
- }
- with static member empty = { Strength = 0 ; Focus = 0 ; Luck = 0 ; Charisma = 0 }
-
- []
- type PlayerData = {
- DiscordId : uint64
- Name : string
- Inventory : Item array
- Events : PlayerEvent array
- Traits : PlayerTraits
- Bank : int
- }
-// Achievements : string array
-// XP : int
- with member this.basicPlayer = { Id = this.DiscordId ; Name = this.Name }
- static member empty =
- { DiscordId = 0uL
- Name = "None"
- Inventory = [||]
- Events = [||]
- Traits = PlayerTraits.empty
-// Achievements = [||]
-// XP = 0
- Bank = 0 }
-
-module Armory =
- let battleItems =
- let file = System.IO.File.ReadAllText("Items.json")
-// let file = System.IO.File.ReadAllText("Bot/Items.json")
- JsonConvert.DeserializeObject
- (file)
-
- let hacks = battleItems |> Array.filter (fun bi -> match bi.Type with ItemType.Hack -> true | _ -> false)
- let shields = battleItems |> Array.filter (fun bi -> match bi.Type with ItemType.Shield -> true | _ -> false)
-
- let getItem itemId = battleItems |> Array.find (fun w -> w.Id = itemId)
-
-module Messaging =
- type InteractiveMessage = {
- ButtonId : string
- ButtonText : string
- Message : string
- }
-
- type DiscordContext =
- | Interaction of InteractionContext
- | Event of ComponentInteractionCreateEventArgs
-
- type IDiscordContext =
- abstract member Respond : InteractionResponseType -> Task
- abstract member Respond : InteractionResponseType * DiscordInteractionResponseBuilder -> Task
- abstract member FollowUp : DiscordFollowupMessageBuilder -> Task
- abstract member GetDiscordMember : unit -> DiscordMember
- abstract member GetGuild : unit -> DiscordGuild
- abstract member GetInteractionId : unit -> string
- abstract member GetChannel : unit -> DiscordChannel
- abstract member GetContext : unit -> obj
-
- type DiscordInteractionContext(ctx : InteractionContext) =
- interface IDiscordContext with
- member this.Respond responseType =
- async {
- do! ctx.Interaction.CreateResponseAsync(responseType) |> Async.AwaitTask
- } |> Async.StartAsTask :> Task
- member this.Respond (responseType, builder) =
- async {
- do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask
- } |> Async.StartAsTask :> Task
- member this.FollowUp(builder) =
- async {
- do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore
- } |> Async.StartAsTask :> Task
- member this.GetDiscordMember() = ctx.Member
- member this.GetGuild() = ctx.Guild
- member this.GetInteractionId() = string ctx.InteractionId
- member this.GetChannel() = ctx.Channel
- member this.GetContext() = ctx
-
- type DiscordEventContext(ctx : ComponentInteractionCreateEventArgs) =
- interface IDiscordContext with
- member this.Respond responseType =
- async {
- do! ctx.Interaction.CreateResponseAsync(responseType) |> Async.AwaitTask
- } |> Async.StartAsTask :> Task
- member this.Respond (responseType, builder) =
- async {
- do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask
- } |> Async.StartAsTask :> Task
- member this.FollowUp(builder) =
- async {
- do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore
- } |> Async.StartAsTask :> Task
- member this.GetDiscordMember() = ctx.User :?> DiscordMember
- member this.GetGuild() = ctx.Guild
- member this.GetInteractionId() = ctx.Id
- member this.GetChannel() = ctx.Channel
- member this.GetContext() = ctx
-
- let getTimeText isCooldown (timespan : TimeSpan) timestamp =
- let span =
- if isCooldown
- then timespan - (DateTime.UtcNow - timestamp)
- else (DateTime.UtcNow - timestamp)
- let plural amount = if amount = 1 then "" else "s"
- let ``and`` = if span.Hours > 0 then "and " else ""
- let hours = if span.Hours > 0 then $"{span.Hours} hour{plural span.Hours} {``and``}" else String.Empty
- let totalMins = span.Minutes
- let minutes = if totalMins > 0 then $"{totalMins} minute{plural totalMins}" else "1 minute"
- $"{hours}{minutes}"
-
- let getShortTimeText (timespan : TimeSpan) timestamp =
- let remaining = timespan - (DateTime.UtcNow - timestamp)
- let hours = if remaining.Hours > 0 then $"{remaining.Hours}h " else String.Empty
- let minutesRemaining = if remaining.Hours = 0 then remaining.Minutes + 1 else remaining.Minutes
- $"{hours}{minutesRemaining}min"
-
- let defer (ctx: IDiscordContext) = async {
- let builder = DiscordInteractionResponseBuilder()
- builder.IsEphemeral <- true
- do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask
- }
-
- let sendSimpleResponse (ctx: IDiscordContext) msg =
- async {
- let builder = DiscordInteractionResponseBuilder()
- builder.Content <- msg
- builder.AsEphemeral true |> ignore
- do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
- }
-
- let sendFollowUpMessage (ctx : IDiscordContext) msg =
- async {
- let builder = DiscordFollowupMessageBuilder()
- builder.IsEphemeral <- true
- builder.Content <- msg
- do! ctx.FollowUp(builder) |> Async.AwaitTask
- }
-
- let sendFollowUpEmbed (ctx : IDiscordContext) embed =
- async {
- let builder =
- DiscordFollowupMessageBuilder()
- .AsEphemeral(true)
- .AddEmbed(embed)
- do! ctx.FollowUp(builder) |> Async.AwaitTask
- }
-
- let sendFollowUpMessageWithButton (ctx : IDiscordContext) interactiveMessage =
- async {
- let builder = DiscordFollowupMessageBuilder()
- let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText) :> DiscordComponent
- builder.AddComponents [| button |] |> ignore
- builder.IsEphemeral <- true
- builder.Content <- interactiveMessage.Message
- do! ctx.FollowUp(builder) |> Async.AwaitTask
- }
-
- let updateMessageWithGreyedOutButtons (ctx : IDiscordContext) interactiveMessage =
- async {
- let builder = DiscordInteractionResponseBuilder()
- let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText, true) :> DiscordComponent
- builder.AddComponents [| button |] |> ignore
- builder.IsEphemeral <- true
- builder.Content <- interactiveMessage.Message
- do! ctx.Respond(InteractionResponseType.UpdateMessage, builder) |> Async.AwaitTask
- }
-
- let handleResultWithResponse ctx fn (player : Result) =
- match player with
- | Ok p -> fn p
- | Error e -> async { do! sendFollowUpMessage ctx e }
-