Anti spam cooldown for player and a 12 hour global cooldown for victim

This commit is contained in:
Joseph Ferano 2022-02-13 20:12:04 +07:00
parent 7e2b46a96b
commit 04add71c6e
7 changed files with 137 additions and 74 deletions

View File

@ -41,15 +41,17 @@ module Game =
| None -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one" | None -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
} |> Async.StartAsTask :> Task } |> Async.StartAsTask :> Task
let executePlayerWithTargetAction (targetPlayer : DiscordUser) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async<unit>) = let executePlayerActionWithTarget (targetPlayer : DiscordUser) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async<unit>) =
async { async {
let builder = DiscordInteractionResponseBuilder() let builder = DiscordInteractionResponseBuilder()
builder.IsEphemeral <- true builder.IsEphemeral <- true
builder.Content <- "Content" builder.Content <- "Content"
do! ctx.Respond InteractionResponseType.DeferredChannelMessageWithSource builder |> Async.AwaitTask do! ctx.Respond InteractionResponseType.DeferredChannelMessageWithSource builder |> Async.AwaitTask
let! playerResult = tryFindPlayer (ctx.GetDiscordMember().Id) let! players =
let! targetResult = tryFindPlayer targetPlayer.Id [ tryFindPlayer (ctx.GetDiscordMember().Id)
match playerResult , targetResult with tryFindPlayer targetPlayer.Id ]
|> Async.Parallel
match players.[0] , players.[1] with
| Some player , Some target -> do! dispatch player target | Some player , Some target -> do! dispatch player target
| None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one" | None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
| _ , None -> | _ , None ->
@ -58,6 +60,22 @@ module Game =
else do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command" else do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command"
} |> Async.StartAsTask :> Task } |> Async.StartAsTask :> Task
let executePlayerActionWithTargetId (targetId : uint64) (ctx : IDiscordContext) (dispatch : PlayerData -> PlayerData -> Async<unit>) =
async {
let builder = DiscordInteractionResponseBuilder()
builder.IsEphemeral <- true
builder.Content <- "Content"
do! ctx.Respond InteractionResponseType.DeferredChannelMessageWithSource builder |> Async.AwaitTask
let! players =
[ tryFindPlayer (ctx.GetDiscordMember().Id)
tryFindPlayer targetId ]
|> Async.Parallel
match players.[0] , players.[1] with
| Some player , Some target -> do! dispatch player target
| None , _ -> do! Messaging.sendFollowUpMessage ctx "You are currently not a hacker, first use the /redpill command to become one"
| _ , None -> do! Messaging.sendFollowUpMessage ctx "Your target is not connected to the network, they must join first by using the /redpill command"
} |> Async.StartAsTask :> Task
module Player = module Player =
let getItems itemType (player : PlayerData) = player.Arsenal |> Array.filter (fun i -> i.Type = itemType) let getItems itemType (player : PlayerData) = player.Arsenal |> Array.filter (fun i -> i.Type = itemType)
let getHacks (player : PlayerData) = getItems ItemType.Hack player let getHacks (player : PlayerData) = getItems ItemType.Hack player

View File

