Refactor for new inventory, events, and traits
This commit is contained in:
parent
c1c51dcd99
commit
7900bba3c3
@ -1,6 +1,7 @@
|
||||
module Degenz.Embeds
|
||||
|
||||
open System
|
||||
open DSharpPlus
|
||||
open Degenz.Messaging
|
||||
open Degenz.Types
|
||||
open DSharpPlus.Entities
|
||||
@ -37,15 +38,15 @@ let constructButtons (actionId: string) (buttonInfo : string) (player: PlayerDat
|
||||
|> Player.getItems itemType
|
||||
|> Array.map (fun item ->
|
||||
let action =
|
||||
player.Actions
|
||||
|> Array.tryFind (fun i -> i.ActionId = item.Id)
|
||||
player.Events
|
||||
|> Array.tryFind (fun i -> i.ItemId = item.Id)
|
||||
match action , isTrainer with
|
||||
| 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 ->
|
||||
let c = ((Armory.getItem act.ActionId).Cooldown)
|
||||
let c = ((Armory.getItem act.ItemId).Cooldown)
|
||||
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>
|
||||
|
||||
let pickDefense actionId player isTrainer =
|
||||
@ -109,25 +110,24 @@ let getBuyItemsEmbed (player : PlayerData) (itemType : ItemType) (store : Battle
|
||||
match item.Type with
|
||||
| Hack ->
|
||||
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)
|
||||
.WithThumbnail(getHackIcon (enum<HackId>(item.Id)))
|
||||
|> ignore
|
||||
| Shield ->
|
||||
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)
|
||||
.WithThumbnail(getShieldIcon (enum<ShieldId>(item.Id)))
|
||||
|> ignore
|
||||
embed
|
||||
.AddField("Cost 💰", $"{item.Cost} $GBT", true)
|
||||
.WithColor(Game.getClassEmbedColor item.Class)
|
||||
.AddField("Cost 💰", $"{item.Price} $GBT", true)
|
||||
.WithTitle($"{item.Name}")
|
||||
|> ignore
|
||||
let button =
|
||||
if player.Arsenal |> Array.exists (fun i -> i.Id = item.Id)
|
||||
then DiscordButtonComponent(Game.getClassButtonColor item.Class, $"Buy-{item.Id}", $"Own {item.Name}", true)
|
||||
else DiscordButtonComponent(Game.getClassButtonColor item.Class, $"Buy-{item.Id}", $"Buy {item.Name}")
|
||||
if player.Inventory |> Array.exists (fun i -> i.Id = item.Id)
|
||||
then DiscordButtonComponent(ButtonStyle.Primary, $"Buy-{item.Id}", $"Own {item.Name}", true)
|
||||
else DiscordButtonComponent(ButtonStyle.Primary, $"Buy-{item.Id}", $"Buy {item.Name}")
|
||||
embed.Build() , button :> DiscordComponent)
|
||||
|> Array.unzip
|
||||
|
||||
@ -138,7 +138,7 @@ let getBuyItemsEmbed (player : PlayerData) (itemType : ItemType) (store : Battle
|
||||
|
||||
let getSellItemsEmbed (itemType : ItemType) (player : PlayerData) =
|
||||
let embeds , buttons =
|
||||
player.Arsenal
|
||||
player.Inventory
|
||||
|> Array.filter (fun i -> i.Type = itemType)
|
||||
|> Array.map (fun item ->
|
||||
let embed = DiscordEmbedBuilder()
|
||||
@ -146,11 +146,10 @@ let getSellItemsEmbed (itemType : ItemType) (player : PlayerData) =
|
||||
| Hack -> embed.WithThumbnail(getHackIcon (enum<HackId>(item.Id))) |> ignore
|
||||
| Shield -> embed.WithThumbnail(getShieldIcon (enum<ShieldId>(item.Id))) |> ignore
|
||||
embed
|
||||
.AddField("Sell For 💰", $"{item.Cost} $GBT", true)
|
||||
.WithColor(Game.getClassEmbedColor item.Class)
|
||||
.AddField("Sell For 💰", $"{item.Price} $GBT", true)
|
||||
.WithTitle($"{item.Name}")
|
||||
|> 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)
|
||||
|> Array.unzip
|
||||
|
||||
|
70
Bot/Game.fs
70
Bot/Game.fs
@ -14,21 +14,6 @@ module Game =
|
||||
|
||||
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>) =
|
||||
async {
|
||||
let builder = DiscordInteractionResponseBuilder()
|
||||
@ -78,65 +63,64 @@ module Game =
|
||||
} |> Async.StartAsTask :> Task
|
||||
|
||||
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 getShields (player : PlayerData) = getItems ItemType.Shield player
|
||||
let getAttacks player =
|
||||
player.Actions
|
||||
|> Array.filter (fun act -> match act.Type with Attack _ -> true | _ -> false || act.ActionId < 12)
|
||||
let getDefenses player =
|
||||
player.Actions
|
||||
|> Array.filter (fun act -> match act.Type with Defense -> true | _ -> false || act.ActionId < 12)
|
||||
let getHackEvents player =
|
||||
player.Events
|
||||
|> Array.filter (fun act -> match act.Type with PlayerEventType.Hack -> true | _ -> false || act.ItemId < 12)
|
||||
let getShieldEvents player =
|
||||
player.Events
|
||||
|> 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
|
||||
// side and only check if it's the same target, we need to refactor Actions
|
||||
let removeExpiredActions filterByAttackCooldown player =
|
||||
let actions =
|
||||
player.Actions
|
||||
|> Array.filter (fun (act : Action) ->
|
||||
player.Events
|
||||
|> Array.filter (fun (act : PlayerEvent) ->
|
||||
let itemCooldown =
|
||||
if act.ActionId < 12 then
|
||||
(Armory.getItem act.ActionId).Cooldown
|
||||
if act.ItemId < 12 then
|
||||
(Armory.getItem act.ItemId).Cooldown
|
||||
else
|
||||
match act.Type with
|
||||
| Attack _ -> 1<mins>
|
||||
| Defense -> 720<mins>
|
||||
| PlayerEventType.Steal -> 1<mins>
|
||||
| _ -> 720<mins>
|
||||
|> int
|
||||
|
||||
match act.Type , filterByAttackCooldown with
|
||||
| Attack _ , true -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(itemCooldown)
|
||||
| Attack _ , false -> System.DateTime.UtcNow - act.Timestamp < Game.SameTargetAttackCooldown
|
||||
| Defense , _ -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(itemCooldown))
|
||||
{ player with Actions = actions }
|
||||
| PlayerEventType.Hack , true -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(itemCooldown)
|
||||
| PlayerEventType.Hack , false -> System.DateTime.UtcNow - act.Timestamp < Game.SameTargetAttackCooldown
|
||||
| PlayerEventType.Shield , _ -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(itemCooldown)
|
||||
| _ -> 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 getAttacksFlat actions = actions |> Array.choose (fun act -> match act.Type with Attack ar -> Some (act,ar.Target,ar.Result) | Defense -> None)
|
||||
|
||||
module Arsenal =
|
||||
let battleItemFormat (items : BattleItem array) =
|
||||
match items with
|
||||
| [||] -> "None"
|
||||
| _ -> items |> Array.toList |> List.map (fun i -> i.Name) |> String.concat ", "
|
||||
|
||||
let actionFormat (actions : Action array) =
|
||||
let actionFormat (actions : PlayerEvent array) =
|
||||
match actions with
|
||||
| [||] -> "None"
|
||||
| _ ->
|
||||
let hacks , defenses =
|
||||
actions
|
||||
|> Array.filter (fun act -> act.ActionId < 12)
|
||||
|> Array.partition (fun act -> match act.Type with Attack _ -> true | Defense -> false)
|
||||
|> Array.filter (fun act -> act.ItemId < 12)
|
||||
|> Array.partition (fun act -> match act.Type with PlayerEventType.Hack -> true | _ -> false)
|
||||
let hacks = hacks |> Array.take (min hacks.Length 10)
|
||||
hacks
|
||||
|> Array.append defenses
|
||||
|> Array.map (fun act ->
|
||||
let item = Armory.getItem act.ActionId
|
||||
let item = Armory.getItem act.ItemId
|
||||
match act.Type with
|
||||
| Attack atk ->
|
||||
| PlayerEventType.Hack ->
|
||||
let cooldown = Messaging.getTimeText false Game.SameTargetAttackCooldown act.Timestamp
|
||||
$"Hacked {atk.Target.Name} {cooldown} ago"
|
||||
| Defense ->
|
||||
$"Hacked {act.Adversary.Name} {cooldown} ago"
|
||||
| _ ->
|
||||
let cooldown = Messaging.getTimeText true (System.TimeSpan.FromMinutes(int item.Cooldown)) act.Timestamp
|
||||
$"{item.Name} Shield active for {cooldown}")
|
||||
|> String.concat "\n"
|
||||
@ -144,6 +128,6 @@ module Arsenal =
|
||||
let statusFormat p =
|
||||
$"**Hacks:** {Player.getHacks p |> battleItemFormat}\n
|
||||
**Shields:** {Player.getShields p |> battleItemFormat}\n
|
||||
**Hack Attacks:**\n{Player.getAttacks p |> actionFormat}\n
|
||||
**Active Shields:**\n{Player.getDefenses p |> actionFormat}"
|
||||
**Hack Attacks:**\n{Player.getHackEvents p |> actionFormat}\n
|
||||
**Active Shields:**\n{Player.getShieldEvents p |> actionFormat}"
|
||||
|
||||
|
@ -15,19 +15,18 @@ let checkPlayerIsAttackingThemselves defender attacker =
|
||||
| false -> Ok attacker
|
||||
|
||||
let checkAlreadyHackedTarget defenderId attacker =
|
||||
attacker.Actions
|
||||
|> Player.getAttacksFlat
|
||||
|> Array.tryFind (fun (_,t,_) -> t.Id = defenderId)
|
||||
attacker.Events
|
||||
|> Array.tryFind (fun pe -> pe.Adversary.Id = defenderId)
|
||||
|> function
|
||||
| Some ( atk , target , _ ) ->
|
||||
let cooldown = getTimeText true 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}."
|
||||
| Some event ->
|
||||
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 {event.Adversary.Name}."
|
||||
| None -> Ok attacker
|
||||
|
||||
let checkItemHasCooldown itemId attacker =
|
||||
let cooldown =
|
||||
attacker.Actions
|
||||
|> Array.tryFind (fun a -> a.ActionId = itemId)
|
||||
attacker.Events
|
||||
|> Array.tryFind (fun a -> a.ItemId = itemId)
|
||||
|> function
|
||||
| Some a -> a.Timestamp
|
||||
| None -> DateTime.MinValue
|
||||
@ -45,7 +44,7 @@ let checkHasEmptyHacks attacker =
|
||||
| _ -> Ok attacker
|
||||
|
||||
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
|
||||
| false -> Error $"You sold your weapon already, you cheeky bastard..."
|
||||
|
||||
@ -56,7 +55,7 @@ let checkTargetHasMoney (target : PlayerData) attacker =
|
||||
|
||||
let checkPlayerHasShieldSlotsAvailable shield player =
|
||||
let updatedPlayer = player |> Player.removeExpiredActions false
|
||||
let defenses = Player.getDefenses updatedPlayer
|
||||
let defenses = Player.getShieldEvents updatedPlayer
|
||||
match defenses |> Array.length >= 2 with
|
||||
| true ->
|
||||
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
|
||||
|
||||
let calculateDamage (hack : BattleItem) (shield : BattleItem) =
|
||||
if hack.Class = shield.Class
|
||||
if hack.Power < shield.Power
|
||||
then Weak
|
||||
else Strong
|
||||
|
||||
let runHackerBattle defender hack =
|
||||
defender
|
||||
|> Player.removeExpiredActions false
|
||||
|> Player.getDefenses
|
||||
|> Array.map (fun dfn -> Armory.battleItems |> Array.find (fun w -> w.Id = dfn.ActionId))
|
||||
|> Player.getShieldEvents
|
||||
|> Array.map (fun dfn -> Armory.battleItems |> Array.find (fun w -> w.Id = dfn.ItemId))
|
||||
|> Array.map (calculateDamage (hack))
|
||||
|> Array.contains Weak
|
||||
|
||||
let updateCombatants attacker defender hack prize =
|
||||
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 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 <| Player.modifyBank defender -prize ]
|
||||
|> Async.Parallel
|
||||
@ -181,8 +189,14 @@ let handleDefense (ctx : IDiscordContext) =
|
||||
|> handleResultWithResponse ctx (fun _ -> async { // Don't use this player, it removes player cooldowns
|
||||
let embed = Embeds.responseCreatedShield (Armory.getItem shieldId)
|
||||
do! ctx.FollowUp embed |> Async.AwaitTask
|
||||
let defense = { ActionId = shieldId ; Type = Defense ; Timestamp = DateTime.UtcNow }
|
||||
do! DbService.updatePlayer <| { player with Actions = Array.append [| defense |] player.Actions }
|
||||
let defense = {
|
||||
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()
|
||||
builder.WithContent($"{ctx.GetDiscordMember().Username} has protected their system!") |> ignore
|
||||
let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelEventsHackerBattle)
|
||||
|
@ -14,8 +14,8 @@ module Commands =
|
||||
|
||||
{ DiscordId = membr
|
||||
Name = nickname
|
||||
Arsenal = [| hack ; shield |]
|
||||
Actions = [||]
|
||||
Inventory = [| hack ; shield |]
|
||||
Events = [||]
|
||||
XP = 0
|
||||
Achievements = [||]
|
||||
Traits = PlayerTraits.empty
|
||||
|
24
Bot/Store.fs
24
Bot/Store.fs
@ -9,22 +9,22 @@ open Degenz
|
||||
open Degenz.Messaging
|
||||
|
||||
let checkHasSufficientFunds (item : BattleItem) player =
|
||||
if player.Bank - item.Cost >= 0<GBT>
|
||||
if player.Bank - item.Price >= 0<GBT>
|
||||
then Ok player
|
||||
else Error $"You do not have sufficient funds to buy this item! Current balance: {player.Bank} GBT"
|
||||
|
||||
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}!"
|
||||
else Ok 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
|
||||
else Error $"{item.Name} not found in your arsenal! Looks like you sold it already."
|
||||
|
||||
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
|
||||
else Error $"You currently have no {itemType}s in your arsenal to sell!"
|
||||
|
||||
@ -50,8 +50,8 @@ let handleBuyItem (ctx : IDiscordContext) itemId =
|
||||
|> checkHasSufficientFunds item
|
||||
>>= checkAlreadyOwnsItem item
|
||||
|> handleResultWithResponse ctx (fun player -> async {
|
||||
let newBalance = player.Bank - item.Cost
|
||||
let p = { player with Bank = newBalance ; Arsenal = Array.append [| item |] player.Arsenal }
|
||||
let newBalance = player.Bank - item.Price
|
||||
let p = { player with Bank = newBalance ; Inventory = Array.append [| item |] player.Inventory }
|
||||
do! DbService.updatePlayer p
|
||||
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 {
|
||||
let updatedPlayer = {
|
||||
player with
|
||||
Bank = player.Bank + item.Cost
|
||||
Arsenal = player.Arsenal |> Array.filter (fun i -> i.Id <> itemId)
|
||||
Actions =
|
||||
Bank = player.Bank + item.Price
|
||||
Inventory = player.Inventory |> Array.filter (fun i -> i.Id <> itemId)
|
||||
Events =
|
||||
if item.Type = ItemType.Shield
|
||||
then player.Actions |> Array.filter (fun a -> a.ActionId <> itemId)
|
||||
else player.Actions
|
||||
then player.Events |> Array.filter (fun a -> a.ItemId <> itemId)
|
||||
else player.Events
|
||||
}
|
||||
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}"
|
||||
})
|
||||
})
|
||||
|
||||
|
33
Bot/Thief.fs
33
Bot/Thief.fs
@ -7,11 +7,6 @@ open DSharpPlus.Entities
|
||||
open DSharpPlus.SlashCommands
|
||||
open Degenz.Messaging
|
||||
|
||||
[<Literal>]
|
||||
let StealActionId = 12
|
||||
[<Literal>]
|
||||
let VictimDefenseActionId = 12
|
||||
|
||||
let ThiefCooldown = TimeSpan.FromMinutes(1)
|
||||
let VictimRecovery = TimeSpan.FromHours(6)
|
||||
|
||||
@ -89,8 +84,8 @@ let getResultEmbed targetName result =
|
||||
let checkVictimStealingCooldown defender attacker =
|
||||
defender
|
||||
|> Player.removeExpiredActions false
|
||||
|> Player.getDefenses
|
||||
|> Array.tryFind (fun act -> act.ActionId = VictimDefenseActionId)
|
||||
|> Player.getShieldEvents
|
||||
|> Array.tryFind (fun pe -> pe.Type = PlayerEventType.Mugged)
|
||||
|> function
|
||||
| Some act ->
|
||||
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
|
||||
let checkThiefCooldown attacker =
|
||||
attacker
|
||||
|> Player.getAttacks
|
||||
|> Array.tryFind (fun act -> act.ActionId = StealActionId)
|
||||
|> Player.getHackEvents
|
||||
|> Array.tryFind (fun pe -> pe.Type = PlayerEventType.Steal)
|
||||
|> function
|
||||
| Some act ->
|
||||
if ThiefCooldown > (DateTime.UtcNow - act.Timestamp) then
|
||||
@ -144,7 +139,7 @@ let handleSteal (ctx : IDiscordContext) =
|
||||
let split = ctx.GetInteractionId().Split("-")
|
||||
let answer = split.[1]
|
||||
|
||||
let handleYes player = async {
|
||||
let handleYes (player : PlayerData) = async {
|
||||
let targetId = uint64 split.[2]
|
||||
let targetName = split.[3]
|
||||
let chance = double split.[4]
|
||||
@ -154,7 +149,7 @@ let handleSteal (ctx : IDiscordContext) =
|
||||
|
||||
let result = chance >= rand.NextDouble() , rand.Next(0,3) = 0
|
||||
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: We need to check if the player is on cooldown
|
||||
match result with
|
||||
@ -166,24 +161,24 @@ let handleSteal (ctx : IDiscordContext) =
|
||||
do! Messaging.sendFollowUpEmbed ctx (embed.Build())
|
||||
match! DbService.tryFindPlayer targetId with
|
||||
| Some t ->
|
||||
let action = { ActionId = VictimDefenseActionId ; Type = Defense ; Timestamp = DateTime.UtcNow }
|
||||
let actions = t |> Player.removeExpiredActions false |> fun p -> Array.append [| action |] p.Actions
|
||||
do! DbService.updatePlayer { t with Bank = max (t.Bank - prize) 0<GBT> ; Actions = actions }
|
||||
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 [| mugged |] p.Events
|
||||
do! DbService.updatePlayer { t with Bank = max (t.Bank - prize) 0<GBT> ; Events = actions }
|
||||
| None -> ()
|
||||
let action = { ActionId = StealActionId ; Type = Attack { AttackResult.Result = true ; AttackResult.Target = dp } ; Timestamp = DateTime.UtcNow }
|
||||
let actions = player |> Player.removeExpiredActions false |> fun p -> Array.append [| action |] p.Actions
|
||||
do! DbService.updatePlayer { player with Bank = player.Bank + prize ; XP = player.XP + xp ; Actions = actions }
|
||||
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 [| stole |] p.Events
|
||||
do! DbService.updatePlayer { player with Bank = player.Bank + prize ; XP = player.XP + xp ; Events = actions }
|
||||
let newLevel = XP.getLevel (player.XP + xp)
|
||||
// if XP.getLevel player.XP < newLevel then
|
||||
do! Async.Sleep 2000
|
||||
do! ctx.FollowUp (XP.getRewardsEmbed 1 player) |> Async.AwaitTask
|
||||
| false , false ->
|
||||
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())
|
||||
| false , true ->
|
||||
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! Async.Sleep 2000
|
||||
let role = ctx.GetGuild().GetRole(GuildEnvironment.rolePrisoner)
|
||||
|
@ -53,7 +53,7 @@ let defend (ctx : IDiscordContext) =
|
||||
Game.executePlayerAction ctx (fun player -> async {
|
||||
let playerWithShields =
|
||||
match Player.getShields player with
|
||||
| [||] -> { player with Arsenal = [| defaultShield |] }
|
||||
| [||] -> { player with Inventory = [| defaultShield |] }
|
||||
| _ -> player
|
||||
|
||||
let embed = Embeds.pickDefense "Trainer-2" playerWithShields true
|
||||
@ -78,12 +78,11 @@ let handleDefense (ctx : IDiscordContext) =
|
||||
let embed = Embeds.responseCreatedShield shield
|
||||
do! ctx.FollowUp embed |> Async.AwaitTask
|
||||
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 **{snd weakHack}**"
|
||||
do! sendMessage' $"Ok, good, let me make sure that worked.\n\nI'll try to **hack** you now with **VIRUS**"
|
||||
do! Async.Sleep 5000
|
||||
do! sendMessage' $"❌ HACKING FAILED!\n\n{player.Name} defended hack from <@{GuildEnvironment.botIdHackerBattle}>!"
|
||||
do! Async.Sleep 4000
|
||||
do! sendFollowUpMessageWithButton ctx (handleDefenseMsg shieldId (snd weakHack))
|
||||
do! sendFollowUpMessageWithButton ctx (handleDefenseMsg shieldId "VIRUS")
|
||||
|
||||
})
|
||||
let handleTrainerStep3 (ctx : IDiscordContext) =
|
||||
@ -110,7 +109,7 @@ let attack (target : DiscordUser) (ctx : IDiscordContext) =
|
||||
| true ->
|
||||
let playerWithAttacks =
|
||||
match Player.getHacks player with
|
||||
| [||] -> { player with Arsenal = [| defaultHack |] }
|
||||
| [||] -> { player with Inventory = [| defaultHack |] }
|
||||
| _ -> player
|
||||
let bot = { player with DiscordId = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" }
|
||||
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 freeHack = Armory.hacks.[rand.Next(0, 3)]
|
||||
let freeShield = Armory.shields.[rand.Next(0, 3)]
|
||||
let hackMoney = if hasHacks then defaultHack.Cost else 0<GBT>
|
||||
let shieldMoney = if hasShields then defaultShield.Cost else 0<GBT>
|
||||
let hackMoney = if hasHacks then defaultHack.Price else 0<GBT>
|
||||
let shieldMoney = if hasShields then defaultShield.Price else 0<GBT>
|
||||
|
||||
let giftMsg =
|
||||
match hasHacks , hasShields with
|
||||
| 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"
|
||||
| true , false -> $"I'm going to gift you a shield, `{freeShield.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.Price} 💰$GBT"
|
||||
| false , false -> $"I'm going to gift you a hack,`{freeHack.Name}` and a shield, `{freeShield.Name}`"
|
||||
|
||||
sb.Append(giftMsg) |> ignore
|
||||
@ -171,27 +170,25 @@ let handleAttack (ctx : IDiscordContext) =
|
||||
let updatedPlayer = {
|
||||
player with
|
||||
Bank = player.Bank + hackMoney + shieldMoney
|
||||
Actions = [
|
||||
{ Action.Timestamp = System.DateTime.UtcNow
|
||||
Action.Type =
|
||||
Attack {
|
||||
Result = true
|
||||
Target = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" }
|
||||
}
|
||||
ActionId = defaultHack.Id
|
||||
}
|
||||
if not hasShields && Array.exists (fun act -> act.ActionId = freeShield.Id) player.Actions |> not then {
|
||||
Action.Timestamp = System.DateTime.UtcNow
|
||||
Action.Type = Defense
|
||||
Action.ActionId = freeShield.Id
|
||||
}
|
||||
Events = [
|
||||
{ PlayerEvent.Timestamp = System.DateTime.UtcNow
|
||||
PlayerEvent.Adversary = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" }
|
||||
PlayerEvent.Type = PlayerEventType.Shield
|
||||
PlayerEvent.Result = PlayerEventResult.Successful
|
||||
PlayerEvent.ItemId = defaultHack.Id }
|
||||
if not hasShields && Array.exists (fun act -> act.ItemId = freeShield.Id) player.Events |> not then {
|
||||
PlayerEvent.Timestamp = System.DateTime.UtcNow
|
||||
PlayerEvent.Adversary = { Id = GuildEnvironment.botIdHackerBattle ; Name = "Sensei" }
|
||||
PlayerEvent.Type = PlayerEventType.Shield
|
||||
PlayerEvent.Result = PlayerEventResult.Successful
|
||||
PlayerEvent.ItemId = defaultHack.Id }
|
||||
] |> Seq.toArray
|
||||
|> Array.append player.Actions
|
||||
Arsenal = [
|
||||
|> Array.append player.Events
|
||||
Inventory = [
|
||||
if not hasHacks then freeHack
|
||||
if not hasShields then freeShield
|
||||
] |> Seq.toArray
|
||||
|> Array.append player.Arsenal
|
||||
|> Array.append player.Inventory
|
||||
}
|
||||
do! DbService.updatePlayer updatedPlayer
|
||||
do! sendFollowUpMessage ctx (sb.ToString())
|
||||
|
@ -26,26 +26,6 @@ type PlayerEntry =
|
||||
XP : 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) = {
|
||||
DiscordId = player.DiscordId
|
||||
Name = player.Name
|
||||
@ -60,13 +40,13 @@ let tryWithDefault (bson : BsonDocument) field (defaultValue : 'a) (map : BsonVa
|
||||
let private mapBack (bson : BsonDocument) : PlayerData =
|
||||
{ DiscordId = tryWithDefault bson "Player.DiscordId" 0uL (fun v -> v.AsInt64 |> uint64)
|
||||
Name = tryWithDefault bson "Player.Name" "Empty" (fun v -> v.AsString)
|
||||
Arsenal =
|
||||
Inventory =
|
||||
tryWithDefault bson "Inventory" [||] (fun v ->
|
||||
v.AsBsonArray
|
||||
|> Seq.map (fun (bv : BsonValue) -> bv.AsInt32)
|
||||
|> Seq.map (fun w -> Armory.battleItems |> Array.find (fun w' -> w = w'.Id))
|
||||
|> Seq.toArray)
|
||||
Actions = tryWithDefault bson "Events" [||] (fun _ -> [||])
|
||||
Events = tryWithDefault bson "Events" [||] (fun _ -> [||])
|
||||
Traits = tryWithDefault bson "Traits" PlayerTraits.empty (fun _ -> PlayerTraits.empty)
|
||||
Achievements = tryWithDefault bson "Achievements" [||] (fun _ -> [||])
|
||||
XP = tryWithDefault bson "XP" 0 (fun _ -> 0)
|
||||
@ -96,9 +76,9 @@ let updatePlayer (player : PlayerData) =
|
||||
let update = Builders<BsonDocument>.Update
|
||||
.Set("Player", playerMap player)
|
||||
.AddToSet("Traits", player.Traits)
|
||||
.AddToSet("Events", player.Actions)
|
||||
.AddToSet("Events", player.Events)
|
||||
.AddToSet("Achievements", player.Achievements)
|
||||
.AddToSet("Inventory", player.Arsenal)
|
||||
.AddToSet("Inventory", player.Inventory)
|
||||
return! players.UpdateOneAsync(filter, update) |> Async.AwaitTask |> Async.Ignore
|
||||
}
|
||||
|
||||
|
@ -36,11 +36,6 @@ module Types =
|
||||
[<Measure>]
|
||||
type GBT
|
||||
|
||||
type BattleClass =
|
||||
| Network
|
||||
| Exploit
|
||||
| Penetration
|
||||
|
||||
type HackId =
|
||||
| Virus = 0
|
||||
| RemoteAccess = 1
|
||||
@ -58,9 +53,8 @@ module Types =
|
||||
type BattleItem = {
|
||||
Id : int
|
||||
Name : string
|
||||
Cost : int<GBT>
|
||||
Price : int<GBT>
|
||||
Type : ItemType
|
||||
Class : BattleClass
|
||||
Power : int
|
||||
Cooldown : int<mins>
|
||||
}
|
||||
@ -71,51 +65,49 @@ module Types =
|
||||
|
||||
[<CLIMutable>]
|
||||
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>]
|
||||
type AttackResult = {
|
||||
Result : bool
|
||||
Target : DiscordPlayer
|
||||
}
|
||||
|
||||
type ActionType =
|
||||
| Attack of AttackResult
|
||||
| Defense
|
||||
|
||||
[<CLIMutable>]
|
||||
type Action =
|
||||
{ ActionId : int
|
||||
Type : ActionType
|
||||
type PlayerEvent =
|
||||
{ Type : PlayerEventType
|
||||
Result : PlayerEventResult
|
||||
Adversary : DiscordPlayer
|
||||
ItemId : int
|
||||
Timestamp : DateTime }
|
||||
|
||||
type StatAmount = int
|
||||
type XPAmount = int
|
||||
|
||||
type AttributeId =
|
||||
| Strength = 0
|
||||
| Cunning = 1
|
||||
|
||||
type PlayerTraits = {
|
||||
Strength : int
|
||||
Focus : int
|
||||
Luck : int
|
||||
Charisma : int
|
||||
}
|
||||
with static member empty = { Strength = 0 ; Focus = 0 }
|
||||
|
||||
type PlayerXP = {
|
||||
Amount : XPAmount
|
||||
}
|
||||
with static member empty = { Strength = 0 ; Focus = 0 ; Luck = 0 ; Charisma = 0 }
|
||||
|
||||
[<CLIMutable>]
|
||||
type PlayerData = {
|
||||
DiscordId : uint64
|
||||
Name : string
|
||||
Arsenal : BattleItem array
|
||||
Actions : Action array
|
||||
Inventory : BattleItem array
|
||||
Events : PlayerEvent array
|
||||
Traits : PlayerTraits
|
||||
Achievements : string array
|
||||
XP : int
|
||||
Bank : int<GBT>
|
||||
}
|
||||
with member this.basicPlayer = { Id = this.DiscordId ; Name = this.Name }
|
||||
|
||||
module Armory =
|
||||
let battleItems =
|
||||
|
Loading…
x
Reference in New Issue
Block a user