Cooldowns done right. Fix shield sell exploit

This commit is contained in:
Joseph Ferano 2022-02-09 21:12:26 +07:00
parent 8dbb5e1145
commit a785ba3120
6 changed files with 133 additions and 109 deletions

View File

@ -1,7 +1,6 @@
module Degenz.Embeds module Degenz.Embeds
open System open System
open DSharpPlus
open DSharpPlus.EventArgs open DSharpPlus.EventArgs
open Degenz.Types open Degenz.Types
open DSharpPlus.Entities open DSharpPlus.Entities
@ -33,18 +32,24 @@ let getShieldGif = function
| ShieldId.Cypher -> "https://s10.gifyu.com/images/Cypher-Smaller.gif" | ShieldId.Cypher -> "https://s10.gifyu.com/images/Cypher-Smaller.gif"
| _ -> shieldGif | _ -> shieldGif
let constructButtons (actionType: string) (player: PlayerData) (buttonId : string) (items: BattleItem array) isTrainer = let constructButtons (actionId: string) (buttonInfo : string) (player: PlayerData) itemType isTrainer =
items player
|> Player.getItems itemType
|> Array.map (fun item -> |> Array.map (fun item ->
// match player.Actions |> Array.exists (fun i -> i.ActionId = item.Id) && not isTrainer with let action =
match false with player.Actions
| true -> DiscordButtonComponent(Game.getClassButtonColor item.Class, $"{actionType}-{item.Id}", $"{item.Name} (on cooldown)", true) |> Array.tryFind (fun i -> i.ActionId = item.Id)
| false -> DiscordButtonComponent(Game.getClassButtonColor item.Class, $"{actionType}-{item.Id}-{buttonId}", $"{item.Name}")) match action , isTrainer with
| None , _ | Some _ , true ->
DiscordButtonComponent(Game.getClassButtonColor item.Class, $"{actionId}-{item.Id}-{buttonInfo}", $"{item.Name}")
| Some act , false ->
let c = ((Armory.getItem act.ActionId).Cooldown)
let time = Messaging.getShortTimeText (TimeSpan.FromMinutes(int c)) act.Timestamp
DiscordButtonComponent(Game.getClassButtonColor item.Class, $"{actionId}-{item.Id}", $"{item.Name} ({time} left)", true))
|> Seq.cast<DiscordComponent>
let pickDefense actionId player isTrainer = let pickDefense actionId player isTrainer =
let buttons = let buttons = constructButtons actionId (string player.DiscordId) player ItemType.Shield isTrainer
constructButtons actionId player (string player.DiscordId) (Player.shields player) isTrainer
|> Seq.cast<DiscordComponent>
let embed = let embed =
DiscordEmbedBuilder() DiscordEmbedBuilder()
@ -58,10 +63,7 @@ let pickDefense actionId player isTrainer =
.AsEphemeral(true) .AsEphemeral(true)
let pickHack actionId attacker defender isTrainer = let pickHack actionId attacker defender isTrainer =
let buttons = let buttons = constructButtons actionId $"{defender.DiscordId}-{defender.Name}" attacker ItemType.Hack isTrainer
let hacks = Player.hacks attacker
constructButtons actionId attacker $"{defender.DiscordId}-{defender.Name}" hacks isTrainer
|> Seq.cast<DiscordComponent>
let embed = let embed =
DiscordEmbedBuilder() DiscordEmbedBuilder()

View File

