Cooldowns done right. Fix shield sell exploit
This commit is contained in:
parent
8dbb5e1145
commit
a785ba3120
@ -1,7 +1,6 @@
|
||||
module Degenz.Embeds
|
||||
|
||||
open System
|
||||
open DSharpPlus
|
||||
open DSharpPlus.EventArgs
|
||||
open Degenz.Types
|
||||
open DSharpPlus.Entities
|
||||
@ -33,18 +32,24 @@ let getShieldGif = function
|
||||
| ShieldId.Cypher -> "https://s10.gifyu.com/images/Cypher-Smaller.gif"
|
||||
| _ -> shieldGif
|
||||
|
||||
let constructButtons (actionType: string) (player: PlayerData) (buttonId : string) (items: BattleItem array) isTrainer =
|
||||
items
|
||||
let constructButtons (actionId: string) (buttonInfo : string) (player: PlayerData) itemType isTrainer =
|
||||
player
|
||||
|> Player.getItems itemType
|
||||
|> Array.map (fun item ->
|
||||
// match player.Actions |> Array.exists (fun i -> i.ActionId = item.Id) && not isTrainer with
|
||||
match false with
|
||||
| true -> DiscordButtonComponent(Game.getClassButtonColor item.Class, $"{actionType}-{item.Id}", $"{item.Name} (on cooldown)", true)
|
||||
| false -> DiscordButtonComponent(Game.getClassButtonColor item.Class, $"{actionType}-{item.Id}-{buttonId}", $"{item.Name}"))
|
||||
let action =
|
||||
player.Actions
|
||||
|> Array.tryFind (fun i -> i.ActionId = item.Id)
|
||||
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 buttons =
|
||||
constructButtons actionId player (string player.DiscordId) (Player.shields player) isTrainer
|
||||
|> Seq.cast<DiscordComponent>
|
||||
let buttons = constructButtons actionId (string player.DiscordId) player ItemType.Shield isTrainer
|
||||
|
||||
let embed =
|
||||
DiscordEmbedBuilder()
|
||||
@ -58,10 +63,7 @@ let pickDefense actionId player isTrainer =
|
||||
.AsEphemeral(true)
|
||||
|
||||
let pickHack actionId attacker defender isTrainer =
|
||||
let buttons =
|
||||
let hacks = Player.hacks attacker
|
||||
constructButtons actionId attacker $"{defender.DiscordId}-{defender.Name}" hacks isTrainer
|
||||
|> Seq.cast<DiscordComponent>
|
||||
let buttons = constructButtons actionId $"{defender.DiscordId}-{defender.Name}" attacker ItemType.Hack isTrainer
|
||||
|
||||
let embed =
|
||||
DiscordEmbedBuilder()
|
||||
|
18
Bot/Game.fs
18
Bot/Game.fs
@ -53,23 +53,23 @@ module Game =
|
||||
:> Task
|
||||
|
||||
module Player =
|
||||
let hacks (player : PlayerData) = player.Arsenal |> Array.filter (fun bi -> bi.Type = Hack)
|
||||
let shields (player : PlayerData) = player.Arsenal |> Array.filter (fun bi -> bi.Type = Shield)
|
||||
let getItems itemType (player : PlayerData) = player.Arsenal |> Array.filter (fun i -> i.Type = itemType)
|
||||
let hacks (player : PlayerData) = getItems ItemType.Hack player
|
||||
let getShields (player : PlayerData) = getItems ItemType.Shield player
|
||||
let attacks player =
|
||||
player.Actions
|
||||
|> 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 removeExpiredActions player =
|
||||
let removeExpiredActions filterByAttackCooldown player =
|
||||
let actions =
|
||||
player.Actions
|
||||
|> 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
|
||||
System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(int item.Cooldown))
|
||||
let item = Armory.getItem act.ActionId
|
||||
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 }
|
||||
|
||||
let modifyBank (player : PlayerData) amount = { player with Bank = max (player.Bank + amount) 0<GBT> }
|
||||
|
@ -22,23 +22,23 @@ let checkAlreadyHackedTarget defenderId attacker =
|
||||
|> Array.tryFind (fun (_,t,_) -> t.Id = defenderId)
|
||||
|> function
|
||||
| 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}."
|
||||
| None -> Ok attacker
|
||||
|
||||
let checkHackHasCooldown hackId attacker =
|
||||
let mostRecentHackAttack =
|
||||
let checkItemHasCooldown itemId attacker =
|
||||
let cooldown =
|
||||
attacker.Actions
|
||||
|> Array.tryFind (fun a -> a.ActionId = hackId)
|
||||
|> Array.tryFind (fun a -> a.ActionId = itemId)
|
||||
|> function
|
||||
| Some a -> a.Timestamp
|
||||
| None -> DateTime.MinValue
|
||||
let item = Armory.getItem hackId
|
||||
if DateTime.UtcNow - mostRecentHackAttack > TimeSpan.FromMinutes(int item.Cooldown) then
|
||||
let item = Armory.getItem itemId
|
||||
if DateTime.UtcNow - cooldown > TimeSpan.FromMinutes(int item.Cooldown) then
|
||||
Ok attacker
|
||||
else
|
||||
let cooldown = getTimeTillCooldownFinishes (TimeSpan.FromMinutes(int item.Cooldown)) mostRecentHackAttack
|
||||
let item = Armory.battleItems |> Array.find (fun i -> i.Id = hackId)
|
||||
let cooldown = getTimeText true (TimeSpan.FromMinutes(int item.Cooldown)) cooldown
|
||||
let item = Armory.battleItems |> Array.find (fun i -> i.Id = itemId)
|
||||
Error $"{item.Name} is currently on cooldown, wait {cooldown} to use it again."
|
||||
|
||||
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."
|
||||
| _ -> 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 =
|
||||
if target.Bank < Game.HackPrize
|
||||
then Error $"{target.Name} does not have enough 💰$GBT to steal from, the broke loser. Pick a different target."
|
||||
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) =
|
||||
if hack.Class = shield.Class
|
||||
then Weak
|
||||
@ -58,7 +73,7 @@ let calculateDamage (hack : BattleItem) (shield : BattleItem) =
|
||||
|
||||
let runHackerBattle defender hack =
|
||||
defender
|
||||
|> Player.removeExpiredActions
|
||||
|> Player.removeExpiredActions false
|
||||
|> Player.defenses
|
||||
|> Array.map (fun dfn -> Armory.battleItems |> Array.find (fun w -> w.Id = dfn.ActionId))
|
||||
|> Array.map (calculateDamage (hack))
|
||||
@ -79,7 +94,7 @@ let successfulHack (event : ComponentInteractionCreateEventArgs) attacker defend
|
||||
async {
|
||||
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)
|
||||
|> Async.AwaitTask
|
||||
|> Async.Ignore
|
||||
@ -112,14 +127,14 @@ let attack (target : DiscordUser) (ctx : InteractionContext) =
|
||||
match defender with
|
||||
| Some defender ->
|
||||
do! attacker
|
||||
|> Player.removeExpiredActions
|
||||
|> checkAlreadyHackedTarget defender.DiscordId
|
||||
<!> (Player.removeExpiredActions true)
|
||||
>>= checkHasEmptyHacks
|
||||
>>= checkTargetHasMoney defender
|
||||
>>= checkPlayerIsAttackingThemselves defender
|
||||
|> function
|
||||
| Ok _ ->
|
||||
let embed = Embeds.pickHack "Attack" attacker defender false
|
||||
| Ok atkr ->
|
||||
let embed = Embeds.pickHack "Attack" atkr defender false
|
||||
ctx.FollowUpAsync(embed)
|
||||
|> Async.AwaitTask
|
||||
|> Async.Ignore
|
||||
@ -134,30 +149,32 @@ let attack (target : DiscordUser) (ctx : InteractionContext) =
|
||||
let handleAttack (event : ComponentInteractionCreateEventArgs) =
|
||||
Game.executePlayerEvent event (fun attacker -> async {
|
||||
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! resultTarget = DbService.tryFindPlayer targetId
|
||||
|
||||
match resultTarget , true , resultId with
|
||||
| Some defender , true , true ->
|
||||
do! attacker
|
||||
|> Player.removeExpiredActions
|
||||
|> Player.removeExpiredActions false
|
||||
|> checkAlreadyHackedTarget defender.DiscordId
|
||||
>>= (checkHackHasCooldown (int hack))
|
||||
>>= checkPlayerOwnsWeapon hackId
|
||||
>>= checkItemHasCooldown hackId
|
||||
|> function
|
||||
| Ok _ ->
|
||||
runHackerBattle defender (Armory.getItem (int hack))
|
||||
| Ok atkr ->
|
||||
runHackerBattle defender (Armory.getItem (int hackId))
|
||||
|> function
|
||||
| false -> successfulHack event attacker defender hack
|
||||
| true -> failedHack event attacker defender hack
|
||||
| false -> successfulHack event atkr defender hackId
|
||||
| true -> failedHack event attacker defender hackId
|
||||
| Error msg -> Messaging.sendFollowUpMessage event msg
|
||||
| _ -> do! Messaging.sendFollowUpMessage event "Error occurred processing attack"
|
||||
})
|
||||
|
||||
let defend (ctx : InteractionContext) =
|
||||
Game.executePlayerInteraction ctx (fun player -> async {
|
||||
if Player.shields player |> Array.length > 0 then
|
||||
let p = Player.removeExpiredActions player
|
||||
if Player.getShields player |> Array.length > 0 then
|
||||
let p = Player.removeExpiredActions false player
|
||||
let embed = Embeds.pickDefense "Defend" p false
|
||||
do! ctx.FollowUpAsync(embed)
|
||||
|> Async.AwaitTask
|
||||
@ -172,33 +189,25 @@ let handleDefense (event : ComponentInteractionCreateEventArgs) =
|
||||
let split = event.Id.Split("-")
|
||||
let shieldId = int split.[1]
|
||||
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
|
||||
| false , true ->
|
||||
let embed = Embeds.responseCreatedShield (Armory.getItem shieldId)
|
||||
do! event.Interaction.CreateFollowupMessageAsync(embed)
|
||||
|> Async.AwaitTask
|
||||
|> Async.Ignore
|
||||
let defense = { ActionId = shieldId ; Type = Defense ; Timestamp = DateTime.UtcNow }
|
||||
do! DbService.updatePlayer <| { player with Actions = Array.append [| defense |] player.Actions }
|
||||
let builder = DiscordMessageBuilder()
|
||||
builder.WithContent($"{event.User.Username} has protected their system!") |> ignore
|
||||
let channel = event.Guild.GetChannel(GuildEnvironment.channelEventsHackerBattle)
|
||||
do! channel.SendMessageAsync(builder)
|
||||
|> Async.AwaitTask
|
||||
|> 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 }
|
||||
do! player
|
||||
|> checkPlayerOwnsWeapon shieldId
|
||||
>>= checkPlayerHasShieldSlotsAvailable shield
|
||||
>>= checkItemHasCooldown shieldId
|
||||
|> handleResultWithResponseFromEvent event (fun p -> async {
|
||||
let embed = Embeds.responseCreatedShield (Armory.getItem shieldId)
|
||||
do! event.Interaction.CreateFollowupMessageAsync(embed)
|
||||
|> Async.AwaitTask
|
||||
|> Async.Ignore
|
||||
let defense = { ActionId = shieldId ; Type = Defense ; Timestamp = DateTime.UtcNow }
|
||||
do! DbService.updatePlayer <| { player with Actions = Array.append [| defense |] player.Actions }
|
||||
let builder = DiscordMessageBuilder()
|
||||
builder.WithContent($"{event.User.Username} has protected their system!") |> ignore
|
||||
let channel = event.Guild.GetChannel(GuildEnvironment.channelEventsHackerBattle)
|
||||
do! channel.SendMessageAsync(builder)
|
||||
|> Async.AwaitTask
|
||||
|> Async.Ignore
|
||||
})
|
||||
})
|
||||
|
||||
let handleButtonEvent (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) =
|
||||
|
36
Bot/Store.fs
36
Bot/Store.fs
@ -9,16 +9,6 @@ open Degenz
|
||||
open Degenz.Embeds
|
||||
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 =
|
||||
if player.Bank - item.Cost >= 0<GBT>
|
||||
then Ok player
|
||||
@ -39,15 +29,37 @@ let checkHasItemsInArsenal itemType player =
|
||||
then Ok player
|
||||
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 =
|
||||
$"**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
|
||||
**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) =
|
||||
Game.executePlayerInteraction ctx (fun player -> async {
|
||||
let updatedPlayer = Player.removeExpiredActions player
|
||||
let updatedPlayer = Player.removeExpiredActions false player
|
||||
let builder = DiscordFollowupMessageBuilder()
|
||||
let embed = DiscordEmbedBuilder()
|
||||
embed.AddField("Arsenal", statusFormat updatedPlayer) |> ignore
|
||||
|
@ -1,7 +1,6 @@
|
||||
module Degenz.Trainer
|
||||
|
||||
open DSharpPlus
|
||||
open System.Threading.Tasks
|
||||
open DSharpPlus.Entities
|
||||
open DSharpPlus.EventArgs
|
||||
open DSharpPlus.SlashCommands
|
||||
@ -34,10 +33,10 @@ let sendInitialEmbed (client : DiscordClient) =
|
||||
let handleTrainerStep1 (event : ComponentInteractionCreateEventArgs) =
|
||||
Game.executePlayerEvent event (fun player -> async {
|
||||
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
|
||||
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
|
||||
|
||||
let membr = event.User :?> DiscordMember
|
||||
@ -56,7 +55,7 @@ let handleTrainerStep1 (event : ComponentInteractionCreateEventArgs) =
|
||||
let defend (ctx : InteractionContext) =
|
||||
Game.executePlayerInteraction ctx (fun player -> async {
|
||||
let playerWithShields =
|
||||
match Player.shields player with
|
||||
match Player.getShields player with
|
||||
| [||] -> { player with Arsenal = [| defaultShield |] }
|
||||
| _ -> player
|
||||
|
||||
|
@ -10,6 +10,7 @@ open Newtonsoft.Json
|
||||
[<Microsoft.FSharp.Core.AutoOpen>]
|
||||
module ResultHelpers =
|
||||
let (>>=) x f = Result.bind f x
|
||||
let (<!>) x f = Result.map f x
|
||||
|
||||
[<Microsoft.FSharp.Core.AutoOpen>]
|
||||
module Types =
|
||||
@ -94,32 +95,23 @@ module Messaging =
|
||||
Message : string
|
||||
}
|
||||
|
||||
let getTimeTillCooldownFinishes (timespan : TimeSpan) timestamp =
|
||||
let remaining = timespan - (DateTime.UtcNow - timestamp)
|
||||
let getTimeText isCooldown (timespan : TimeSpan) timestamp =
|
||||
let span =
|
||||
if isCooldown
|
||||
then timespan - (DateTime.UtcNow - timestamp)
|
||||
else (DateTime.UtcNow - timestamp)
|
||||
let plural amount = if amount = 1 then "" else "s"
|
||||
let ``and`` = if remaining.Hours > 0 then "and " else ""
|
||||
let hours = if remaining.Hours > 0 then $"{remaining.Hours} hour{plural remaining.Hours} {``and``}" else String.Empty
|
||||
let totalMins = remaining.Minutes
|
||||
let ``and`` = if span.Hours > 0 then "and " else ""
|
||||
let hours = if span.Hours > 0 then $"{span.Hours} hour{plural span.Hours} {``and``}" else String.Empty
|
||||
let totalMins = span.Minutes
|
||||
let minutes = if totalMins > 0 then $"{totalMins} minute{plural totalMins}" else "1 minute"
|
||||
$"{hours}{minutes}"
|
||||
|
||||
let 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
|
||||
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 getShortTimeText (timespan : TimeSpan) timestamp =
|
||||
let remaining = timespan - (DateTime.UtcNow - timestamp)
|
||||
let hours = if remaining.Hours > 0 then $"{remaining.Hours}h " else String.Empty
|
||||
let minutesRemaining = if remaining.Hours = 0 then remaining.Minutes + 1 else remaining.Minutes
|
||||
$"{hours}{minutesRemaining}min"
|
||||
|
||||
let sendSimpleResponse (ctx: InteractionContext) msg =
|
||||
async {
|
||||
@ -193,3 +185,13 @@ module Messaging =
|
||||
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 }
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user