Simplify stealing mechanic and finish RPS

This commit is contained in:
Joseph Ferano 2022-02-15 20:47:52 +07:00
parent 9f756540f1
commit 9f7b1956b4
6 changed files with 155 additions and 44 deletions

View File

@ -13,6 +13,7 @@
<Content Include="paket.references" />
<Compile Include="GuildEnvironment.fs" />
<Compile Include="Game.fs" />
<Compile Include="XP.fs" />
<Compile Include="PlayerInteractions.fs" />
<Compile Include="Embeds.fs" />
<Compile Include="SlotMachine.fs" />

View File

@ -88,6 +88,8 @@ module Player =
player.Actions
|> Array.filter (fun act -> match act.Type with Defense -> true | _ -> false || act.ActionId < 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 actions =
player.Actions

View File

@ -13,30 +13,30 @@ let StealActionId = 12
let VictimDefenseActionId = 12
let ThiefCooldown = TimeSpan.FromMinutes(1)
let VictimRecovery = TimeSpan.FromHours(12)
let VictimRecovery = TimeSpan.FromHours(6)
type StealResult =
| Success
| WentToPrison
| VictimRanAway
let getRandomStealBtnLabels () =
let rand = Random(Guid.NewGuid().GetHashCode())
let affirmative = [| "LFG" ; "YOLO" ; "IDGAF" |]
let negative = [| "NOPE" ; "IM OUT" ; "BAIL" |]
( affirmative.[rand.Next(0, 3)] , negative.[rand.Next(0, 3)] )
//let getRandomStealBtnLabels () =
// let rand = Random(Guid.NewGuid().GetHashCode())
// let affirmative = [| "LFG" ; "YOLO" ; "IDGAF" |]
// let negative = [| "NOPE" ; "IM OUT" ; "BAIL" |]
// ( affirmative.[rand.Next(0, 3)] , negative.[rand.Next(0, 3)] )
let chanceOfSuccessMsg = function
| amt when amt < 0.20 -> "Looking pretty bad"
| amt when amt < 0.50 -> "I mean, maybe"
| amt when amt < 0.80 -> "I think you got this"
| _ -> "Totally worth it"
//let chanceOfSuccessMsg = function
// | amt when amt < 0.20 -> "Looking bad"
// | amt when amt < 0.50 -> "I mean, maybe"
// | amt when amt < 0.80 -> "I think you got this"
// | _ -> "Totally worth it"
let targetEvaluationMsg = function
| amt when amt < 0.20 -> "but man, they look swole"
| amt when amt < 0.50 -> "but they look a little confident"
| amt when amt < 0.80 -> "and they're looking a little nervous"
| _ -> "and they look weak af man"
//let targetEvaluationMsg = function
// | amt when amt < 0.20 -> "but man, they look swole"
// | amt when amt < 0.50 -> "but they look a little confident"
// | amt when amt < 0.80 -> "and they're looking a little nervous"
// | _ -> "and they look weak af man"
let payout defenderBank chance =
let rand = Random(Guid.NewGuid().GetHashCode())
@ -45,19 +45,19 @@ let payout defenderBank chance =
let randomAmount = baseAmount + randomBonus |> int
max 1 randomAmount
let getStealEmbed chance prize (target : PlayerData) =
let getStealEmbed (chance : double) prize (target : PlayerData) =
let chance = int (chance * 100.0)
let buttons =
let yes , no = getRandomStealBtnLabels ()
[ DiscordButtonComponent(ButtonStyle.Success, $"Steal-yes-{target.DiscordId}-{target.Name}-{chance}-{prize}", yes)
DiscordButtonComponent(ButtonStyle.Danger, $"Steal-no", no) ]
// let yes , no = getRandomStealBtnLabels ()
let btnId = $"Steal-yes-{target.DiscordId}-{target.Name}-{chance}-{prize}"
[ DiscordButtonComponent(ButtonStyle.Success, btnId, "Do it") ]
// DiscordButtonComponent(ButtonStyle.Danger, $"Steal-no", no) ]
|> Seq.cast<DiscordComponent>
let embed =
DiscordEmbedBuilder()
.AddField("Chance of Success", $"{chanceOfSuccessMsg chance}", true)
.AddField("Chance of Success", $"{chance}%%", true)
.AddField("Payout", $"{prize}", true)
.WithDescription($"{target.Name} is coming towards you in a dark alley, {targetEvaluationMsg chance}")
.WithImageUrl("https://cdnb.artstation.com/p/assets/images/images/017/553/457/large/maarten-hof-backalley-mainshot.jpg")
.WithTitle($"Steal Money")
.WithTitle($"Steal $GBT from {target.Name}")
DiscordFollowupMessageBuilder()
.AddEmbed(embed)
@ -81,13 +81,14 @@ let getResultEmbed targetName result =
, "https://i.imgur.com/NLHMvVK.jpg"
DiscordEmbedBuilder()
.AddField("Result" , resultMsg)
// .AddField("Result" , resultMsg)
.WithDescription(msg)
.WithImageUrl(img)
// .WithImageUrl(img)
.WithTitle($"Robbery Results")
let checkVictimStealingCooldown defender attacker =
defender
|> Player.removeExpiredActions false
|> Player.getDefenses
|> Array.tryFind (fun act -> act.ActionId = VictimDefenseActionId)
|> function
@ -112,19 +113,31 @@ let checkThiefCooldown attacker =
Ok attacker
| None -> Ok attacker
let steal target (ctx : IDiscordContext) =
Game.executePlayerActionWithTarget target ctx (fun attacker defender -> async {
do! attacker
|> checkVictimStealingCooldown defender
>>= checkThiefCooldown
|> handleResultWithResponse ctx (fun player -> async {
let ``base`` = 0.5
let winPercentage = double (attacker.Stats.Strength - defender.Stats.Strength) * 0.45 + ``base``
let prize = payout (float defender.Bank) winPercentage
let embed = getStealEmbed winPercentage prize defender
do! ctx.FollowUp(embed) |> Async.AwaitTask
})
let calculateWinPercentage amountRequested bank attackerStrength defenderStrength =
let powerPercentage = float (attackerStrength - defenderStrength) * 0.005 + 0.25
printfn $"{(attackerStrength - defenderStrength)}"
printfn $"{powerPercentage}"
let cappedAmount = float bank * 0.5
let cappedRequest = min amountRequested (cappedAmount |> ceil)
let wagerPercentage = 1.0 - (cappedRequest / cappedAmount)
// Max chance of success is 97.5%
( cappedRequest , max 0.0 (wagerPercentage * 0.7 + powerPercentage * 1.3 ) / 2.05 )
//calculateWinPercentage 500 1000 100 50
let steal target amount (ctx : IDiscordContext) =
Game.executePlayerActionWithTarget target ctx (fun attacker defender -> async {
do!
attacker
|> checkVictimStealingCooldown defender
>>= checkThiefCooldown
|> handleResultWithResponse ctx (fun _ -> async {
let cappedPrize , winPercentage = calculateWinPercentage amount (int defender.Bank) attacker.Stats.Strength defender.Stats.Strength
let embed = getStealEmbed winPercentage cappedPrize defender
do! ctx.FollowUp(embed) |> Async.AwaitTask
})
})
let handleSteal (ctx : IDiscordContext) =
@ -143,9 +156,10 @@ let handleSteal (ctx : IDiscordContext) =
let dp = { DiscordPlayer.Id = targetId ; DiscordPlayer.Name = targetName }
let stealAction = { ActionId = StealActionId ; Type = Attack { AttackResult.Result = false ; AttackResult.Target = dp } ; Timestamp = DateTime.UtcNow }
// TODO: Send event to the hall of privacy
// TODO: We need to check if the player is on cooldown
match result with
| true , _ ->
let xp = 15
let xp = 25
let embed = getResultEmbed targetName Success
embed.AddField("$GBT Stolen", string prize) |> ignore
embed.AddField("XP Gained", $"{xp}") |> ignore
@ -153,10 +167,16 @@ let handleSteal (ctx : IDiscordContext) =
match! DbService.tryFindPlayer targetId with
| 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 }
let actions = t |> Player.removeExpiredActions false |> fun p -> Array.append [| action |] p.Actions
do! DbService.updatePlayer { t with Bank = max (t.Bank - prize) 0<GBT> ; Actions = actions }
| None -> ()
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 }
let actions = player |> Player.removeExpiredActions false |> fun p -> Array.append [| action |] p.Actions
do! DbService.updatePlayer { player with Bank = player.Bank + prize ; XP = player.XP + xp ; Actions = actions }
let newLevel = XP.getLevel (player.XP + xp)
// if XP.getLevel player.XP < newLevel then
do! Async.Sleep 2000
do! ctx.FollowUp (XP.getRewardsEmbed 1 player) |> Async.AwaitTask
| false , false ->
let embed = getResultEmbed targetName VictimRanAway
do! DbService.updatePlayer { player with Actions = Array.append [| stealAction |] player.Actions }
@ -198,8 +218,10 @@ type StealGame() =
}
[<SlashCommand("steal", "Steal some money from another player, but you might go to prison if caught")>]
member this.Steal (ctx : InteractionContext, [<Option("target", "Who do you want to steal from?")>] target : DiscordUser) =
enforceChannel (DiscordInteractionContext ctx) (steal target)
// steal target (DiscordInteractionContext ctx)
member this.Steal (ctx : InteractionContext,
[<Option("target", "Who do you want to steal from?")>] target : DiscordUser,
[<Option("amount", "How much you would like to steal")>] amount : double) =
// enforceChannel (DiscordInteractionContext ctx) (steal target amount)
steal target amount (DiscordInteractionContext ctx)