@ -53,23 +53,23 @@ module Game =
:> Task :> Task
module Player = module Player =
let hacks (player : PlayerData) = player.Arsenal |> Array.filter (fun bi -> bi.Type = Hack) let getItems itemType (player : PlayerData) = player.Arsenal |> Array.filter (fun i -> i.Type = itemType)
let shields (player : PlayerData) = player.Arsenal |> Array.filter (fun bi -> bi.Type = Shield) let hacks (player : PlayerData) = getItems ItemType.Hack player
let getShields (player : PlayerData) = getItems ItemType.Shield player
let attacks player = let attacks player =
player.Actions player.Actions
|> Array.filter (fun act -> match act.Type with Attack _ -> true | _ -> false) |> Array.filter (fun act -> match act.Type with Attack _ -> true | _ -> false)
let defenses player = player.Actions |> Array.filter (fun act -> match act.Type with Defense -> true | _ -> false) let defenses player = player.Actions |> Array.filter (fun act -> match act.Type with Defense -> true | _ -> false)
let removeExpiredActions player = let removeExpiredActions filterByAttackCooldown player =
let actions = let actions =
player.Actions player.Actions
|> Array.filter (fun (act : Action) -> |> Array.filter (fun (act : Action) ->
match act.Type with
// So the player doesnt attack the same player so many times in a row
| Attack _ -> System.DateTime.UtcNow - act.Timestamp < Game.SameTargetAttackCooldown
| Defense ->
let item = Armory.getItem act.ActionId let item = Armory.getItem act.ActionId
System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(int item.Cooldown)) match act.Type , filterByAttackCooldown with
| Attack _ , true -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(int item.Cooldown)
| Attack _ , false -> System.DateTime.UtcNow - act.Timestamp < Game.SameTargetAttackCooldown
| Defense , _ -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(int item.Cooldown))
{ player with Actions = actions } { player with Actions = 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> }

View File

