Steal embed improvements, bug fixes, more fields for Events

This commit is contained in:
Joseph Ferano 2022-02-18 00:37:25 +07:00
parent 63e3853788
commit f7391682fd
7 changed files with 118 additions and 106 deletions

View File

@ -44,7 +44,7 @@ let storeCommands = storeBot.UseSlashCommands()
hackerCommands.RegisterCommands<HackerGame>(guild);
hackerCommands.RegisterCommands<StealGame>(guild);
hackerCommands.RegisterCommands<RPSGame>(guild);
//hackerCommands.RegisterCommands<RPSGame>(guild);
storeCommands.RegisterCommands<Store>(guild);
//sc3.RegisterCommands<SlotMachine>(guild);

View File

@ -165,7 +165,7 @@ let getArsenalEmbed (player : PlayerData) =
DiscordEmbedBuilder()
.AddField( "Arsenal", Arsenal.statusFormat player ))
let getAchievementEmbed description achievement =
let getAchievementEmbed rewards description achievement =
let embed = DiscordEmbedBuilder()
GuildEnvironment.botUserHackerBattle
@ -176,12 +176,12 @@ let getAchievementEmbed description achievement =
DiscordFollowupMessageBuilder()
.AddEmbed(
// TODO: We can add a Reward field but we'd need to keep track of what the player was awarded
embed.WithTitle("Achievement Unlocked!")
.WithDescription(description)
.WithColor(DiscordColor.Gold)
// .AddField("Achievement", $"🏆 {achievement}")
.AddField("Achievement", $"{achievement}")
.AddField("Achievement", $"{achievement}", true)
.AddField("Rewards", rewards |> String.concat "\n", true)
// TODO: Once we add another achievement, fix this
.WithImageUrl("https://s10.gifyu.com/images/MasterTraining_Degenz.gif"))
.AsEphemeral(true)

View File

@ -73,26 +73,12 @@ module Player =
player.Events
|> Array.filter (fun act -> match act.Type with PlayerEventType.Shielding -> true | _ -> false && act.ItemId < 12)
// TODO: This parameter is a result of putting the cooldown on the attack side. Put the cooldown on the defender
// side and only check if it's the same target, we need to refactor Actions
let removeExpiredActions filterByAttackCooldown player =
let removeExpiredActions player =
let actions =
player.Events
|> Array.filter (fun (act : PlayerEvent) ->
let itemCooldown =
if act.ItemId > 0 && act.ItemId < 12 then
(Armory.getItem act.ItemId).Cooldown
else
match act.Type with
| PlayerEventType.Steal -> 1<mins>
| _ -> 720<mins>
|> int
match act.Type , filterByAttackCooldown with
| PlayerEventType.Hacking , true -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(itemCooldown)
| PlayerEventType.Hacking , false -> System.DateTime.UtcNow - act.Timestamp < Game.SameTargetAttackCooldown
| PlayerEventType.Shielding , _ -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(itemCooldown)
| _ -> System.DateTime.UtcNow - act.Timestamp < System.TimeSpan.FromMinutes(itemCooldown))
let cooldown = System.TimeSpan.FromMinutes(int act.Cooldown)
System.DateTime.UtcNow - act.Timestamp < cooldown)
{ player with Events = actions }
let modifyBank (player : PlayerData) amount = { player with Bank = max (player.Bank + amount) 0<GBT> }

View File

