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