discord-bot-game/Bot/DbService.fs

334 lines
14 KiB
Forth

module Degenz.DbService
open System
open Npgsql
open Npgsql.FSharp
open Degenz
let connStr = GuildEnvironment.connectionString
type StatMod = { mod_type :string ; target_stat : string ; mod_amount : float }
NpgsqlConnection.GlobalTypeMapper.MapComposite<StatMod>("stat_mod") |> ignore
let readItem (reader : RowReader) =
let convertStatMod { mod_type = modType ; target_stat = targetStat; mod_amount = modAmount } =
let fx =
match modType with
| "Min" -> Min (int modAmount)
| "Max" -> Max (int modAmount)
| "RateMultiplier" -> RateMultiplier (modAmount)
| "Booster" -> Add (int modAmount)
| _ -> Add (int modAmount)
let ( _ , stat ) = StatId.TryParse(targetStat)
{ TargetStat = stat ; Effect = fx }
{ Item.Id = reader.int "id"
Item.Name = reader.string "name"
Item.Type =
match reader.string "category" with
| "Hack" -> ItemType.Hack
| "Shield" -> ItemType.Shield
| "Food" -> ItemType.Food
| "Accessory" -> ItemType.Accessory
| _ -> ItemType.Accessory
Item.Attributes = [
reader.intOrNone "buy_price" |> Option.map (fun a -> Buyable (a * 1<GBT>))
reader.intOrNone "sell_price" |> Option.map (fun a -> Sellable (a * 1<GBT>))
reader.intOrNone "expiration" |> Option.map (fun a -> Expireable (a * 1<mins>))
reader.floatOrNone "drop_chance" |> Option.map (float >> Droppable)
reader.intOrNone "attack_power" |> Option.map Attackable
reader.intOrNone "defense_power" |> Option.map Defendable
reader.stringOrNone "class_name" |> Option.map Classable
reader.intOrNone "max_stack" |> Option.map Stackable
if reader.bool "can_trade" then Some Tradeable else None
if reader.bool "can_consume" then Some Consumable else None
(match reader.fieldValue<StatMod array> "mods" with
| [||] -> None
| mods -> mods |> Array.map convertStatMod |> Array.toList |> Modifiable |> Some)
] |> List.choose id
}
let getPlayerInventory (did : uint64) =
connStr
|> Sql.connect
|> Sql.parameters [ "did", Sql.string (string did) ]
|> Sql.query """
SELECT ii.id,name,category,buy_price,sell_price,rate_limit,expiration,drop_chance,can_trade,can_consume,
attack_power,defense_power,class_name,max_stack,mods
FROM inventory_item
JOIN item ii on inventory_item.item_id = ii.id
JOIN "user" usr on inventory_item.user_id = usr.id
WHERE usr.discord_id = @did;
"""
|> Sql.executeAsync readItem
|> Async.AwaitTask
let addToPlayerInventory (did : uint64) (item : Item) =
connStr
|> Sql.connect
|> Sql.parameters [ ( "@did" , Sql.string (string did) ) ; ( "iid" , Sql.int item.Id )]
|> Sql.query """
INSERT INTO inventory_item (item_id, user_id)
VALUES (@iid, (SELECT id FROM "user" WHERE discord_id = @did));
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
let getStoreItems (channelId : uint64) =
connStr
|> Sql.connect
|> Sql.parameters [ "cid", Sql.string (string channelId) ]
|> Sql.query """
SELECT i.id,name,category,buy_price,sell_price,rate_limit,expiration,drop_chance,can_trade,can_consume,
attack_power,defense_power,class_name,max_stack,mods
FROM store_item
JOIN store st on store_item.store_id = st.id
JOIN item i on store_item.item_id = i.id
WHERE channel_id = @cid AND available;
"""
|> Sql.executeAsync readItem
|> Async.AwaitTask
let getPlayerEvents (did : uint64) =
connStr
|> Sql.connect
|> Sql.parameters [ "did", Sql.string (string did) ]
|> Sql.query """
WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did)
SELECT event_type, success, is_instigator, item_id, cooldown, adversary_id, adversary_name, created_at
FROM player_event ,usr WHERE user_id = usr.id and created_at > NOW() at time zone 'utc' - INTERVAL '24 HOURS';
"""
|> Sql.executeAsync (fun read ->
match read.string "event_type" with
| "Hacking" ->
Hacking {
IsInstigator = read.bool "is_instigator"
Success = read.bool "success"
Adversary = { Id = read.string "adversary_id" |> uint64 ; Name = read.string "adversary_name" }
HackId = read.int "item_id"
}
| "Shielding" -> Shielding (read.int "item_id")
| "Stealing" -> Stealing ( read.bool "is_instigator" , { Id = read.string "adversary_id" |> uint64 ; Name = read.string "adversary_name" } )
| "PlayingSlot" -> PlayingSlot
| _ -> Imprison
|> fun t ->
let date = read.dateTimeOrNone "created_at" |> Option.defaultValue DateTime.UtcNow
{ Type = t ; Cooldown = read.int "cooldown" * 1<mins> ; Timestamp = date }
)
|> Async.AwaitTask
let updatePlayerStats (player : PlayerData) =
connStr
|> Sql.connect
|> Sql.parameters
[ ( "did" , Sql.string (string player.DiscordId) )
( "strength", Sql.int player.Stats.Strength.Amount )
( "focus", Sql.int player.Stats.Focus.Amount )
( "charisma", Sql.int player.Stats.Charisma.Amount )
( "luck", Sql.int player.Stats.Luck.Amount ) ]
|> Sql.query """
WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did)
UPDATE player_stat SET strength = @strength, focus = @focus, charisma = @charisma, luck = @luck,
updated_at = now() at time zone 'utc'
FROM usr WHERE usr.id = user_id;
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
let tryFindPlayer (discordId : uint64) = async {
try
let! user =
connStr
|> Sql.connect
|> Sql.parameters [ "did", Sql.string (string discordId) ]
|> Sql.query """
SELECT discord_id, display_name, gbt, in_game, strength, focus, charisma, luck FROM "user"
WHERE discord_id = @did
"""
|> Sql.executeAsync (fun read ->
{| DiscordId = read.string "discord_id" |> uint64
Name = read.string "display_name"
Bank = read.intOrNone "gbt" |> Option.map ((*) 1<GBT>) |> Option.defaultValue 0<GBT>
Strength = read.intOrNone "strength" |> Option.defaultValue 0
Focus = read.intOrNone "focus" |> Option.defaultValue 0
Charisma = read.intOrNone "charisma" |> Option.defaultValue 0
Luck = read.intOrNone "luck" |> Option.defaultValue 0
Active = read.bool "in_game"
|})
|> Async.AwaitTask
match List.tryHead user with
| None -> return None
| Some u ->
let! events = getPlayerEvents u.DiscordId
let! inventory = getPlayerInventory discordId
let strength = PlayerStats.calculateActiveStat StatId.Strength u.Strength inventory
let focus = PlayerStats.calculateActiveStat StatId.Focus u.Focus inventory
let charisma = PlayerStats.calculateActiveStat StatId.Charisma u.Charisma inventory
let luck = PlayerStats.calculateActiveStat StatId.Luck u.Luck inventory
return Some
{ DiscordId = u.DiscordId
Name = u.Name
Inventory = inventory
Events = events
Stats = { Strength = strength ; Focus = focus ; Charisma = charisma ; Luck = luck }
Bank = u.Bank
Active = u.Active }
with e ->
printfn $"Got an error{e.Message}"
return None
}
let updatePlayerCurrency (addAmount : int<GBT>) (player : PlayerData) =
connStr
|> Sql.connect
|> Sql.parameters [
"did", Sql.string (string player.DiscordId)
"gbt", Sql.int (int addAmount)
] |> Sql.query """
UPDATE "user" SET gbt = gbt + @gbt WHERE discord_id = @did;
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
let updatePlayer (player : PlayerData) =
connStr
|> Sql.connect
|> Sql.parameters [
"did", Sql.string (string player.DiscordId)
"gbt", Sql.int (int player.Bank)
"strength", Sql.int player.Stats.Strength.Amount
"focus", Sql.int player.Stats.Focus.Amount
"charisma", Sql.int player.Stats.Charisma.Amount
"luck", Sql.int player.Stats.Luck.Amount
] |> Sql.query """
UPDATE "user" SET gbt = @gbt, strength = @strength, focus = @focus, charisma = @charisma, luck = @luck
WHERE discord_id = @did
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
let addAchievement (did : uint64) (achievement : string) =
connStr
|> Sql.connect
|> Sql.parameters
[ ( "did" , Sql.string (string did) )
( "symbol", Sql.string achievement ) ]
|> Sql.query """
WITH ach AS (SELECT id FROM achievement WHERE symbol = cast(@symbol AS achievement_symbol_enum)),
usr AS (SELECT id FROM "user" WHERE discord_id = @did)
INSERT INTO user_achievements_achievement (user_id, achievement_id) SELECT usr.id, ach.id FROM usr, ach;
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
let checkHasAchievement (did : uint64) (achievement : string) = async {
let! result =
connStr
|> Sql.connect
|> Sql.parameters
[ ( "did" , Sql.string (string did) )
( "symbol", Sql.string achievement ) ]
|> Sql.query """
WITH ach AS (SELECT id FROM achievement WHERE symbol = cast(@symbol AS achievement_symbol_enum)),
usr AS (SELECT id FROM "user" WHERE discord_id = @did)
SELECT achievement_id FROM user_achievements_achievement, ach, usr WHERE user_id = usr.id and achievement_id = ach.id;
"""
|> Sql.executeAsync (fun read -> read.int "achievement_id")
|> Async.AwaitTask
return List.isEmpty result |> not
}
let removeShieldEvent (did : uint64) shieldId =
connStr
|> Sql.connect
|> Sql.parameters
[ ( "did" , Sql.string (string did) )
( "shield_id", Sql.int shieldId ) ]
|> Sql.query """
WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did)
DELETE FROM player_event USING usr WHERE user_id = usr.id and event_type = 'Shielding' and item_id = @shield_id
"""
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
let addPlayerEvent (did : uint64) (playerEvent : PlayerEvent) =
let sqlParams , query =
match playerEvent.Type with
| Hacking h ->
[
( "did", Sql.string (string did) )
( "success" , Sql.bool h.Success )
( "is_instigator", Sql.bool h.IsInstigator )
( "item_id", Sql.int h.HackId )
( "cooldown", Sql.int (int playerEvent.Cooldown) )
( "adversary_id", Sql.string (string h.Adversary.Id) )
( "adversary_name", Sql.string (string h.Adversary.Name) )
] ,
"""
WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did)
INSERT INTO player_event (event_type, success, is_instigator, item_id, cooldown, adversary_id, adversary_name, user_id)
SELECT 'Hacking', @success, @is_instigator, @item_id, @cooldown, @adversary_id, @adversary_name, usr.id FROM usr
"""
| Shielding id ->
[
( "did" , Sql.string (string did) )
( "item_id", Sql.int id )
( "cooldown", Sql.int (int playerEvent.Cooldown) )
] ,
"""
WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did)
INSERT INTO player_event (event_type, item_id, cooldown, user_id)
SELECT 'Shielding', @item_id, @cooldown, usr.id FROM usr
"""
| Stealing(instigator, victim) ->
[
( "did" , Sql.string (string did) )
( "is_instigator", Sql.bool instigator )
( "adversary_id", Sql.string (string victim.Id) )
( "adversary_name", Sql.string (string victim.Name) )
( "cooldown", Sql.int (int playerEvent.Cooldown) )
] ,
"""
WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did)
INSERT INTO player_event (event_type, is_instigator, adversary_id, adversary_name, cooldown, user_id)
SELECT 'Stealing', @is_instigator, @adversary_id, @adversary_name, @cooldown, usr.id FROM usr
"""
| PlayingSlot ->
[ ( "did" , Sql.string (string did) ) ] ,
"""
WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did)
INSERT INTO player_event (event_type, cooldown, user_id)
SELECT 'PlayingSlot', 1, usr.id FROM usr
"""
| Imprison ->
[
( "did" , Sql.string (string did) )
( "cooldown", Sql.int (int playerEvent.Cooldown) )
] ,
"""
WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did)
INSERT INTO player_event (event_type, cooldown, user_id)
SELECT 'Imprison', @cooldown, usr.id FROM usr
"""
connStr
|> Sql.connect
|> Sql.parameters sqlParams
|> Sql.query query
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
let getRandomHackablePlayers (did : uint64) =
connStr
|> Sql.connect
|> Sql.parameters [ "did", Sql.string (string did) ]
|> Sql.query """
SELECT discord_id, display_name FROM "user"
JOIN user_achievements_achievement uaa ON "user".id = uaa.user_id
JOIN achievement a ON uaa.achievement_id = a.id AND a.symbol = 'FINISHED_TRAINER'
WHERE "user".in_game = true AND gbt > 20 AND "user".discord_id != @did
ORDER BY random() LIMIT 10
"""
|> Sql.executeAsync (fun read -> {| Id = read.string "discord_id" |> uint64 ; Name = read.string "display_name" |})
|> Async.AwaitTask