Refactor for new inventory, events, and traits

This commit is contained in:
Joseph Ferano 2022-02-16 17:40:17 +07:00
parent c1c51dcd99
commit 7900bba3c3
9 changed files with 154 additions and 193 deletions

View File

@ -1,6 +1,7 @@
module Degenz.Embeds module Degenz.Embeds
open System open System
open DSharpPlus
open Degenz.Messaging open Degenz.Messaging
open Degenz.Types open Degenz.Types
open DSharpPlus.Entities open DSharpPlus.Entities
@ -37,15 +38,15 @@ let constructButtons (actionId: string) (buttonInfo : string) (player: PlayerDat
|> Player.getItems itemType |> Player.getItems itemType
|> Array.map (fun item -> |> Array.map (fun item ->
let action = let action =
player.Actions player.Events
|> Array.tryFind (fun i -> i.ActionId = item.Id) |> Array.tryFind (fun i -> i.ItemId = item.Id)
match action , isTrainer with match action , isTrainer with
| None , _ | Some _ , true -> | None , _ | Some _ , true ->
DiscordButtonComponent(Game.getClassButtonColor item.Class, $"{actionId}-{item.Id}-{buttonInfo}", $"{item.Name}") DiscordButtonComponent(ButtonStyle.Primary, $"{actionId}-{item.Id}-{buttonInfo}", $"{item.Name}")
| Some act , false -> | Some act , false ->
let c = ((Armory.getItem act.ActionId).Cooldown) let c = ((Armory.getItem act.ItemId).Cooldown)
let time = Messaging.getShortTimeText (TimeSpan.FromMinutes(int c)) act.Timestamp let time = Messaging.getShortTimeText (TimeSpan.FromMinutes(int c)) act.Timestamp
DiscordButtonComponent(Game.getClassButtonColor item.Class, $"{actionId}-{item.Id}", $"{item.Name} ({time} left)", true)) DiscordButtonComponent(ButtonStyle.Primary, $"{actionId}-{item.Id}", $"{item.Name} ({time} left)", true))
|> Seq.cast<DiscordComponent> |> Seq.cast<DiscordComponent>
let pickDefense actionId player isTrainer = let pickDefense actionId player isTrainer =
@ -109,25 +110,24 @@ let getBuyItemsEmbed (player : PlayerData) (itemType : ItemType) (store : Battle
match item.Type with match item.Type with
| Hack -> | Hack ->
embed embed
.AddField($"Weak Against |", Game.getGoodAgainst item.Class |> fst |> string , true) .AddField($"Hacking Power |", string item.Power, true)
.AddField("Cooldown |", $"{TimeSpan.FromMinutes(int item.Cooldown).Minutes} minutes", true) .AddField("Cooldown |", $"{TimeSpan.FromMinutes(int item.Cooldown).Minutes} minutes", true)
.WithThumbnail(getHackIcon (enum<HackId>(item.Id))) .WithThumbnail(getHackIcon (enum<HackId>(item.Id)))
|> ignore |> ignore
| Shield -> | Shield ->
embed embed
.AddField($"Strong Against |", Game.getGoodAgainst item.Class |> snd |> string , true) .AddField($"Defensive Strength |", string item.Power, true)
.AddField("Active For |", $"{TimeSpan.FromMinutes(int item.Cooldown).Hours} hours", true) .AddField("Active For |", $"{TimeSpan.FromMinutes(int item.Cooldown).Hours} hours", true)
.WithThumbnail(getShieldIcon (enum<ShieldId>(item.Id))) .WithThumbnail(getShieldIcon (enum<ShieldId>(item.Id)))
|> ignore |> ignore
embed embed
.AddField("Cost 💰", $"{item.Cost} $GBT", true) .AddField("Cost 💰", $"{item.Price} $GBT", true)
.WithColor(Game.getClassEmbedColor item.Class)
.WithTitle($"{item.Name}") .WithTitle($"{item.Name}")
|> ignore |> ignore
let button = let button =
if player.Arsenal |> Array.exists (fun i -> i.Id = item.Id) if player.Inventory |> Array.exists (fun i -> i.Id = item.Id)
then DiscordButtonComponent(Game.getClassButtonColor item.Class, $"Buy-{item.Id}", $"Own {item.Name}", true) then DiscordButtonComponent(ButtonStyle.Primary, $"Buy-{item.Id}", $"Own {item.Name}", true)
else DiscordButtonComponent(Game.getClassButtonColor item.Class, $"Buy-{item.Id}", $"Buy {item.Name}") else DiscordButtonComponent(ButtonStyle.Primary, $"Buy-{item.Id}", $"Buy {item.Name}")
embed.Build() , button :> DiscordComponent) embed.Build() , button :> DiscordComponent)
|> Array.unzip |> Array.unzip
@ -138,7 +138,7 @@ let getBuyItemsEmbed (player : PlayerData) (itemType : ItemType) (store : Battle
let getSellItemsEmbed (itemType : ItemType) (player : PlayerData) = let getSellItemsEmbed (itemType : ItemType) (player : PlayerData) =
let embeds , buttons = let embeds , buttons =
player.Arsenal player.Inventory
|> Array.filter (fun i -> i.Type = itemType) |> Array.filter (fun i -> i.Type = itemType)
|> Array.map (fun item -> |> Array.map (fun item ->
let embed = DiscordEmbedBuilder() let embed = DiscordEmbedBuilder()
@ -146,11 +146,10 @@ let getSellItemsEmbed (itemType : ItemType) (player : PlayerData) =
| Hack -> embed.WithThumbnail(getHackIcon (enum<HackId>(item.Id))) |> ignore | Hack -> embed.WithThumbnail(getHackIcon (enum<HackId>(item.Id))) |> ignore
| Shield -> embed.WithThumbnail(getShieldIcon (enum<ShieldId>(item.Id))) |> ignore | Shield -> embed.WithThumbnail(getShieldIcon (enum<ShieldId>(item.Id))) |> ignore
embed embed
.AddField("Sell For 💰", $"{item.Cost} $GBT", true) .AddField("Sell For 💰", $"{item.Price} $GBT", true)
.WithColor(Game.getClassEmbedColor item.Class)
.WithTitle($"{item.Name}") .WithTitle($"{item.Name}")
|> ignore |> ignore
let button = DiscordButtonComponent(Game.getClassButtonColor item.Class, $"Sell-{item.Id}", $"Sell {item.Name}") let button = DiscordButtonComponent(ButtonStyle.Primary, $"Sell-{item.Id}", $"Sell {item.Name}")
embed.Build() , button :> DiscordComponent) embed.Build() , button :> DiscordComponent)
|> Array.unzip |> Array.unzip

View File

@ -14,21 +14,6 @@ module Game =
let SameTargetAttackCooldown = System.TimeSpan.FromHours(6) 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 getGoodAgainst = function
| BattleClass.Network -> ( ShieldId.Firewall , HackId.Virus )
| BattleClass.Penetration -> ( ShieldId.Cypher , HackId.RemoteAccess )
| BattleClass.Exploit -> ( ShieldId.Encryption , HackId.Worm )
let executePlayerAction (ctx : IDiscordContext) (dispatch : PlayerData -> Async<unit>) = let executePlayerAction (ctx : IDiscordContext) (dispatch : PlayerData -> Async<unit>) =
async { async {
let builder = DiscordInteractionResponseBuilder() let builder = DiscordInteractionResponseBuilder()
@ -78,65 +63,64 @@ module Game =
} |> Async.StartAsTask :> Task } |> Async.StartAsTask :> Task
module Player = module Player =
let getItems itemType (player : PlayerData) = player.Arsenal |> Array.filter (fun i -> i.Type = itemType) let getItems itemType (player : PlayerData) = player.Inventory |> Array.filter (fun i -> i.Type = itemType)
let getHacks (player : PlayerData) = getItems ItemType.Hack player let getHacks (player : PlayerData) = getItems ItemType.Hack player
let getShields (player : PlayerData) = getItems ItemType.Shield player let getShields (player : PlayerData) = getItems ItemType.Shield player
let getAttacks player = let getHackEvents player =
player.Actions player.Events
|> Array.filter (fun act -> match act.Type with Attack _ -> true | _ -> false || act.ActionId < 12) |> Array.filter (fun act -> match act.Type with PlayerEventType.Hack -> true | _ -> false || act.ItemId < 12)
let getDefenses player = let getShieldEvents player =
player.Actions player.Events
|> Array.filter (fun act -> match act.Type with Defense -> true | _ -> false || act.ActionId < 12) |> Array.filter (fun act -> match act.Type with PlayerEventType.Shield -> true | _ -> false || act.ItemId < 12)
// TODO: This parameter is a result of putting the cooldown on the attack side. Put the cooldown on the defender // TODO: This parameter is a result of putting the cooldown on the attack side. Put the cooldown on the defender
// side and only check if it's the same target, we need to refactor Actions // side and only check if it's the same target, we need to refactor Actions
let removeExpiredActions filterByAttackCooldown player = let removeExpiredActions filterByAttackCooldown player =
let actions = let actions =
player.Actions player.Events
|> Array.filter (fun (act : Action) -> |> Array.filter (fun (act : PlayerEvent) ->
let itemCooldown = let itemCooldown =
if act.ActionId < 12 then if act.ItemId < 12 then
(Armory.getItem act.ActionId).Cooldown (Armory.getItem act.ItemId).Cooldown
else else
match act.Type with match act.Type with
| Attack _ -> 1<mins> | PlayerEventType.Steal -> 1<mins>
| Defense -> 720<mins> | _ -> 720<mins>
|> int |> int
match act.Type , filterByAttackCooldown with match act.Type , filterByAttackCooldown with
| Attack _ , true -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(itemCooldown) | PlayerEventType.Hack , true -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(itemCooldown)
| Attack _ , false -> System.DateTime.UtcNow - act.Timestamp < Game.SameTargetAttackCooldown | PlayerEventType.Hack , false -> System.DateTime.UtcNow - act.Timestamp < Game.SameTargetAttackCooldown
| Defense , _ -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(itemCooldown)) | PlayerEventType.Shield , _ -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(itemCooldown)
{ player with Actions = actions } | _ -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(itemCooldown))
{ player with Events = actions }
let modifyBank (player : PlayerData) amount = { player with Bank = max (player.Bank + amount) 0<GBT> } 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)
module Arsenal = module Arsenal =
let battleItemFormat (items : BattleItem array) = let battleItemFormat (items : BattleItem array) =
match items with match items with
| [||] -> "None" | [||] -> "None"
| _ -> items |> Array.toList |> List.map (fun i -> i.Name) |> String.concat ", " | _ -> items |> Array.toList |> List.map (fun i -> i.Name) |> String.concat ", "
let actionFormat (actions : Action array) = let actionFormat (actions : PlayerEvent array) =
match actions with match actions with
| [||] -> "None" | [||] -> "None"
| _ -> | _ ->
let hacks , defenses = let hacks , defenses =
actions actions
|> Array.filter (fun act -> act.ActionId < 12) |> Array.filter (fun act -> act.ItemId < 12)
|> Array.partition (fun act -> match act.Type with Attack _ -> true | Defense -> false) |> Array.partition (fun act -> match act.Type with PlayerEventType.Hack -> true | _ -> false)
let hacks = hacks |> Array.take (min hacks.Length 10) let hacks = hacks |> Array.take (min hacks.Length 10)
hacks hacks
|> Array.append defenses |> Array.append defenses
|> Array.map (fun act -> |> Array.map (fun act ->
let item = Armory.getItem act.ActionId let item = Armory.getItem act.ItemId
match act.Type with match act.Type with
| Attack atk -> | PlayerEventType.Hack ->
let cooldown = Messaging.getTimeText false Game.SameTargetAttackCooldown act.Timestamp let cooldown = Messaging.getTimeText false Game.SameTargetAttackCooldown act.Timestamp
$"Hacked {atk.Target.Name} {cooldown} ago" $"Hacked {act.Adversary.Name} {cooldown} ago"
| Defense -> | _ ->
let cooldown = Messaging.getTimeText true (System.TimeSpan.FromMinutes(int item.Cooldown)) act.Timestamp let cooldown = Messaging.getTimeText true (System.TimeSpan.FromMinutes(int item.Cooldown)) act.Timestamp
$"{item.Name} Shield active for {cooldown}") $"{item.Name} Shield active for {cooldown}")
|> String.concat "\n" |> String.concat "\n"
@ -144,6 +128,6 @@ module Arsenal =
let statusFormat p = let statusFormat p =
$"**Hacks:** {Player.getHacks p |> battleItemFormat}\n $"**Hacks:** {Player.getHacks p |> battleItemFormat}\n
**Shields:** {Player.getShields p |> battleItemFormat}\n **Shields:** {Player.getShields p |> battleItemFormat}\n
**Hack Attacks:**\n{Player.getAttacks p |> actionFormat}\n **Hack Attacks:**\n{Player.getHackEvents p |> actionFormat}\n
**Active Shields:**\n{Player.getDefenses p |> actionFormat}" **Active Shields:**\n{Player.getShieldEvents p |> actionFormat}"