@ -22,23 +22,23 @@ let checkAlreadyHackedTarget defenderId attacker =
|> Array.tryFind (fun (_,t,_) -> t.Id = defenderId) |> Array.tryFind (fun (_,t,_) -> t.Id = defenderId)
|> function |> function
| Some ( atk , target , _ ) -> | Some ( atk , target , _ ) ->
let cooldown = getTimeTillCooldownFinishes Game.SameTargetAttackCooldown atk.Timestamp 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}." Error $"You can only hack the same target once every {Game.SameTargetAttackCooldown.Hours} hours, wait {cooldown} to attempt another hack on {target.Name}."
| None -> Ok attacker | None -> Ok attacker
let checkHackHasCooldown hackId attacker = let checkItemHasCooldown itemId attacker =
let mostRecentHackAttack = let cooldown =
attacker.Actions attacker.Actions
|> Array.tryFind (fun a -> a.ActionId = hackId) |> Array.tryFind (fun a -> a.ActionId = itemId)
|> function |> function
| Some a -> a.Timestamp | Some a -> a.Timestamp
| None -> DateTime.MinValue | None -> DateTime.MinValue
let item = Armory.getItem hackId let item = Armory.getItem itemId
if DateTime.UtcNow - mostRecentHackAttack > TimeSpan.FromMinutes(int item.Cooldown) then if DateTime.UtcNow - cooldown > TimeSpan.FromMinutes(int item.Cooldown) then
Ok attacker Ok attacker
else else
let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromMinutes(int item.Cooldown)) mostRecentHackAttack let cooldown = getTimeText true (TimeSpan.FromMinutes(int item.Cooldown)) cooldown
let item = Armory.battleItems |> Array.find (fun i -> i.Id = hackId) let item = Armory.battleItems |> Array.find (fun i -> i.Id = itemId)
Error $"{item.Name} is currently on cooldown, wait {cooldown} to use it again." Error $"{item.Name} is currently on cooldown, wait {cooldown} to use it again."
let checkHasEmptyHacks attacker = let checkHasEmptyHacks attacker =
@ -46,11 +46,26 @@ let checkHasEmptyHacks attacker =
| [||] -> Error $"You currently do not have any Hacks to steal 💰$GBT from others. Please go to the <#{GuildEnvironment.channelArmory}> and purchase one." | [||] -> Error $"You currently do not have any Hacks to steal 💰$GBT from others. Please go to the <#{GuildEnvironment.channelArmory}> and purchase one."
| _ -> Ok attacker | _ -> Ok attacker
let checkPlayerOwnsWeapon itemId player =
match player.Arsenal |> Array.exists (fun i -> i.Id = itemId) with
| true -> Ok player
| false -> Error $"You sold your weapon already, you cheeky bastard..."
let checkTargetHasMoney (target : PlayerData) attacker = let checkTargetHasMoney (target : PlayerData) attacker =
if target.Bank < Game.HackPrize if target.Bank < Game.HackPrize
then Error $"{target.Name} does not have enough 💰$GBT to steal from, the broke loser. Pick a different target." then Error $"{target.Name} does not have enough 💰$GBT to steal from, the broke loser. Pick a different target."
else Ok attacker else Ok attacker
let checkPlayerHasShieldSlotsAvailable shield player =
let updatedPlayer = player |> Player.removeExpiredActions false
let defenses = updatedPlayer |> fun p -> p.Actions
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
let cooldown = getTimeText true (TimeSpan.FromMinutes(int shield.Cooldown)) timestamp
Error $"You are only allowed two shields at a time. Wait {cooldown} to add another shield"
| false -> Ok updatedPlayer
let calculateDamage (hack : BattleItem) (shield : BattleItem) = let calculateDamage (hack : BattleItem) (shield : BattleItem) =
if hack.Class = shield.Class if hack.Class = shield.Class
then Weak then Weak
@ -58,7 +73,7 @@ let calculateDamage (hack : BattleItem) (shield : BattleItem) =
let runHackerBattle defender hack = let runHackerBattle defender hack =
defender defender
|> Player.removeExpiredActions |> Player.removeExpiredActions false
|> Player.defenses |> Player.defenses
|> 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.ActionId))
|> Array.map (calculateDamage (hack)) |> Array.map (calculateDamage (hack))
@ -79,7 +94,7 @@ let successfulHack (event : ComponentInteractionCreateEventArgs) attacker defend
async { async {
do! updateCombatants attacker defender hack Game.HackPrize do! updateCombatants attacker defender hack Game.HackPrize
let embed = Embeds.responseSuccessfulHack (defender.Name) (Armory.getItem (int hack)) let embed = Embeds.responseSuccessfulHack (defender.Name) (Armory.getItem hack)
do! event.Interaction.CreateFollowupMessageAsync(embed) do! event.Interaction.CreateFollowupMessageAsync(embed)
|> Async.AwaitTask |> Async.AwaitTask
|> Async.Ignore |> Async.Ignore
@ -112,14 +127,14 @@ let attack (target : DiscordUser) (ctx : InteractionContext) =
match defender with match defender with
| Some defender -> | Some defender ->
do! attacker do! attacker
|> Player.removeExpiredActions
|> checkAlreadyHackedTarget defender.DiscordId |> checkAlreadyHackedTarget defender.DiscordId
<!> (Player.removeExpiredActions true)
>>= checkHasEmptyHacks >>= checkHasEmptyHacks
>>= checkTargetHasMoney defender >>= checkTargetHasMoney defender
>>= checkPlayerIsAttackingThemselves defender >>= checkPlayerIsAttackingThemselves defender
|> function |> function
| Ok _ -> | Ok atkr ->
let embed = Embeds.pickHack "Attack" attacker defender false let embed = Embeds.pickHack "Attack" atkr defender false
ctx.FollowUpAsync(embed) ctx.FollowUpAsync(embed)
|> Async.AwaitTask |> Async.AwaitTask
|> Async.Ignore |> Async.Ignore
@ -134,30 +149,32 @@ let attack (target : DiscordUser) (ctx : InteractionContext) =
let handleAttack (event : ComponentInteractionCreateEventArgs) = let handleAttack (event : ComponentInteractionCreateEventArgs) =
Game.executePlayerEvent event (fun attacker -> async { Game.executePlayerEvent event (fun attacker -> async {
let split = event.Id.Split("-") let split = event.Id.Split("-")
let hack = enum<HackId>(int split.[1]) let hackId = int split.[1]
let hack = enum<HackId>(hackId)
let ( resultId , targetId ) = UInt64.TryParse split.[2] let ( resultId , targetId ) = UInt64.TryParse split.[2]
let! resultTarget = DbService.tryFindPlayer targetId let! resultTarget = DbService.tryFindPlayer targetId
match resultTarget , true , resultId with match resultTarget , true , resultId with
| Some defender , true , true -> | Some defender , true , true ->
do! attacker do! attacker
|> Player.removeExpiredActions |> Player.removeExpiredActions false
|> checkAlreadyHackedTarget defender.DiscordId |> checkAlreadyHackedTarget defender.DiscordId
>>= (checkHackHasCooldown (int hack)) >>= checkPlayerOwnsWeapon hackId
>>= checkItemHasCooldown hackId
|> function |> function
| Ok _ -> | Ok atkr ->
runHackerBattle defender (Armory.getItem (int hack)) runHackerBattle defender (Armory.getItem (int hackId))
|> function |> function
| false -> successfulHack event attacker defender hack | false -> successfulHack event atkr defender hackId
| true -> failedHack event attacker defender hack | true -> failedHack event attacker defender hackId
| Error msg -> Messaging.sendFollowUpMessage event msg | Error msg -> Messaging.sendFollowUpMessage event msg
| _ -> do! Messaging.sendFollowUpMessage event "Error occurred processing attack" | _ -> do! Messaging.sendFollowUpMessage event "Error occurred processing attack"
}) })
let defend (ctx : InteractionContext) = let defend (ctx : InteractionContext) =
Game.executePlayerInteraction ctx (fun player -> async { Game.executePlayerInteraction ctx (fun player -> async {
if Player.shields player |> Array.length > 0 then if Player.getShields player |> Array.length > 0 then
let p = Player.removeExpiredActions player let p = Player.removeExpiredActions false player
let embed = Embeds.pickDefense "Defend" p false let embed = Embeds.pickDefense "Defend" p false
do! ctx.FollowUpAsync(embed) do! ctx.FollowUpAsync(embed)
|> Async.AwaitTask |> Async.AwaitTask
@ -172,11 +189,12 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) =
let split = event.Id.Split("-") let split = event.Id.Split("-")
let shieldId = int split.[1] let shieldId = int split.[1]
let shield = Armory.getItem shieldId let shield = Armory.getItem shieldId
let updatedDefenses = player |> Player.removeExpiredActions |> Player.defenses
let alreadyUsedShield = updatedDefenses |> Array.exists (fun d -> d.ActionId = shieldId)
match alreadyUsedShield , updatedDefenses.Length < 2 with do! player
| false , true -> |> checkPlayerOwnsWeapon shieldId
>>= checkPlayerHasShieldSlotsAvailable shield
>>= checkItemHasCooldown shieldId
|> handleResultWithResponseFromEvent event (fun p -> async {
let embed = Embeds.responseCreatedShield (Armory.getItem shieldId) let embed = Embeds.responseCreatedShield (Armory.getItem shieldId)
do! event.Interaction.CreateFollowupMessageAsync(embed) do! event.Interaction.CreateFollowupMessageAsync(embed)
|> Async.AwaitTask |> Async.AwaitTask
@ -189,16 +207,7 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) =
do! channel.SendMessageAsync(builder) do! channel.SendMessageAsync(builder)
|> Async.AwaitTask |> Async.AwaitTask
|> Async.Ignore |> Async.Ignore
| _ , false -> })
let timestamp = updatedDefenses |> Array.rev |> Array.head |> fun a -> a.Timestamp // This should be the next expiring timestamp
let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromMinutes(int shield.Cooldown)) timestamp
do! sendFollowUpMessage event $"You are only allowed two shields at a time. Wait {cooldown} to add another shield"
do! DbService.updatePlayer <| { player with Actions = updatedDefenses }
| true , _ ->
let timestamp = updatedDefenses |> Array.find (fun d -> d.ActionId = int shieldId) |> fun a -> a.Timestamp
let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromMinutes(int shield.Cooldown)) timestamp
do! sendFollowUpMessage event $"{shield.Name} shield is already in use. Wait {cooldown} to use this shield again"
do! DbService.updatePlayer <| { player with Actions = updatedDefenses }
}) })
let handleButtonEvent (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) = let handleButtonEvent (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) =