53
Bot/XP.fs Normal file
View File

@ -0,0 +1,53 @@
module Degenz.XP
open DSharpPlus
open DSharpPlus.Entities
type RewardType =
| Currency of int<GBT>
| RandomItem of itemType : ItemType * amount : int
| SpecialItem of id : int
[<Literal>]
let ToroRojo = 13
[<Literal>]
let AnabolicCycle = 14
let levels = [|
50
100
160
250
|]
let getLevel totalXp = levels |> Array.toList |> List.foldi (fun i acc elem -> if totalXp < elem then acc else i + 1) 0
let RewardTable = [|
[| Currency 100<GBT> ; SpecialItem ToroRojo |]
[| Currency 150<GBT> ; SpecialItem AnabolicCycle |]
[| Currency 200<GBT> ; SpecialItem ToroRojo |]
[| Currency 100<GBT> ; SpecialItem AnabolicCycle |]
|]
let getRewardsEmbed level (player : PlayerData) =
let buttons =
[ DiscordButtonComponent(ButtonStyle.Success, $"Reward-levelup-{level}", "Claim") ]
|> Seq.cast<DiscordComponent>
let header =
DiscordEmbedBuilder()
.WithTitle($"Level Up")
.WithDescription($"You reached Level {level}")
.WithImageUrl("https://thumbs.dreamstime.com/z/pixel-art-design-outdoor-landscape-background-colorful-arcade-screen-game-banner-button-level-up-concept-retro-style-180858223.jpg")
let footer =
DiscordEmbedBuilder()
.WithTitle("Rewards")
.AddField("$GBT", $"{RewardTable.[level].[0]}", true)
.AddField("Items", $"{RewardTable.[level].[1]}", true)
DiscordFollowupMessageBuilder()
.AddEmbeds([ header ; footer ] |> List.map (fun e -> e.Build()))
.AddComponents(buttons)
.AsEphemeral(true)