View File

@ -15,19 +15,18 @@ let checkPlayerIsAttackingThemselves defender attacker =
| false -> Ok attacker | false -> Ok attacker
let checkAlreadyHackedTarget defenderId attacker = let checkAlreadyHackedTarget defenderId attacker =
attacker.Actions attacker.Events
|> Player.getAttacksFlat |> Array.tryFind (fun pe -> pe.Adversary.Id = defenderId)
|> Array.tryFind (fun (_,t,_) -> t.Id = defenderId)
|> function |> function
| Some ( atk , target , _ ) -> | Some event ->
let cooldown = getTimeText true Game.SameTargetAttackCooldown atk.Timestamp let cooldown = getTimeText true Game.SameTargetAttackCooldown event.Timestamp
Error $"You can only hack the same target once every {Game.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 {event.Adversary.Name}."
| None -> Ok attacker | None -> Ok attacker
let checkItemHasCooldown itemId attacker = let checkItemHasCooldown itemId attacker =
let cooldown = let cooldown =
attacker.Actions attacker.Events
|> Array.tryFind (fun a -> a.ActionId = itemId) |> Array.tryFind (fun a -> a.ItemId = itemId)
|> function |> function
| Some a -> a.Timestamp | Some a -> a.Timestamp
| None -> DateTime.MinValue | None -> DateTime.MinValue
@ -45,7 +44,7 @@ let checkHasEmptyHacks attacker =
| _ -> Ok attacker | _ -> Ok attacker
let checkPlayerOwnsWeapon itemId player = let checkPlayerOwnsWeapon itemId player =
match player.Arsenal |> Array.exists (fun i -> i.Id = itemId) with match player.Inventory |> Array.exists (fun i -> i.Id = itemId) with
| true -> Ok player | true -> Ok player
| false -> Error $"You sold your weapon already, you cheeky bastard..." | false -> Error $"You sold your weapon already, you cheeky bastard..."
@ -56,7 +55,7 @@ let checkTargetHasMoney (target : PlayerData) attacker =
let checkPlayerHasShieldSlotsAvailable shield player = let checkPlayerHasShieldSlotsAvailable shield player =
let updatedPlayer = player |> Player.removeExpiredActions false let updatedPlayer = player |> Player.removeExpiredActions false
let defenses = Player.getDefenses updatedPlayer let defenses = Player.getShieldEvents updatedPlayer
match defenses |> Array.length >= 2 with match defenses |> Array.length >= 2 with
| true -> | true ->
let timestamp = defenses |> Array.rev |> Array.head |> fun a -> a.Timestamp // This should be the next expiring timestamp let timestamp = defenses |> Array.rev |> Array.head |> fun a -> a.Timestamp // This should be the next expiring timestamp
@ -65,24 +64,33 @@ let checkPlayerHasShieldSlotsAvailable shield player =
| false -> Ok updatedPlayer | false -> Ok updatedPlayer
let calculateDamage (hack : BattleItem) (shield : BattleItem) = let calculateDamage (hack : BattleItem) (shield : BattleItem) =
if hack.Class = shield.Class if hack.Power < shield.Power
then Weak then Weak
else Strong else Strong
let runHackerBattle defender hack = let runHackerBattle defender hack =
defender defender
|> Player.removeExpiredActions false |> Player.removeExpiredActions false
|> Player.getDefenses |> Player.getShieldEvents
|> Array.map (fun dfn -> Armory.battleItems |> Array.find (fun w -> w.Id = dfn.ActionId)) |> Array.map (fun dfn -> Armory.battleItems |> Array.find (fun w -> w.Id = dfn.ItemId))
|> Array.map (calculateDamage (hack)) |> Array.map (calculateDamage (hack))
|> Array.contains Weak |> Array.contains Weak
let updateCombatants attacker defender hack prize = let updateCombatants attacker defender hack prize =
let updatePlayer amount attack p = let updatePlayer amount attack p =
{ p with Actions = Array.append [| attack |] p.Actions ; Bank = max (p.Bank + amount) 0<GBT> } { p with Events = Array.append [| attack |] p.Events ; Bank = max (p.Bank + amount) 0<GBT> }
let target = { Id = defender.DiscordId ; Name = defender.Name } let target = { Id = defender.DiscordId ; Name = defender.Name }
let attack = { ActionId = int hack ; Type = Attack { Target = target ; Result = prize > 0<GBT> } ; Timestamp = DateTime.UtcNow } let attack = {
ItemId = int hack
Type = PlayerEventType.Hack
Adversary = target
Result = if prize > 0<GBT> then PlayerEventResult.Successful else PlayerEventResult.Failed
Timestamp = DateTime.UtcNow
}
// TODO: This is what I was talking about, this isn't a "Shield" event, this is a hack event but there's an adversary
// who loses, so the event itself is to just "hack", so there's no "mugged" event, there's just a failed steal defense
// or something like that.
[ DbService.updatePlayer <| updatePlayer prize attack attacker [ DbService.updatePlayer <| updatePlayer prize attack attacker
DbService.updatePlayer <| Player.modifyBank defender -prize ] DbService.updatePlayer <| Player.modifyBank defender -prize ]
|> Async.Parallel |> Async.Parallel
@ -181,8 +189,14 @@ let handleDefense (ctx : IDiscordContext) =
|> handleResultWithResponse ctx (fun _ -> async { // Don't use this player, it removes player cooldowns |> handleResultWithResponse ctx (fun _ -> async { // Don't use this player, it removes player cooldowns
let embed = Embeds.responseCreatedShield (Armory.getItem shieldId) let embed = Embeds.responseCreatedShield (Armory.getItem shieldId)
do! ctx.FollowUp embed |> Async.AwaitTask do! ctx.FollowUp embed |> Async.AwaitTask
let defense = { ActionId = shieldId ; Type = Defense ; Timestamp = DateTime.UtcNow } let defense = {
do! DbService.updatePlayer <| { player with Actions = Array.append [| defense |] player.Actions } ItemId = shieldId
Type = PlayerEventType.Shield
Result = PlayerEventResult.Successful
Timestamp = DateTime.UtcNow
Adversary = DiscordPlayer.empty
}
do! DbService.updatePlayer <| { player with Events = Array.append [| defense |] player.Events }
let builder = DiscordMessageBuilder() let builder = DiscordMessageBuilder()
builder.WithContent($"{ctx.GetDiscordMember().Username} has protected their system!") |> ignore builder.WithContent($"{ctx.GetDiscordMember().Username} has protected their system!") |> ignore
let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelEventsHackerBattle) let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelEventsHackerBattle)