@ -9,8 +9,6 @@ open DSharpPlus.SlashCommands
open Degenz open Degenz
open Degenz.Messaging open Degenz.Messaging
// TODO: Do not allow any attacks until the user has completed training
// TODO: Introduce second round of weapons, more expensive and with better stats
let checkPlayerIsAttackingThemselves defender attacker = let checkPlayerIsAttackingThemselves defender attacker =
match attacker.DiscordId = defender.DiscordId with match attacker.DiscordId = defender.DiscordId with
| true -> Error "You think you're clever? You can't hack yourself, pal." | true -> Error "You think you're clever? You can't hack yourself, pal."
@ -120,7 +118,7 @@ let failedHack (ctx : IDiscordContext) attacker defender hack =
} }
let attack (target : DiscordUser) (ctx : IDiscordContext) = let attack (target : DiscordUser) (ctx : IDiscordContext) =
Game.executePlayerWithTargetAction target ctx (fun attacker defender -> async { Game.executePlayerActionWithTarget target ctx (fun attacker defender -> async {
do! attacker do! attacker
|> checkAlreadyHackedTarget defender.DiscordId |> checkAlreadyHackedTarget defender.DiscordId
<!> (Player.removeExpiredActions true) <!> (Player.removeExpiredActions true)
@ -180,7 +178,7 @@ let handleDefense (ctx : IDiscordContext) =
|> checkPlayerOwnsWeapon shieldId |> checkPlayerOwnsWeapon shieldId
>>= checkPlayerHasShieldSlotsAvailable shield >>= checkPlayerHasShieldSlotsAvailable shield
>>= checkItemHasCooldown shieldId >>= checkItemHasCooldown shieldId
|> handleResultWithResponseFromEvent ctx (fun p -> async { |> handleResultWithResponse ctx (fun p -> async {
let embed = Embeds.responseCreatedShield (Armory.getItem shieldId) let embed = Embeds.responseCreatedShield (Armory.getItem shieldId)
do! ctx.FollowUp embed |> Async.AwaitTask do! ctx.FollowUp embed |> Async.AwaitTask
let defense = { ActionId = shieldId ; Type = Defense ; Timestamp = DateTime.UtcNow } let defense = { ActionId = shieldId ; Type = Defense ; Timestamp = DateTime.UtcNow }

View File

@ -28,7 +28,7 @@ let player1Won p1m p2m =
| _ , _ -> Draw | _ , _ -> Draw
let playRPS target ctx = let playRPS target ctx =
Game.executePlayerWithTargetAction target ctx (fun attacker defender -> async { Game.executePlayerActionWithTarget target ctx (fun attacker defender -> async {
return () return ()
}) })

View File

@ -49,7 +49,7 @@ let handleBuyItem (ctx : IDiscordContext) itemId =
do! player do! player
|> checkHasSufficientFunds item |> checkHasSufficientFunds item
>>= checkAlreadyOwnsItem item >>= checkAlreadyOwnsItem item
|> handleResultWithResponseFromEvent ctx (fun player -> async { |> handleResultWithResponse ctx (fun player -> async {
let newBalance = player.Bank - item.Cost let newBalance = player.Bank - item.Cost
let p = { player with Bank = newBalance ; Arsenal = Array.append [| item |] player.Arsenal } let p = { player with Bank = newBalance ; Arsenal = Array.append [| item |] player.Arsenal }
do! DbService.updatePlayer p do! DbService.updatePlayer p
@ -62,7 +62,7 @@ let handleSell (ctx : IDiscordContext) itemId =
let item = Armory.getItem itemId let item = Armory.getItem itemId
do! player do! player
|> checkSoldItemAlready item |> checkSoldItemAlready item
|> handleResultWithResponseFromEvent ctx (fun player -> async { |> handleResultWithResponse ctx (fun player -> async {
let updatedPlayer = { let updatedPlayer = {
player with player with
Bank = player.Bank + item.Cost Bank = player.Bank + item.Cost

View File

@ -7,6 +7,14 @@ open DSharpPlus.Entities
open DSharpPlus.SlashCommands open DSharpPlus.SlashCommands
open Degenz.Messaging open Degenz.Messaging
[<Literal>]
let StealActionId = 12
[<Literal>]
let VictimDefenseActionId = 12
let ThiefCooldown = TimeSpan.FromMinutes(1)
let VictimRecovery = TimeSpan.FromHours(12)
type StealResult = type StealResult =
| Success | Success
| WentToPrison | WentToPrison
@ -78,8 +86,38 @@ let getResultEmbed targetName result =
.WithImageUrl(img) .WithImageUrl(img)
.WithTitle($"Robbery Results") .WithTitle($"Robbery Results")
let checkVictimStealingCooldown defender attacker =
defender
|> Player.getDefenses
|> Array.tryFind (fun act -> act.ActionId = VictimDefenseActionId)
|> function
| Some act ->
let cooldown = VictimRecovery - (DateTime.UtcNow - act.Timestamp)
let hours = if cooldown.Hours = 0 then "hour" else $"{cooldown.Hours} hours"
Error $"{defender.Name} was robbed recently so they won't be going out for at least another {hours}."
| None -> Ok attacker
// TODO: Look for ways to generalize checking for action cooldowns
let checkThiefCooldown attacker =
attacker
|> Player.getAttacks
|> Array.tryFind (fun act -> act.ActionId = StealActionId)
|> function
| Some act ->
if ThiefCooldown > (DateTime.UtcNow - act.Timestamp) then
let cooldown = ThiefCooldown - (DateTime.UtcNow - act.Timestamp)
let minutes = if cooldown.Minutes = 0 then "minute" else $"{cooldown.Minutes} minutes"
Error $"Whoa there you clepto, wait at least another {minutes} before you try stealing again."
else
Ok attacker
| None -> Ok attacker
let steal target (ctx : IDiscordContext) = let steal target (ctx : IDiscordContext) =
Game.executePlayerWithTargetAction target ctx (fun attacker defender -> async { Game.executePlayerActionWithTarget target ctx (fun attacker defender -> async {
do! attacker
|> checkVictimStealingCooldown defender
>>= checkThiefCooldown
|> handleResultWithResponse ctx (fun player -> async {
let ``base`` = 0.5 let ``base`` = 0.5
let winPercentage = double (attacker.Stats.Strength - defender.Stats.Strength) * 0.45 + ``base`` let winPercentage = double (attacker.Stats.Strength - defender.Stats.Strength) * 0.45 + ``base``
let prize = payout (float defender.Bank) winPercentage let prize = payout (float defender.Bank) winPercentage
@ -87,12 +125,13 @@ let steal target (ctx : IDiscordContext) =
do! ctx.FollowUp(embed) |> Async.AwaitTask do! ctx.FollowUp(embed) |> Async.AwaitTask
}) })
})
let handleSteal (ctx : IDiscordContext) = let handleSteal (ctx : IDiscordContext) =
let split = ctx.GetInteractionId().Split("-") let split = ctx.GetInteractionId().Split("-")
let answer = split.[1] let answer = split.[1]
if answer = "yes" then
Game.executePlayerAction ctx (fun player -> async { let handleYes player = async {
let targetId = uint64 split.[2] let targetId = uint64 split.[2]
let targetName = split.[3] let targetName = split.[3]
let chance = double split.[4] let chance = double split.[4]
@ -109,9 +148,13 @@ let handleSteal (ctx : IDiscordContext) =
embed.AddField("XP Gained", $"{xp}+") |> ignore embed.AddField("XP Gained", $"{xp}+") |> ignore
do! Messaging.sendFollowUpEmbed ctx (embed.Build()) do! Messaging.sendFollowUpEmbed ctx (embed.Build())
match! DbService.tryFindPlayer targetId with match! DbService.tryFindPlayer targetId with
| Some t -> do! DbService.updatePlayer { t with Bank = max (t.Bank - prize) 0<GBT> } | Some t ->
let action = { ActionId = VictimDefenseActionId ; Type = Defense ; Timestamp = DateTime.UtcNow }
do! DbService.updatePlayer { t with Bank = max (t.Bank - prize) 0<GBT> ; Actions = Array.append [| action |] t.Actions }
| None -> () | None -> ()
do! DbService.updatePlayer { player with Bank = player.Bank + prize ; XP = player.XP + xp } let dp = { DiscordPlayer.Id = targetId ; DiscordPlayer.Name = targetName }
let action = { ActionId = StealActionId ; Type = Attack { AttackResult.Result = true ; AttackResult.Target = dp } ; Timestamp = DateTime.UtcNow }
do! DbService.updatePlayer { player with Bank = player.Bank + prize ; XP = player.XP + xp ; Actions = Array.append [| action |] player.Actions }
| false , false -> | false , false ->
let embed = getResultEmbed targetName TargetRanAway let embed = getResultEmbed targetName TargetRanAway
do! Messaging.sendFollowUpEmbed ctx (embed.Build()) do! Messaging.sendFollowUpEmbed ctx (embed.Build())
@ -121,6 +164,14 @@ let handleSteal (ctx : IDiscordContext) =
do! Async.Sleep 5000 do! Async.Sleep 5000
let role = ctx.GetGuild().GetRole(GuildEnvironment.rolePrisoner) let role = ctx.GetGuild().GetRole(GuildEnvironment.rolePrisoner)
do! ctx.GetDiscordMember().GrantRoleAsync(role) |> Async.AwaitTask do! ctx.GetDiscordMember().GrantRoleAsync(role) |> Async.AwaitTask
}
if answer = "yes" then
let targetId = uint64 split.[2]
Game.executePlayerActionWithTargetId targetId ctx (fun attacker defender -> async {
do! attacker
|> checkVictimStealingCooldown defender
>>= checkThiefCooldown
|> handleResultWithResponse ctx handleYes
}) })
else else
async { async {

View File

@ -169,7 +169,8 @@ let handleAttack (ctx : IDiscordContext) =
sb.AppendLine("To finish your training and collect the loot, type the `/arsenal` command **NOW**") |> ignore sb.AppendLine("To finish your training and collect the loot, type the `/arsenal` command **NOW**") |> ignore
do! Async.Sleep 1000 do! Async.Sleep 1000
let updatedPlayer = { let updatedPlayer = {
player with Bank = player.Bank + hackMoney + shieldMoney player with
Bank = player.Bank + hackMoney + shieldMoney
Actions = [ Actions = [
{ Action.Timestamp = System.DateTime.UtcNow { Action.Timestamp = System.DateTime.UtcNow
Action.Type = Action.Type =

View File

@ -228,8 +228,3 @@ module Messaging =
| Ok p -> fn p | Ok p -> fn p
| Error e -> async { do! sendFollowUpMessage ctx e } | Error e -> async { do! sendFollowUpMessage ctx e }
let handleResultWithResponseFromEvent event fn (player : Result<PlayerData, string>) =
match player with
| Ok p -> fn p
| Error e -> async { do! sendFollowUpMessage event e }