View File

@ -26,6 +26,7 @@ type PlayerEntry =
Arsenal : int array
Attacks : AttackAction array
Defenses : DefenseAction array
// XP : int
Bank : int }
let private actionToAttack (action : Action) (hack : AttackResult) =
@ -72,6 +73,24 @@ let private mapBack (player : PlayerEntry) : PlayerData = {
Bank = player.Bank * 1<GBT>
}
//let private mapBack (bson : BsonDocument) : PlayerData = {
// DiscordId = bson.GetValue("Player.DiscordId").AsInt64 |> uint64
// Name = bson.GetValue("Player.Name").AsString
// Arsenal =
// bson.GetValue("Inventory").AsBsonArray
// |> Seq.map (fun (bv : BsonValue) -> bv.AsInt32)
// |> Seq.map (fun w -> Armory.battleItems |> Array.find (fun w' -> w = w'.Id))
// |> Seq.toArray
// Actions =
// let atks = player.Attacks |> Array.map attackToAction
// let dfns = player.Defenses |> Array.map defenseToAction
// Array.append atks dfns
// Stats = PlayerStats.empty
// XP = 0
// Bank = player.Bank * 1<GBT>
//}
//
let mongo = MongoClient(Environment.GetEnvironmentVariable("CONN_STRING"))
let db = mongo.GetDatabase("degenz")

View File

@ -13,6 +13,20 @@ module ResultHelpers =
let (>>=) x f = Result.bind f x
let (<!>) x f = Result.map f x
[<RequireQualifiedAccess>]
module List =
let cons xs x = x :: xs
let consTo x xs = x :: xs
let rec foldk f (acc:'TState) xs =
match xs with
| [] -> acc
| x::xs -> f acc x (fun lacc -> foldk f lacc xs)
let foldi (f : int -> 'Acc -> 'elem -> 'Acc) (acc : 'Acc) xs =
let f' ( i , st ) acc = ( i + 1 , f i st acc )
List.fold f' ( 0 , acc ) xs |> snd
[<Microsoft.FSharp.Core.AutoOpen>]
module Types =