Cooldowns done right. Fix shield sell exploit
This commit is contained in:
parent
8dbb5e1145
commit
a785ba3120
@ -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()
|
||||||
|
16
Bot/Game.fs
16
Bot/Game.fs
@ -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> }
|
||||||
|
@ -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) =
|
||||||
|
36
Bot/Store.fs
36
Bot/Store.fs
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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 }
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user