View File

@ -14,8 +14,8 @@ module Commands =
{ DiscordId = membr { DiscordId = membr
Name = nickname Name = nickname
Arsenal = [| hack ; shield |] Inventory = [| hack ; shield |]
Actions = [||] Events = [||]
XP = 0 XP = 0
Achievements = [||] Achievements = [||]
Traits = PlayerTraits.empty Traits = PlayerTraits.empty

View File

@ -9,22 +9,22 @@ open Degenz
open Degenz.Messaging open Degenz.Messaging
let checkHasSufficientFunds (item : BattleItem) player = let checkHasSufficientFunds (item : BattleItem) player =
if player.Bank - item.Cost >= 0<GBT> if player.Bank - item.Price >= 0<GBT>
then Ok player then Ok player
else Error $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT" else Error $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT"
let checkAlreadyOwnsItem (item : BattleItem) player = let checkAlreadyOwnsItem (item : BattleItem) player =
if player.Arsenal |> Array.exists (fun w -> item.Id = w.Id) if player.Inventory |> Array.exists (fun w -> item.Id = w.Id)
then Error $"You already own {item.Name}!" then Error $"You already own {item.Name}!"
else Ok player else Ok player
let checkSoldItemAlready (item : BattleItem) player = let checkSoldItemAlready (item : BattleItem) player =
if player.Arsenal |> Array.exists (fun w -> item.Id = w.Id) if player.Inventory |> Array.exists (fun w -> item.Id = w.Id)
then Ok player then Ok player
else Error $"{item.Name} not found in your arsenal! Looks like you sold it already." else Error $"{item.Name} not found in your arsenal! Looks like you sold it already."
let checkHasItemsInArsenal itemType player = let checkHasItemsInArsenal itemType player =
if player.Arsenal |> Array.filter (fun i -> i.Type = itemType ) |> Array.length > 0 if player.Inventory |> Array.filter (fun i -> i.Type = itemType ) |> Array.length > 0
then Ok player then Ok player
else Error $"You currently have no {itemType}s in your arsenal to sell!" else Error $"You currently have no {itemType}s in your arsenal to sell!"
@ -50,8 +50,8 @@ let handleBuyItem (ctx : IDiscordContext) itemId =
|> checkHasSufficientFunds item |> checkHasSufficientFunds item
>>= checkAlreadyOwnsItem item >>= checkAlreadyOwnsItem item
|> handleResultWithResponse ctx (fun player -> async { |> handleResultWithResponse ctx (fun player -> async {
let newBalance = player.Bank - item.Cost let newBalance = player.Bank - item.Price
let p = { player with Bank = newBalance ; Arsenal = Array.append [| item |] player.Arsenal } let p = { player with Bank = newBalance ; Inventory = Array.append [| item |] player.Inventory }
do! DbService.updatePlayer p do! DbService.updatePlayer p
do! sendFollowUpMessage ctx $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining" do! sendFollowUpMessage ctx $"Successfully purchased {item.Name}! You now have {newBalance} 💰$GBT remaining"
}) })
@ -65,15 +65,15 @@ let handleSell (ctx : IDiscordContext) itemId =
|> handleResultWithResponse ctx (fun player -> async { |> handleResultWithResponse ctx (fun player -> async {
let updatedPlayer = { let updatedPlayer = {
player with player with
Bank = player.Bank + item.Cost Bank = player.Bank + item.Price
Arsenal = player.Arsenal |> Array.filter (fun i -> i.Id <> itemId) Inventory = player.Inventory |> Array.filter (fun i -> i.Id <> itemId)
Actions = Events =
if item.Type = ItemType.Shield if item.Type = ItemType.Shield
then player.Actions |> Array.filter (fun a -> a.ActionId <> itemId) then player.Events |> Array.filter (fun a -> a.ItemId <> itemId)
else player.Actions else player.Events
} }
do! DbService.updatePlayer updatedPlayer do! DbService.updatePlayer updatedPlayer
do! sendFollowUpMessage ctx $"Sold {item.Type} {item.Name} for {item.Cost}! Current Balance: {updatedPlayer.Bank}" do! sendFollowUpMessage ctx $"Sold {item.Type} {item.Name} for {item.Price}! Current Balance: {updatedPlayer.Bank}"
}) })
}) })