View File

@ -9,16 +9,6 @@ open Degenz
open Degenz.Embeds open Degenz.Embeds
open Degenz.Messaging open Degenz.Messaging
let handleResultWithResponse ctx fn (player : Result<PlayerData, string>) =
match player with
| Ok p -> fn p
| Error e -> async { do! sendFollowUpMessageFromCtx ctx e }
let handleResultWithResponseFromEvent event fn (player : Result<PlayerData, string>) =
match player with
| Ok p -> fn p
| Error e -> async { do! sendFollowUpMessage event e }
let checkHasSufficientFunds (item : BattleItem) player = let checkHasSufficientFunds (item : BattleItem) player =
if player.Bank - item.Cost >= 0<GBT> if player.Bank - item.Cost >= 0<GBT>
then Ok player then Ok player
@ -39,15 +29,37 @@ let checkHasItemsInArsenal itemType player =
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!"
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) =
match actions with
| [||] -> "None"
| _ ->
actions
|> Array.map (fun act ->
let item = Armory.getItem act.ActionId
match act.Type with
| Attack atk ->
let cooldown = getTimeText false Game.SameTargetAttackCooldown act.Timestamp
$"Hacked {atk.Target.Name} {cooldown} ago"
| Defense ->
let cooldown = getTimeText true (System.TimeSpan.FromMinutes(int item.Cooldown)) act.Timestamp
$"{item.Name} Shield active for {cooldown}")
|> String.concat "\n"
let statusFormat p = let statusFormat p =
$"**Hacks:** {Player.hacks p |> battleItemFormat}\n $"**Hacks:** {Player.hacks p |> battleItemFormat}\n
**Shields:** {Player.shields p |> battleItemFormat}\n **Shields:** {Player.getShields p |> battleItemFormat}\n
**Hack Attacks:**\n{Player.attacks p |> actionFormat}\n **Hack Attacks:**\n{Player.attacks p |> actionFormat}\n
**Active Shields:**\n{Player.defenses p |> actionFormat}" **Active Shields:**\n{Player.defenses p |> actionFormat}"
// TODO: There's a 1000 character limit for embeds, so you need to filter by N last actions
let arsenal (ctx : InteractionContext) = let arsenal (ctx : InteractionContext) =
Game.executePlayerInteraction ctx (fun player -> async { Game.executePlayerInteraction ctx (fun player -> async {
let updatedPlayer = Player.removeExpiredActions player let updatedPlayer = Player.removeExpiredActions false player
let builder = DiscordFollowupMessageBuilder() let builder = DiscordFollowupMessageBuilder()
let embed = DiscordEmbedBuilder() let embed = DiscordEmbedBuilder()
embed.AddField("Arsenal", statusFormat updatedPlayer) |> ignore embed.AddField("Arsenal", statusFormat updatedPlayer) |> ignore

View File

@ -1,7 +1,6 @@
module Degenz.Trainer module Degenz.Trainer
open DSharpPlus open DSharpPlus
open System.Threading.Tasks
open DSharpPlus.Entities open DSharpPlus.Entities
open DSharpPlus.EventArgs open DSharpPlus.EventArgs
open DSharpPlus.SlashCommands open DSharpPlus.SlashCommands
@ -34,10 +33,10 @@ let sendInitialEmbed (client : DiscordClient) =
let handleTrainerStep1 (event : ComponentInteractionCreateEventArgs) = let handleTrainerStep1 (event : ComponentInteractionCreateEventArgs) =
Game.executePlayerEvent event (fun player -> async { Game.executePlayerEvent event (fun player -> async {
let shieldMessage , weaponName = let shieldMessage , weaponName =
if Player.shields player |> Array.isEmpty if Player.getShields player |> Array.isEmpty
then $"You do not have any Shields in your arsenal, take this {defaultShield.Name}, you can use it for now.\n\n" , defaultShield.Name then $"You do not have any Shields in your arsenal, take this {defaultShield.Name}, you can use it for now.\n\n" , defaultShield.Name
else else
let name = Player.shields player |> Array.tryHead |> Option.defaultValue defaultShield |> fun w -> w.Name let name = Player.getShields player |> Array.tryHead |> Option.defaultValue defaultShield |> fun w -> w.Name
$"Looks like you have `{name}` in your arsenal… 👀\n\n" , name $"Looks like you have `{name}` in your arsenal… 👀\n\n" , name
let membr = event.User :?> DiscordMember let membr = event.User :?> DiscordMember
@ -56,7 +55,7 @@ let handleTrainerStep1 (event : ComponentInteractionCreateEventArgs) =
let defend (ctx : InteractionContext) = let defend (ctx : InteractionContext) =
Game.executePlayerInteraction ctx (fun player -> async { Game.executePlayerInteraction ctx (fun player -> async {
let playerWithShields = let playerWithShields =
match Player.shields player with match Player.getShields player with
| [||] -> { player with Arsenal = [| defaultShield |] } | [||] -> { player with Arsenal = [| defaultShield |] }
| _ -> player | _ -> player

View File

@ -10,6 +10,7 @@ open Newtonsoft.Json
[<Microsoft.FSharp.Core.AutoOpen>] [<Microsoft.FSharp.Core.AutoOpen>]
module ResultHelpers = module ResultHelpers =
let (>>=) x f = Result.bind f x let (>>=) x f = Result.bind f x
let (<!>) x f = Result.map f x
[<Microsoft.FSharp.Core.AutoOpen>] [<Microsoft.FSharp.Core.AutoOpen>]
module Types = module Types =
@ -94,32 +95,23 @@ module Messaging =
Message : string Message : string
} }
let getTimeTillCooldownFinishes (timespan : TimeSpan) timestamp = let getTimeText isCooldown (timespan : TimeSpan) timestamp =
let remaining = timespan - (DateTime.UtcNow - timestamp) let span =
if isCooldown
then timespan - (DateTime.UtcNow - timestamp)
else (DateTime.UtcNow - timestamp)
let plural amount = if amount = 1 then "" else "s" let plural amount = if amount = 1 then "" else "s"
let ``and`` = if remaining.Hours > 0 then "and " else "" let ``and`` = if span.Hours > 0 then "and " else ""
let hours = if remaining.Hours > 0 then $"{remaining.Hours} hour{plural remaining.Hours} {``and``}" else String.Empty let hours = if span.Hours > 0 then $"{span.Hours} hour{plural span.Hours} {``and``}" else String.Empty
let totalMins = remaining.Minutes let totalMins = span.Minutes
let minutes = if totalMins > 0 then $"{totalMins} minute{plural totalMins}" else "1 minute" let minutes = if totalMins > 0 then $"{totalMins} minute{plural totalMins}" else "1 minute"
$"{hours}{minutes}" $"{hours}{minutes}"
let battleItemFormat (items : BattleItem array) = let getShortTimeText (timespan : TimeSpan) timestamp =
match items with let remaining = timespan - (DateTime.UtcNow - timestamp)
| [||] -> "None" let hours = if remaining.Hours > 0 then $"{remaining.Hours}h " else String.Empty
| _ -> items |> Array.toList |> List.map (fun i -> i.Name) |> String.concat ", " let minutesRemaining = if remaining.Hours = 0 then remaining.Minutes + 1 else remaining.Minutes
$"{hours}{minutesRemaining}min"
let actionFormat (actions : Action array) =
match actions with
| [||] -> "None"
| _ ->
actions
|> Array.map (fun act ->
let item = Armory.getItem act.ActionId
let cooldown = getTimeTillCooldownFinishes (System.TimeSpan.FromMinutes(int item.Cooldown)) act.Timestamp
match act.Type with
| Attack atk -> $"Hacked {atk.Target.Name} {cooldown} ago"
| Defense -> $"{item.Name} Shield active for {cooldown}")
|> String.concat "\n"
let sendSimpleResponse (ctx: InteractionContext) msg = let sendSimpleResponse (ctx: InteractionContext) msg =
async { async {
@ -193,3 +185,13 @@ module Messaging =
do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
} }
let handleResultWithResponse ctx fn (player : Result<PlayerData, string>) =
match player with
| Ok p -> fn p
| Error e -> async { do! sendFollowUpMessageFromCtx ctx e }
let handleResultWithResponseFromEvent event fn (player : Result<PlayerData, string>) =
match player with
| Ok p -> fn p
| Error e -> async { do! sendFollowUpMessage event e }