@ -14,37 +14,30 @@ let checkPlayerIsAttackingThemselves defender attacker =
| true -> Error "You think you're clever? You can't hack yourself, pal."
| false -> Ok attacker
let checkAlreadyHackedTarget defenderId attacker =
attacker.Events
|> Array.tryFind (fun pe -> pe.Adversary.Id = defenderId)
let checkAlreadyHackedTarget defender attacker =
defender.Events
|> Array.tryFind (fun event -> event.Adversary.Id = attacker.DiscordId && event.IsInstigator = false)
|> function
| Some event ->
let cooldown = getTimeText true Game.SameTargetAttackCooldown event.Timestamp
Error $"You can only hack the same target once every {Game.SameTargetAttackCooldown.Hours} hours, wait {cooldown} to attempt another hack on {event.Adversary.Name}."
Error $"You can only hack the same target once every {Game.SameTargetAttackCooldown.Hours} hours, wait {cooldown} to attempt another hack on <@{defender.DiscordId}>."
| None -> Ok attacker
let checkItemHasCooldown itemId attacker =
let cooldown =
attacker.Events
|> Array.tryFind (fun a -> a.ItemId = itemId)
|> function
| Some a -> a.Timestamp
| None -> DateTime.MinValue
let item = Armory.getItem itemId
if DateTime.UtcNow - cooldown > TimeSpan.FromMinutes(int item.Cooldown) then
Ok attacker
else
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 checkWeaponHasCooldown (weapon : Item) attacker =
let cooldown = attacker.Events |> Array.tryFind (fun a -> a.ItemId = weapon.Id)
match cooldown with
| Some event ->
let cooldown = getTimeText true (TimeSpan.FromMinutes(int event.Cooldown)) event.Timestamp
Error $"{weapon.Name} is still active, it will expire in {cooldown}."
| None -> Ok attacker
let checkHasEmptyHacks attacker =
match Player.getHacks attacker with
| [||] -> 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.Inventory |> Array.exists (fun i -> i.Id = itemId) with
let checkPlayerOwnsWeapon (item : Item) player =
match player.Inventory |> Array.exists (fun i -> i.Id = item.Id) with
| true -> Ok player
| false -> Error $"You sold your weapon already, you cheeky bastard..."
@ -53,8 +46,8 @@ let checkTargetHasMoney (target : PlayerData) attacker =
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 checkPlayerHasShieldSlotsAvailable (shield : Item) player =
let updatedPlayer = player |> Player.removeExpiredActions
let defenses = Player.getShieldEvents updatedPlayer
match defenses |> Array.length >= 2 with
| true ->
@ -71,37 +64,40 @@ let calculateDamage (hack : Item) (shield : Item) =
let runHackerBattle defender hack =
defender
|> Player.removeExpiredActions false
|> Player.removeExpiredActions
|> Player.getShieldEvents
|> Array.map (fun dfn -> Armory.battleItems |> Array.find (fun w -> w.Id = dfn.ItemId))
|> Array.map (calculateDamage (hack))
|> Array.contains Weak
let updateCombatants attacker defender hack prize =
let updateCombatants successfulHack (attacker : PlayerData) (defender : PlayerData) (hack : Item) prize =
let updatePlayer amount attack p =
{ p with Events = Array.append [| attack |] p.Events ; Bank = max (p.Bank + amount) 0<GBT> }
let target = { Id = defender.DiscordId ; Name = defender.Name }
let attack = {
ItemId = int hack
let event isDefenderEvent = {
ItemId = hack.Id
Type = PlayerEventType.Hacking
Adversary = target
Result = if prize > 0<GBT> then PlayerEventResult.Positive else PlayerEventResult.Negative
Adversary = if isDefenderEvent then attacker.basicPlayer else defender.basicPlayer
Cooldown = if isDefenderEvent then Game.SameTargetAttackCooldown.Minutes * 1<mins> else hack.Cooldown
Timestamp = DateTime.UtcNow
IsInstigator = not isDefenderEvent
Result =
match successfulHack , isDefenderEvent with
| true , true -> PlayerEventResult.Negative
| false , true -> PlayerEventResult.Positive
| true , false -> PlayerEventResult.Positive
| false , false -> PlayerEventResult.Negative
}
// TODO: This is what I was talking about, this isn't a "Shield" event, this is a hack event but there's an adversary
// who loses, so the event itself is to just "hack", so there's no "mugged" event, there's just a failed steal defense
// or something like that.
[ DbService.updatePlayer <| updatePlayer prize attack attacker
DbService.updatePlayer <| Player.modifyBank defender -prize ]
[ DbService.updatePlayer <| updatePlayer prize (event false) attacker
DbService.updatePlayer <| updatePlayer -prize (event true) defender ]
|> Async.Parallel
|> Async.Ignore
let successfulHack (ctx : IDiscordContext) attacker defender hack =
async {
do! updateCombatants attacker defender hack Game.HackPrize
do! updateCombatants true attacker defender hack Game.HackPrize
let embed = Embeds.responseSuccessfulHack true defender.DiscordId (Armory.getItem hack)
let embed = Embeds.responseSuccessfulHack true defender.DiscordId hack
do! ctx.FollowUp embed |> Async.AwaitTask
let builder = Embeds.eventSuccessfulHack ctx defender Game.HackPrize
@ -116,7 +112,7 @@ let failedHack (ctx : IDiscordContext) attacker defender hack =
let msg = $"Hack failed! {defender.Name} was able to mount a successful defense! You lost {Game.ShieldPrize} $GBT!"
do! sendFollowUpMessage ctx msg
do! updateCombatants attacker defender hack -Game.ShieldPrize
do! updateCombatants false attacker defender hack -Game.ShieldPrize
let builder = DiscordMessageBuilder()
builder.WithContent($"Hacking attempt failed! <@{defender.DiscordId}> defended hack from {ctx.GetDiscordMember().Username} and stole {Game.ShieldPrize} $GBT from them! ") |> ignore
@ -129,8 +125,8 @@ let failedHack (ctx : IDiscordContext) attacker defender hack =
let attack (target : DiscordUser) (ctx : IDiscordContext) =
Game.executePlayerActionWithTarget target ctx (fun attacker defender -> async {
do! attacker
|> checkAlreadyHackedTarget defender.DiscordId
<!> (Player.removeExpiredActions true)
|> checkAlreadyHackedTarget defender
<!> Player.removeExpiredActions
>>= checkHasEmptyHacks
>>= checkTargetHasMoney defender
>>= checkPlayerIsAttackingThemselves defender
@ -145,23 +141,23 @@ let handleAttack (ctx : IDiscordContext) =
Game.executePlayerAction ctx (fun attacker -> async {
let split = ctx.GetInteractionId().Split("-")
let hackId = int split.[1]
let hack = enum<HackId>(hackId)
let ( resultId , targetId ) = UInt64.TryParse split.[2]
let hack = Armory.getItem 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 false
|> checkAlreadyHackedTarget defender.DiscordId
>>= checkPlayerOwnsWeapon hackId
>>= checkItemHasCooldown hackId
|> Player.removeExpiredActions
|> checkAlreadyHackedTarget defender
>>= checkPlayerOwnsWeapon hack
>>= checkWeaponHasCooldown hack
|> function
| Ok atkr ->
runHackerBattle defender (Armory.getItem (int hackId))
runHackerBattle defender hack
|> function
| false -> successfulHack ctx atkr defender hackId
| true -> failedHack ctx attacker defender hackId
| false -> successfulHack ctx atkr defender hack
| true -> failedHack ctx attacker defender hack
| Error msg -> Messaging.sendFollowUpMessage ctx msg
| _ -> do! Messaging.sendFollowUpMessage ctx "Error occurred processing attack"
})
@ -169,7 +165,7 @@ let handleAttack (ctx : IDiscordContext) =
let defend (ctx : IDiscordContext) =
Game.executePlayerAction ctx (fun player -> async {
if Player.getShields player |> Array.length > 0 then
let p = Player.removeExpiredActions false player
let p = Player.removeExpiredActions player
let embed = Embeds.pickDefense "Defend" p false
do! ctx.FollowUp embed |> Async.AwaitTask
else
@ -184,20 +180,22 @@ let handleDefense (ctx : IDiscordContext) =
let shield = Armory.getItem shieldId
do! player
|> checkPlayerOwnsWeapon shieldId
|> checkPlayerOwnsWeapon shield
>>= checkPlayerHasShieldSlotsAvailable shield
>>= checkItemHasCooldown shieldId
|> handleResultWithResponse ctx (fun _ -> async { // Don't use this player, it removes player cooldowns
let embed = Embeds.responseCreatedShield (Armory.getItem shieldId)
>>= checkWeaponHasCooldown shield
|> handleResultWithResponse ctx (fun p -> async {
let embed = Embeds.responseCreatedShield shield
do! ctx.FollowUp embed |> Async.AwaitTask
let defense = {
ItemId = shieldId
Type = PlayerEventType.Shielding
Result = PlayerEventResult.Positive
Timestamp = DateTime.UtcNow
Cooldown = shield.Cooldown
IsInstigator = true
Adversary = DiscordPlayer.empty
}
do! DbService.updatePlayer <| { player with Events = Array.append [| defense |] player.Events }
do! DbService.updatePlayer <| { p with Events = Array.append [| defense |] p.Events }
let builder = DiscordMessageBuilder()
builder.WithContent($"{ctx.GetDiscordMember().Username} has protected their system!") |> ignore
let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelEventsHackerBattle)
@ -209,7 +207,7 @@ let handleDefense (ctx : IDiscordContext) =
let arsenal (ctx : IDiscordContext) =
Game.executePlayerAction ctx (fun player -> async {
let updatedPlayer = Player.removeExpiredActions false player
let updatedPlayer = Player.removeExpiredActions player
let builder = DiscordFollowupMessageBuilder()
let embed = DiscordEmbedBuilder()
embed.AddField("Arsenal", Arsenal.statusFormat updatedPlayer) |> ignore

View File

@ -92,8 +92,8 @@ let getResultEmbed chance prize (bank : int<GBT>) thief (victim : DiscordPlayer)
let checkVictimStealingCooldown defender attacker =
defender
|> Player.removeExpiredActions false
|> Player.getShieldEvents
|> Player.removeExpiredActions
|> fun p -> p.Events
|> Array.tryFind (fun pe -> pe.Type = PlayerEventType.Steal && pe.Result = PlayerEventResult.Negative)
|> function
| Some act ->
@ -102,19 +102,16 @@ let checkVictimStealingCooldown defender attacker =
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.getHackEvents
|> Player.removeExpiredActions
|> fun p -> p.Events
|> Array.tryFind (fun pe -> pe.Type = PlayerEventType.Steal)
|> 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
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."
| None -> Ok attacker
@ -157,7 +154,15 @@ let handleSteal (ctx : IDiscordContext) =
let num = rand.NextDouble()
let result = winPercentage >= num , rand.Next(0,3) = 0
let dp = { DiscordPlayer.Id = targetId ; DiscordPlayer.Name = targetName }
let stealAction result = { ItemId = -1 ; Type = PlayerEventType.Steal ; Result = result ; Adversary = dp ; Timestamp = DateTime.UtcNow }
let stealAction result = {
ItemId = -1
Type = PlayerEventType.Steal
Result = result
Adversary = dp
IsInstigator = true
Cooldown = ThiefCooldown.Minutes * 1<mins>
Timestamp = DateTime.UtcNow
}
let getResultEmbed' = getResultEmbed winPercentage prize thief.Bank thief dp
// TODO: Send event to the hall of privacy
// TODO: We need to check if the player is on cooldown
@ -169,12 +174,29 @@ let handleSteal (ctx : IDiscordContext) =
do! Messaging.sendFollowUpEmbed ctx (embed.Build())
match! DbService.tryFindPlayer targetId with
| Some t ->
let mugged = { ItemId = -1 ; Type = PlayerEventType.Steal ; Result = PlayerEventResult.Negative ; Adversary = thief.basicPlayer ; Timestamp = DateTime.UtcNow }
let actions = t |> Player.removeExpiredActions false |> fun p -> Array.append [| mugged |] p.Events
let mugged = {
ItemId = -1
Type = PlayerEventType.Steal
Result = PlayerEventResult.Negative
Adversary = thief.basicPlayer
Timestamp = DateTime.UtcNow
IsInstigator = false
Cooldown = VictimRecovery.Minutes * 1<mins>
}
let actions = t |> Player.removeExpiredActions |> fun p -> Array.append [| mugged |] p.Events
do! DbService.updatePlayer { t with Bank = max (t.Bank - prize) 0<GBT> ; Events = actions }
| None -> ()
let stole = { ItemId = -1 ; Type = PlayerEventType.Steal ; Result = PlayerEventResult.Positive ; Adversary = dp ; Timestamp = DateTime.UtcNow }
let actions = thief |> Player.removeExpiredActions false |> fun p -> Array.append [| stole |] p.Events
let stole = {
ItemId = -1
Type = PlayerEventType.Steal
Result = PlayerEventResult.Positive
Adversary = dp
Timestamp = DateTime.UtcNow
IsInstigator = true
Cooldown = ThiefCooldown.Minutes * 1<mins>
}
let actions = thief |> Player.removeExpiredActions |> fun p -> Array.append [| stole |] p.Events
do! DbService.updatePlayer { thief with Bank = thief.Bank + prize ; XP = thief.XP + 0 ; Events = actions }
// do! Async.Sleep 2000
// do! ctx.FollowUp (XP.getRewardsEmbed 1 player) |> Async.AwaitTask
@ -194,7 +216,7 @@ let handleSteal (ctx : IDiscordContext) =
let targetId = uint64 split.[2]
Game.executePlayerActionWithTargetId true targetId ctx (fun attacker defender -> async {
do! attacker
|> Player.removeExpiredActions false
|> Player.removeExpiredActions
|> checkVictimStealingCooldown defender
>>= checkThiefCooldown
|> handleResultWithResponse ctx (handleYes defender )

View File

@ -13,16 +13,20 @@ let defaultHack = Armory.battleItems |> Array.find (fun i -> i.Id = int HackId.V
let defaultShield = Armory.battleItems |> Array.find (fun i -> i.Id = int ShieldId.Firewall)
let TrainerEvents = [|
{ PlayerEvent.Timestamp = System.DateTime.UtcNow
PlayerEvent.Adversary = Sensei
PlayerEvent.Type = PlayerEventType.Hacking
PlayerEvent.Result = PlayerEventResult.Positive
PlayerEvent.ItemId = defaultHack.Id }
{ PlayerEvent.Timestamp = System.DateTime.UtcNow
PlayerEvent.Adversary = DiscordPlayer.empty
PlayerEvent.Type = PlayerEventType.Shielding
PlayerEvent.Result = PlayerEventResult.Positive
PlayerEvent.ItemId = defaultShield.Id }
{ Timestamp = System.DateTime.UtcNow
Adversary = Sensei
Type = PlayerEventType.Hacking
Result = PlayerEventResult.Positive
Cooldown = 5<mins>
IsInstigator = true
ItemId = defaultHack.Id }
{ Timestamp = System.DateTime.UtcNow
Adversary = DiscordPlayer.empty
Type = PlayerEventType.Shielding
Result = PlayerEventResult.Positive
Cooldown = defaultShield.Cooldown
IsInstigator = true
ItemId = defaultShield.Id }
|]
let sendInitialEmbed (client : DiscordClient) =
@ -156,19 +160,20 @@ let handleArsenal (ctx : IDiscordContext) =
let hasStockWeapons = Player.getHacks player |> Array.exists (fun item -> item.Id = defaultHack.Id)
let updatedPlayer =
if not hasStockWeapons then {
Player.removeExpiredActions false player with
Player.removeExpiredActions player with
Events = TrainerEvents |> Array.append player.Events
Inventory = [| defaultHack ; defaultShield |] |> Array.append player.Inventory
}
else
Player.removeExpiredActions false player
Player.removeExpiredActions player
if not hasStockWeapons then
do! DbService.updatePlayer updatedPlayer
let embed = Embeds.getArsenalEmbed updatedPlayer
do! ctx.FollowUp(embed) |> Async.AwaitTask
if not (player.Achievements |> Array.contains trainerAchievement) then
do! Async.Sleep 3000
let embed = Embeds.getAchievementEmbed "You completed the Training Dojo and collected loot." trainerAchievement
let rewards = [ $"{defaultHack.Name} Hack" ; $"{defaultShield.Name} Shield" ]
let embed = Embeds.getAchievementEmbed rewards "You completed the Training Dojo and collected loot." trainerAchievement
do! ctx.FollowUp(embed) |> Async.AwaitTask
do! Async.Sleep 2000
let role = ctx.GetGuild().GetRole(GuildEnvironment.roleTrainee)

View File

@ -59,7 +59,6 @@ module Types =
}
with static member empty = { Sell = false ; Buy = false ; Consume = false ; Drop = false }
type Item = {
Id : int
Name : string
@ -101,8 +100,10 @@ module Types =
type PlayerEvent =
{ Type : PlayerEventType
Result : PlayerEventResult
IsInstigator : bool
Adversary : DiscordPlayer
ItemId : int
Cooldown : int<mins>
Timestamp : DateTime }
[<CLIMutable>]