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
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

View File

@ -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}"

View File

@ -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)

View File

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

View File

@ -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}"
})
})

View File

@ -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)

View File

@ -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())

View File

@ -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
}

View File

@ -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 =