View File

@ -7,11 +7,6 @@ open DSharpPlus.Entities
open DSharpPlus.SlashCommands open DSharpPlus.SlashCommands
open Degenz.Messaging open Degenz.Messaging
[<Literal>]
let StealActionId = 12
[<Literal>]
let VictimDefenseActionId = 12
let ThiefCooldown = TimeSpan.FromMinutes(1) let ThiefCooldown = TimeSpan.FromMinutes(1)
let VictimRecovery = TimeSpan.FromHours(6) let VictimRecovery = TimeSpan.FromHours(6)
@ -89,8 +84,8 @@ let getResultEmbed targetName result =
let checkVictimStealingCooldown defender attacker = let checkVictimStealingCooldown defender attacker =
defender defender
|> Player.removeExpiredActions false |> Player.removeExpiredActions false
|> Player.getDefenses |> Player.getShieldEvents
|> Array.tryFind (fun act -> act.ActionId = VictimDefenseActionId) |> Array.tryFind (fun pe -> pe.Type = PlayerEventType.Mugged)
|> function |> function
| Some act -> | Some act ->
let cooldown = VictimRecovery - (DateTime.UtcNow - act.Timestamp) let cooldown = VictimRecovery - (DateTime.UtcNow - act.Timestamp)
@ -101,8 +96,8 @@ let checkVictimStealingCooldown defender attacker =
// TODO: Look for ways to generalize checking for action cooldowns // TODO: Look for ways to generalize checking for action cooldowns
let checkThiefCooldown attacker = let checkThiefCooldown attacker =
attacker attacker
|> Player.getAttacks |> Player.getHackEvents
|> Array.tryFind (fun act -> act.ActionId = StealActionId) |> Array.tryFind (fun pe -> pe.Type = PlayerEventType.Steal)
|> function |> function
| Some act -> | Some act ->
if ThiefCooldown > (DateTime.UtcNow - act.Timestamp) then if ThiefCooldown > (DateTime.UtcNow - act.Timestamp) then
@ -144,7 +139,7 @@ let handleSteal (ctx : IDiscordContext) =
let split = ctx.GetInteractionId().Split("-") let split = ctx.GetInteractionId().Split("-")
let answer = split.[1] let answer = split.[1]
let handleYes player = async { let handleYes (player : PlayerData) = async {
let targetId = uint64 split.[2] let targetId = uint64 split.[2]
let targetName = split.[3] let targetName = split.[3]
let chance = double split.[4] let chance = double split.[4]
@ -154,7 +149,7 @@ let handleSteal (ctx : IDiscordContext) =
let result = chance >= rand.NextDouble() , rand.Next(0,3) = 0 let result = chance >= rand.NextDouble() , rand.Next(0,3) = 0
let dp = { DiscordPlayer.Id = targetId ; DiscordPlayer.Name = targetName } let dp = { DiscordPlayer.Id = targetId ; DiscordPlayer.Name = targetName }
let stealAction = { ActionId = StealActionId ; Type = Attack { AttackResult.Result = false ; AttackResult.Target = dp } ; Timestamp = DateTime.UtcNow } let stealAction result = { ItemId = -1 ; Type = PlayerEventType.Steal ; Result = result ; Adversary = dp ; Timestamp = DateTime.UtcNow }
// TODO: Send event to the hall of privacy // TODO: Send event to the hall of privacy
// TODO: We need to check if the player is on cooldown // TODO: We need to check if the player is on cooldown
match result with match result with
@ -166,24 +161,24 @@ let handleSteal (ctx : IDiscordContext) =
do! Messaging.sendFollowUpEmbed ctx (embed.Build()) do! Messaging.sendFollowUpEmbed ctx (embed.Build())
match! DbService.tryFindPlayer targetId with match! DbService.tryFindPlayer targetId with
| Some t -> | Some t ->
let action = { ActionId = VictimDefenseActionId ; Type = Defense ; Timestamp = DateTime.UtcNow } let mugged = { ItemId = -1 ; Type = PlayerEventType.Mugged ; Result = PlayerEventResult.Failed ; Adversary = player.basicPlayer ; Timestamp = DateTime.UtcNow }
let actions = t |> Player.removeExpiredActions false |> fun p -> Array.append [| action |] p.Actions let actions = t |> Player.removeExpiredActions false |> fun p -> Array.append [| mugged |] p.Events
do! DbService.updatePlayer { t with Bank = max (t.Bank - prize) 0<GBT> ; Actions = actions } do! DbService.updatePlayer { t with Bank = max (t.Bank - prize) 0<GBT> ; Events = actions }
| None -> () | None -> ()
let action = { ActionId = StealActionId ; Type = Attack { AttackResult.Result = true ; AttackResult.Target = dp } ; Timestamp = DateTime.UtcNow } let stole = { ItemId = -1 ; Type = PlayerEventType.Steal ; Result = PlayerEventResult.Successful ; Adversary = dp ; Timestamp = DateTime.UtcNow }
let actions = player |> Player.removeExpiredActions false |> fun p -> Array.append [| action |] p.Actions let actions = player |> Player.removeExpiredActions false |> fun p -> Array.append [| stole |] p.Events
do! DbService.updatePlayer { player with Bank = player.Bank + prize ; XP = player.XP + xp ; Actions = actions } do! DbService.updatePlayer { player with Bank = player.Bank + prize ; XP = player.XP + xp ; Events = actions }
let newLevel = XP.getLevel (player.XP + xp) let newLevel = XP.getLevel (player.XP + xp)
// if XP.getLevel player.XP < newLevel then // if XP.getLevel player.XP < newLevel then
do! Async.Sleep 2000 do! Async.Sleep 2000
do! ctx.FollowUp (XP.getRewardsEmbed 1 player) |> Async.AwaitTask do! ctx.FollowUp (XP.getRewardsEmbed 1 player) |> Async.AwaitTask
| false , false -> | false , false ->
let embed = getResultEmbed targetName VictimRanAway let embed = getResultEmbed targetName VictimRanAway
do! DbService.updatePlayer { player with Actions = Array.append [| stealAction |] player.Actions } do! DbService.updatePlayer { player with Events = Array.append [| stealAction PlayerEventResult.Neutral |] player.Events }
do! Messaging.sendFollowUpEmbed ctx (embed.Build()) do! Messaging.sendFollowUpEmbed ctx (embed.Build())
| false , true -> | false , true ->
let embed = getResultEmbed targetName WentToPrison let embed = getResultEmbed targetName WentToPrison
do! DbService.updatePlayer { player with Actions = Array.append [| stealAction |] player.Actions } do! DbService.updatePlayer { player with Events = Array.append [| stealAction PlayerEventResult.Neutral |] player.Events }
do! Messaging.sendFollowUpEmbed ctx (embed.Build()) do! Messaging.sendFollowUpEmbed ctx (embed.Build())
do! Async.Sleep 2000 do! Async.Sleep 2000
let role = ctx.GetGuild().GetRole(GuildEnvironment.rolePrisoner) let role = ctx.GetGuild().GetRole(GuildEnvironment.rolePrisoner)

View File

@ -53,7 +53,7 @@ let defend (ctx : IDiscordContext) =
Game.executePlayerAction ctx (fun player -> async { Game.executePlayerAction ctx (fun player -> async {
let playerWithShields = let playerWithShields =
match Player.getShields player with match Player.getShields player with
| [||] -> { player with Arsenal = [| defaultShield |] } | [||] -> { player with Inventory = [| defaultShield |] }
| _ -> player | _ -> player
let embed = Embeds.pickDefense "Trainer-2" playerWithShields true let embed = Embeds.pickDefense "Trainer-2" playerWithShields true
@ -78,12 +78,11 @@ let handleDefense (ctx : IDiscordContext) =
let embed = Embeds.responseCreatedShield shield let embed = Embeds.responseCreatedShield shield
do! ctx.FollowUp embed |> Async.AwaitTask do! ctx.FollowUp embed |> Async.AwaitTask
do! Async.Sleep 4000 do! Async.Sleep 4000
let weakHack = Game.getGoodAgainst shield.Class do! sendMessage' $"Ok, good, let me make sure that worked.\n\nI'll try to **hack** you now with **VIRUS**"
do! sendMessage' $"Ok, good, let me make sure that worked.\n\nI'll try to **hack** you now with **{snd weakHack}**"
do! Async.Sleep 5000 do! Async.Sleep 5000
do! sendMessage' $"❌ HACKING FAILED!\n\n{player.Name} defended hack from <@{GuildEnvironment.botIdHackerBattle}>!" do! sendMessage' $"❌ HACKING FAILED!\n\n{player.Name} defended hack from <@{GuildEnvironment.botIdHackerBattle}>!"
do! Async.Sleep 4000 do! Async.Sleep 4000
do! sendFollowUpMessageWithButton ctx (handleDefenseMsg shieldId (snd weakHack)) do! sendFollowUpMessageWithButton ctx (handleDefenseMsg shieldId "VIRUS")
}) })
let handleTrainerStep3 (ctx : IDiscordContext) = let handleTrainerStep3 (ctx : IDiscordContext) =
@ -110,7 +109,7 @@ let attack (target : DiscordUser) (ctx : IDiscordContext) =
| true -> | true ->
let playerWithAttacks = let playerWithAttacks =
match Player.getHacks player with match Player.getHacks player with
| [||] -> { player with Arsenal = [| defaultHack |] } | [||] -> { player with Inventory = [| defaultHack |] }
| _ -> player | _ -> player
let bot = { player with DiscordId = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" } let bot = { player with DiscordId = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" }
let embed = Embeds.pickHack "Trainer-4" playerWithAttacks bot true let embed = Embeds.pickHack "Trainer-4" playerWithAttacks bot true
@ -154,14 +153,14 @@ let handleAttack (ctx : IDiscordContext) =
let rand = System.Random(System.Guid.NewGuid().GetHashCode()) let rand = System.Random(System.Guid.NewGuid().GetHashCode())
let freeHack = Armory.hacks.[rand.Next(0, 3)] let freeHack = Armory.hacks.[rand.Next(0, 3)]
let freeShield = Armory.shields.[rand.Next(0, 3)] let freeShield = Armory.shields.[rand.Next(0, 3)]
let hackMoney = if hasHacks then defaultHack.Cost else 0<GBT> let hackMoney = if hasHacks then defaultHack.Price else 0<GBT>
let shieldMoney = if hasShields then defaultShield.Cost else 0<GBT> let shieldMoney = if hasShields then defaultShield.Price else 0<GBT>
let giftMsg = let giftMsg =
match hasHacks , hasShields with match hasHacks , hasShields with
| true , true -> $"I'm going to give you these {hackMoney + shieldMoney} 💰$GBT" | true , true -> $"I'm going to give you these {hackMoney + shieldMoney} 💰$GBT"
| false , true -> $"I'm going to gift you a hack, `{freeHack.Name}` and {defaultHack.Cost} 💰$GBT" | false , true -> $"I'm going to gift you a hack, `{freeHack.Name}` and {defaultHack.Price} 💰$GBT"
| true , false -> $"I'm going to gift you a shield, `{freeShield.Name}` and {defaultHack.Cost} 💰$GBT" | true , false -> $"I'm going to gift you a shield, `{freeShield.Name}` and {defaultHack.Price} 💰$GBT"
| false , false -> $"I'm going to gift you a hack,`{freeHack.Name}` and a shield, `{freeShield.Name}`" | false , false -> $"I'm going to gift you a hack,`{freeHack.Name}` and a shield, `{freeShield.Name}`"
sb.Append(giftMsg) |> ignore sb.Append(giftMsg) |> ignore
@ -171,27 +170,25 @@ let handleAttack (ctx : IDiscordContext) =
let updatedPlayer = { let updatedPlayer = {
player with player with
Bank = player.Bank + hackMoney + shieldMoney Bank = player.Bank + hackMoney + shieldMoney
Actions = [ Events = [
{ Action.Timestamp = System.DateTime.UtcNow { PlayerEvent.Timestamp = System.DateTime.UtcNow
Action.Type = PlayerEvent.Adversary = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" }
Attack { PlayerEvent.Type = PlayerEventType.Shield
Result = true PlayerEvent.Result = PlayerEventResult.Successful
Target = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" } PlayerEvent.ItemId = defaultHack.Id }
} if not hasShields && Array.exists (fun act -> act.ItemId = freeShield.Id) player.Events |> not then {
ActionId = defaultHack.Id PlayerEvent.Timestamp = System.DateTime.UtcNow
} PlayerEvent.Adversary = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" }
if not hasShields && Array.exists (fun act -> act.ActionId = freeShield.Id) player.Actions |> not then { PlayerEvent.Type = PlayerEventType.Shield
Action.Timestamp = System.DateTime.UtcNow PlayerEvent.Result = PlayerEventResult.Successful
Action.Type = Defense PlayerEvent.ItemId = defaultHack.Id }
Action.ActionId = freeShield.Id
}
] |> Seq.toArray ] |> Seq.toArray
|> Array.append player.Actions |> Array.append player.Events
Arsenal = [ Inventory = [
if not hasHacks then freeHack if not hasHacks then freeHack
if not hasShields then freeShield if not hasShields then freeShield
] |> Seq.toArray ] |> Seq.toArray
|> Array.append player.Arsenal |> Array.append player.Inventory
} }
do! DbService.updatePlayer updatedPlayer do! DbService.updatePlayer updatedPlayer
do! sendFollowUpMessage ctx (sb.ToString()) do! sendFollowUpMessage ctx (sb.ToString())

View File

@ -26,26 +26,6 @@ type PlayerEntry =
XP : int XP : int
Bank : int } Bank : int }
let private actionToAttack (action : Action) (hack : AttackResult) =
{ ActionId = action.ActionId
Result = hack.Result
Target = hack.Target
Timestamp = action.Timestamp }
let private actionToDefense (action : Action) =
{ ActionId = action.ActionId
Timestamp = action.Timestamp }
let private attackToAction (attack : AttackAction) =
{ ActionId = attack.ActionId
Type = Attack { Target = attack.Target ; Result = attack.Result }
Timestamp = attack.Timestamp }
let private defenseToAction (action : DefenseAction) =
{ ActionId = action.ActionId
Type = Defense
Timestamp = action.Timestamp }
let private playerMap (player : PlayerData) = { let private playerMap (player : PlayerData) = {
DiscordId = player.DiscordId DiscordId = player.DiscordId
Name = player.Name Name = player.Name
@ -60,13 +40,13 @@ let tryWithDefault (bson : BsonDocument) field (defaultValue : 'a) (map : BsonVa
let private mapBack (bson : BsonDocument) : PlayerData = let private mapBack (bson : BsonDocument) : PlayerData =
{ DiscordId = tryWithDefault bson "Player.DiscordId" 0uL (fun v -> v.AsInt64 |> uint64) { DiscordId = tryWithDefault bson "Player.DiscordId" 0uL (fun v -> v.AsInt64 |> uint64)
Name = tryWithDefault bson "Player.Name" "Empty" (fun v -> v.AsString) Name = tryWithDefault bson "Player.Name" "Empty" (fun v -> v.AsString)
Arsenal = Inventory =
tryWithDefault bson "Inventory" [||] (fun v -> tryWithDefault bson "Inventory" [||] (fun v ->
v.AsBsonArray v.AsBsonArray
|> Seq.map (fun (bv : BsonValue) -> bv.AsInt32) |> Seq.map (fun (bv : BsonValue) -> bv.AsInt32)
|> Seq.map (fun w -> Armory.battleItems |> Array.find (fun w' -> w = w'.Id)) |> Seq.map (fun w -> Armory.battleItems |> Array.find (fun w' -> w = w'.Id))
|> Seq.toArray) |> Seq.toArray)
Actions = tryWithDefault bson "Events" [||] (fun _ -> [||]) Events = tryWithDefault bson "Events" [||] (fun _ -> [||])
Traits = tryWithDefault bson "Traits" PlayerTraits.empty (fun _ -> PlayerTraits.empty) Traits = tryWithDefault bson "Traits" PlayerTraits.empty (fun _ -> PlayerTraits.empty)
Achievements = tryWithDefault bson "Achievements" [||] (fun _ -> [||]) Achievements = tryWithDefault bson "Achievements" [||] (fun _ -> [||])
XP = tryWithDefault bson "XP" 0 (fun _ -> 0) XP = tryWithDefault bson "XP" 0 (fun _ -> 0)
@ -96,9 +76,9 @@ let updatePlayer (player : PlayerData) =
let update = Builders<BsonDocument>.Update let update = Builders<BsonDocument>.Update
.Set("Player", playerMap player) .Set("Player", playerMap player)
.AddToSet("Traits", player.Traits) .AddToSet("Traits", player.Traits)
.AddToSet("Events", player.Actions) .AddToSet("Events", player.Events)
.AddToSet("Achievements", player.Achievements) .AddToSet("Achievements", player.Achievements)
.AddToSet("Inventory", player.Arsenal) .AddToSet("Inventory", player.Inventory)
return! players.UpdateOneAsync(filter, update) |> Async.AwaitTask |> Async.Ignore return! players.UpdateOneAsync(filter, update) |> Async.AwaitTask |> Async.Ignore
} }

View File

@ -36,11 +36,6 @@ module Types =
[<Measure>] [<Measure>]
type GBT type GBT
type BattleClass =
| Network
| Exploit
| Penetration
type HackId = type HackId =
| Virus = 0 | Virus = 0
| RemoteAccess = 1 | RemoteAccess = 1
@ -58,9 +53,8 @@ module Types =
type BattleItem = { type BattleItem = {
Id : int Id : int
Name : string Name : string
Cost : int<GBT> Price : int<GBT>
Type : ItemType Type : ItemType
Class : BattleClass
Power : int Power : int
Cooldown : int<mins> Cooldown : int<mins>
} }
@ -71,51 +65,49 @@ module Types =
[<CLIMutable>] [<CLIMutable>]
type DiscordPlayer = { Id: uint64; Name: string } type DiscordPlayer = { Id: uint64; Name: string }
with static member empty = { Id = 0uL ; Name = "None" }
type PlayerEventResult =
| Successful = 0
| Neutral = 1
| Failed = 2
type PlayerEventType =
| Hack = 0
| Shield = 1
| Steal = 2
| Mugged = 3
| Imprison = 3
| Imprisoned = 3
[<CLIMutable>] [<CLIMutable>]
type AttackResult = { type PlayerEvent =
Result : bool { Type : PlayerEventType
Target : DiscordPlayer Result : PlayerEventResult
} Adversary : DiscordPlayer
ItemId : int
type ActionType =
| Attack of AttackResult
| Defense
[<CLIMutable>]
type Action =
{ ActionId : int
Type : ActionType
Timestamp : DateTime } Timestamp : DateTime }
type StatAmount = int
type XPAmount = int
type AttributeId =
| Strength = 0
| Cunning = 1
type PlayerTraits = { type PlayerTraits = {
Strength : int Strength : int
Focus : int Focus : int
Luck : int
Charisma : int
} }
with static member empty = { Strength = 0 ; Focus = 0 } with static member empty = { Strength = 0 ; Focus = 0 ; Luck = 0 ; Charisma = 0 }
type PlayerXP = {
Amount : XPAmount
}
[<CLIMutable>] [<CLIMutable>]
type PlayerData = { type PlayerData = {
DiscordId : uint64 DiscordId : uint64
Name : string Name : string
Arsenal : BattleItem array Inventory : BattleItem array
Actions : Action array Events : PlayerEvent array
Traits : PlayerTraits Traits : PlayerTraits
Achievements : string array Achievements : string array
XP : int XP : int
Bank : int<GBT> Bank : int<GBT>
} }
with member this.basicPlayer = { Id = this.DiscordId ; Name = this.Name }
module Armory = module Armory =
let battleItems = let battleItems =