From 59c2c9796ec2bc8b0212b4f06f7d951cf41ff861 Mon Sep 17 00:00:00 2001 From: nizar Date: Tue, 24 Feb 2026 09:42:12 +0100 Subject: [PATCH] Persist parser resources to PostgreSQL (maps, npcs, monsters, shops, drops, quests) --- scripts/Database/import-parser-data.ps1 | 232 ++++++++ .../Plugin.QuestImpl/Managers/QuestManager.cs | 2 + .../Managers/QuestPostgresSync.cs | 203 +++++++ .../Plugin.QuestImpl/Plugin.QuestImpl.csproj | 4 + .../ServerConfigs/DropManager.cs | 3 + .../ServerConfigs/ItemBoxManager.cs | 5 + .../ServerConfigs/MapManager.cs | 3 + .../ServerConfigs/MapMonsterManager.cs | 2 + .../ServerConfigs/MapNpcManager.cs | 2 + .../Persistence/ParserDataPostgresSync.cs | 555 ++++++++++++++++++ .../ServerConfigs/RecipeManager.cs | 3 + .../ServerConfigs/ShopManager.cs | 4 + .../ServerConfigs/TeleporterManager.cs | 4 + ...ngsEmu.Plugins.BasicImplementations.csproj | 1 + 14 files changed, 1023 insertions(+) create mode 100644 scripts/Database/import-parser-data.ps1 create mode 100644 srcs/_plugins/Plugin.QuestImpl/Managers/QuestPostgresSync.cs create mode 100644 srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/Persistence/ParserDataPostgresSync.cs diff --git a/scripts/Database/import-parser-data.ps1 b/scripts/Database/import-parser-data.ps1 new file mode 100644 index 0000000..1f7a54d --- /dev/null +++ b/scripts/Database/import-parser-data.ps1 @@ -0,0 +1,232 @@ +param( + [string]$DbContainer = "wingsemu-postgres", + [string]$DbName = "game", + [string]$DbUser = "postgres", + [string]$ConfigRoot = "config" +) + +$ErrorActionPreference = 'Stop' + +$root = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)) +Set-Location $root + +$configPath = Join-Path $root $ConfigRoot +if (-not (Test-Path $configPath)) { throw "Config path not found: $configPath" } + +$maps = @() +$mapFlags = @() +$portals = @() +$npcs = @() +$monsters = @() + +function To-IntOrNull($v) { + if ([string]::IsNullOrWhiteSpace($v)) { return $null } + $v = ($v -split '#')[0].Trim() + if ($v -match '^-?\d+$') { return [int]$v } + return $null +} + +function SqlEsc([string]$s) { + if ($null -eq $s) { return 'NULL' } + return "'" + ($s -replace "'","''") + "'" +} + +function SqlNum($n) { + if ($null -eq $n) { return 'NULL' } + return [string]$n +} + +function Read-NonCommentLines([string]$file) { + Get-Content $file | ForEach-Object { $_.TrimEnd() } | Where-Object { $_ -notmatch '^\s*#' } +} + +# -------- maps -------- +Get-ChildItem (Join-Path $configPath 'maps') -File -ErrorAction SilentlyContinue | ForEach-Object { + $lines = Read-NonCommentLines $_.FullName + $current = $null + $inFlags = $false + + foreach ($line in $lines) { + if ($line -match '^\s*-\s*map_id\s*:\s*(.+)$') { + if ($current) { $maps += $current } + $current = [ordered]@{ map_id = To-IntOrNull $matches[1]; map_vnum = $null; map_name_id = $null; map_music_id = $null; flags = @() } + $inFlags = $false + continue + } + if (-not $current) { continue } + + if ($line -match '^\s*flags\s*:') { $inFlags = $true; continue } + + if ($inFlags -and $line -match '^\s*-\s*([A-Z0-9_]+)\s*$') { + $current.flags += $matches[1] + continue + } + + if ($line -match '^\s*map_vnum\s*:\s*(.+)$') { $current.map_vnum = To-IntOrNull $matches[1]; $inFlags=$false; continue } + if ($line -match '^\s*map_name_id\s*:\s*(.+)$') { $current.map_name_id = To-IntOrNull $matches[1]; $inFlags=$false; continue } + if ($line -match '^\s*map_music_id\s*:\s*(.+)$') { $current.map_music_id = To-IntOrNull $matches[1]; $inFlags=$false; continue } + } + + if ($current) { $maps += $current } +} + +foreach ($m in $maps) { + foreach ($f in $m.flags) { + $mapFlags += [ordered]@{ map_id = $m.map_id; flag = $f } + } +} + +# -------- portals -------- +Get-ChildItem (Join-Path $configPath 'map_portals') -File -ErrorAction SilentlyContinue | ForEach-Object { + $lines = Read-NonCommentLines $_.FullName + $current = $null + foreach ($line in $lines) { + if ($line -match '^\s*-\s*destination_map_id\s*:\s*(.+)$') { + if ($current) { $portals += $current } + $current = [ordered]@{ + destination_map_id = To-IntOrNull $matches[1] + destination_map_x = $null + destination_map_y = $null + source_map_id = $null + source_map_x = $null + source_map_y = $null + type = $null + } + continue + } + if (-not $current) { continue } + if ($line -match '^\s*destination_map_x\s*:\s*(.+)$') { $current.destination_map_x = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*destination_map_y\s*:\s*(.+)$') { $current.destination_map_y = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*source_map_id\s*:\s*(.+)$') { $current.source_map_id = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*source_map_x\s*:\s*(.+)$') { $current.source_map_x = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*source_map_y\s*:\s*(.+)$') { $current.source_map_y = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*type\s*:\s*(.+)$') { $current.type = To-IntOrNull $matches[1]; continue } + } + if ($current) { $portals += $current } +} + +# -------- npcs -------- +Get-ChildItem (Join-Path $configPath 'map_npc_placement') -File -ErrorAction SilentlyContinue | ForEach-Object { + $lines = Read-NonCommentLines $_.FullName + $mapId = $null + $current = $null + foreach ($line in $lines) { + if ($line -match '^\s*map_id\s*:\s*(.+)$') { $mapId = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*-\s*map_npc_id\s*:\s*(.+)$') { + if ($current) { $npcs += $current } + $current = [ordered]@{ map_id=$mapId; map_npc_id=To-IntOrNull $matches[1]; vnum=$null; pos_x=$null; pos_y=$null; effect_vnum=$null; effect_delay=$null; dialog_id=$null; direction_facing=$null } + continue + } + if (-not $current) { continue } + if ($line -match '^\s*vnum\s*:\s*(.+)$') { $current.vnum = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*pos_x\s*:\s*(.+)$') { $current.pos_x = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*pos_y\s*:\s*(.+)$') { $current.pos_y = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*effect_vnum\s*:\s*(.+)$') { $current.effect_vnum = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*effect_delay\s*:\s*(.+)$') { $current.effect_delay = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*dialog_id\s*:\s*(.+)$') { $current.dialog_id = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*direction_facing\s*:\s*(.+)$') { $current.direction_facing = To-IntOrNull $matches[1]; continue } + } + if ($current) { $npcs += $current } +} + +# -------- monsters -------- +Get-ChildItem (Join-Path $configPath 'map_monster_placement') -File -ErrorAction SilentlyContinue | ForEach-Object { + $lines = Read-NonCommentLines $_.FullName + $mapId = $null + $current = $null + foreach ($line in $lines) { + if ($line -match '^\s*map_id\s*:\s*(.+)$') { $mapId = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*-\s*map_monster_id\s*:\s*(.+)$') { + if ($current) { $monsters += $current } + $current = [ordered]@{ map_id=$mapId; map_monster_id=To-IntOrNull $matches[1]; vnum=$null; map_x=$null; map_y=$null; position=$null; can_move=$null } + continue + } + if (-not $current) { continue } + if ($line -match '^\s*vnum\s*:\s*(.+)$') { $current.vnum = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*map_x\s*:\s*(.+)$') { $current.map_x = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*map_y\s*:\s*(.+)$') { $current.map_y = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*position\s*:\s*(.+)$') { $current.position = To-IntOrNull $matches[1]; continue } + if ($line -match '^\s*can_move\s*:\s*(.+)$') { $current.can_move = ($matches[1].Trim().ToLower() -eq 'true'); continue } + } + if ($current) { $monsters += $current } +} + +$sql = New-Object System.Text.StringBuilder +[void]$sql.AppendLine("BEGIN;") +[void]$sql.AppendLine(@" +DROP TABLE IF EXISTS server_map_flags; +DROP TABLE IF EXISTS server_maps; +DROP TABLE IF EXISTS map_portals; +DROP TABLE IF EXISTS map_npcs; +DROP TABLE IF EXISTS map_monsters; + +CREATE TABLE server_maps ( + map_id INT PRIMARY KEY, + map_vnum INT, + map_name_id INT, + map_music_id INT +); +CREATE TABLE server_map_flags ( + map_id INT NOT NULL, + flag TEXT NOT NULL, + PRIMARY KEY (map_id, flag) +); +CREATE TABLE map_portals ( + id BIGSERIAL PRIMARY KEY, + destination_map_id INT, + destination_map_x INT, + destination_map_y INT, + source_map_id INT, + source_map_x INT, + source_map_y INT, + type INT +); +CREATE TABLE map_npcs ( + id BIGSERIAL PRIMARY KEY, + map_npc_id INT, + map_id INT, + vnum INT, + pos_x INT, + pos_y INT, + effect_vnum INT, + effect_delay INT, + dialog_id INT, + direction_facing INT +); +CREATE TABLE map_monsters ( + id BIGSERIAL PRIMARY KEY, + map_monster_id INT, + map_id INT, + vnum INT, + map_x INT, + map_y INT, + position INT, + can_move BOOLEAN +); +"@) + +foreach ($m in $maps | Where-Object { $null -ne $_.map_id }) { + [void]$sql.AppendLine("INSERT INTO server_maps(map_id,map_vnum,map_name_id,map_music_id) VALUES ($(SqlNum $m.map_id),$(SqlNum $m.map_vnum),$(SqlNum $m.map_name_id),$(SqlNum $m.map_music_id));") +} +foreach ($f in $mapFlags | Where-Object { $null -ne $_.map_id -and -not [string]::IsNullOrWhiteSpace($_.flag) }) { + [void]$sql.AppendLine("INSERT INTO server_map_flags(map_id,flag) VALUES ($(SqlNum $f.map_id),$(SqlEsc $f.flag));") +} +foreach ($p in $portals) { + [void]$sql.AppendLine("INSERT INTO map_portals(destination_map_id,destination_map_x,destination_map_y,source_map_id,source_map_x,source_map_y,type) VALUES ($(SqlNum $p.destination_map_id),$(SqlNum $p.destination_map_x),$(SqlNum $p.destination_map_y),$(SqlNum $p.source_map_id),$(SqlNum $p.source_map_x),$(SqlNum $p.source_map_y),$(SqlNum $p.type));") +} +foreach ($n in $npcs | Where-Object { $null -ne $_.map_npc_id }) { + [void]$sql.AppendLine("INSERT INTO map_npcs(map_npc_id,map_id,vnum,pos_x,pos_y,effect_vnum,effect_delay,dialog_id,direction_facing) VALUES ($(SqlNum $n.map_npc_id),$(SqlNum $n.map_id),$(SqlNum $n.vnum),$(SqlNum $n.pos_x),$(SqlNum $n.pos_y),$(SqlNum $n.effect_vnum),$(SqlNum $n.effect_delay),$(SqlNum $n.dialog_id),$(SqlNum $n.direction_facing));") +} +foreach ($m in $monsters | Where-Object { $null -ne $_.map_monster_id }) { + $mv = if ($null -eq $m.can_move) { 'NULL' } elseif ($m.can_move) { 'TRUE' } else { 'FALSE' } + [void]$sql.AppendLine("INSERT INTO map_monsters(map_monster_id,map_id,vnum,map_x,map_y,position,can_move) VALUES ($(SqlNum $m.map_monster_id),$(SqlNum $m.map_id),$(SqlNum $m.vnum),$(SqlNum $m.map_x),$(SqlNum $m.map_y),$(SqlNum $m.position),$mv);") +} + +[void]$sql.AppendLine("COMMIT;") + +$tmpSql = Join-Path $env:TEMP "parser_import.sql" +[System.IO.File]::WriteAllText($tmpSql, $sql.ToString()) + +Get-Content -Raw $tmpSql | docker exec -i $DbContainer psql -v ON_ERROR_STOP=1 -U $DbUser -d $DbName | Out-Host + +Write-Host "Imported maps=$($maps.Count) flags=$($mapFlags.Count) portals=$($portals.Count) npcs=$($npcs.Count) monsters=$($monsters.Count)" -ForegroundColor Green diff --git a/srcs/_plugins/Plugin.QuestImpl/Managers/QuestManager.cs b/srcs/_plugins/Plugin.QuestImpl/Managers/QuestManager.cs index 968085d..b5e68bb 100644 --- a/srcs/_plugins/Plugin.QuestImpl/Managers/QuestManager.cs +++ b/srcs/_plugins/Plugin.QuestImpl/Managers/QuestManager.cs @@ -140,6 +140,8 @@ namespace Plugin.QuestImpl.Managers questsNpcCounter++; } + QuestPostgresSync.Sync(quests.ToList(), tutorialScripts.ToList(), questsNpc.ToList()); + Log.Info($"[RESOURCES] Loaded {questCounter} quests."); Log.Info($"[RESOURCES] Loaded {objectivesCounter} quests objectives."); Log.Info($"[RESOURCES] Loaded {prizesCounter} quests rewards."); diff --git a/srcs/_plugins/Plugin.QuestImpl/Managers/QuestPostgresSync.cs b/srcs/_plugins/Plugin.QuestImpl/Managers/QuestPostgresSync.cs new file mode 100644 index 0000000..874f51f --- /dev/null +++ b/srcs/_plugins/Plugin.QuestImpl/Managers/QuestPostgresSync.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using Npgsql; +using PhoenixLib.Logging; +using WingsEmu.DTOs.Quests; + +namespace Plugin.QuestImpl.Managers +{ +public static class QuestPostgresSync +{ + private static bool Enabled => !string.Equals(Environment.GetEnvironmentVariable("PARSER_DB_SYNC"), "false", StringComparison.OrdinalIgnoreCase); + + private static string BuildConnectionString() + { + string host = Environment.GetEnvironmentVariable("DATABASE_IP") + ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_IP") + ?? "127.0.0.1"; + string port = Environment.GetEnvironmentVariable("DATABASE_PORT") + ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PORT") + ?? "5432"; + string db = Environment.GetEnvironmentVariable("DATABASE_NAME") + ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_NAME") + ?? "game"; + string user = Environment.GetEnvironmentVariable("DATABASE_USER") + ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_USER") + ?? "postgres"; + string pass = Environment.GetEnvironmentVariable("DATABASE_PASSWORD") + ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PASSWORD") + ?? "postgres"; + + return $"Host={host};Port={port};Database={db};Username={user};Password={pass}"; + } + + public static void Sync(IReadOnlyList quests, IReadOnlyList tutorials, IReadOnlyList questNpcs) + { + if (!Enabled) return; + + try + { + using var conn = new NpgsqlConnection(BuildConnectionString()); + conn.Open(); + using var tx = conn.BeginTransaction(); + + using (var cmd = new NpgsqlCommand(@" +CREATE TABLE IF NOT EXISTS quests ( + id BIGSERIAL PRIMARY KEY, + quest_id INT, + name TEXT, + description TEXT, + auto_finish BOOLEAN, + dialog_starting INT, + dialog_finish INT, + dialog_during INT, + min_level INT, + max_level INT, + next_quest_id INT, + quest_type INT, + required_quest_id INT, + talker_vnum INT, + target_map_id INT, + target_map_x INT, + target_map_y INT, + unknown1 INT, + is_blue BOOLEAN +); +CREATE TABLE IF NOT EXISTS quest_objectives ( + id BIGSERIAL PRIMARY KEY, + quest_id INT, + data0 INT, + data1 INT, + data2 INT, + data3 INT, + objective_index INT +); +CREATE TABLE IF NOT EXISTS quest_prizes ( + id BIGSERIAL PRIMARY KEY, + quest_id INT, + reward_type INT, + data0 INT, + data1 INT, + data2 INT, + data3 INT, + data4 INT +); +CREATE TABLE IF NOT EXISTS tutorials ( + id BIGSERIAL PRIMARY KEY, + tutorial_id INT, + data INT, + script_id INT, + script_index INT, + type INT +); +CREATE TABLE IF NOT EXISTS quest_npcs ( + id BIGSERIAL PRIMARY KEY, + quest_npc_id INT, + quest_id INT, + npc_vnum INT, + level INT, + starting_script INT, + required_completed_script INT, + is_main_quest BOOLEAN, + map_id INT +);", conn, tx)) cmd.ExecuteNonQuery(); + + using (var cmd = new NpgsqlCommand("TRUNCATE TABLE quests, quest_objectives, quest_prizes, tutorials, quest_npcs RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery(); + + foreach (QuestDto q in quests) + { + using (var cmd = new NpgsqlCommand(@"INSERT INTO quests(quest_id,name,description,auto_finish,dialog_starting,dialog_finish,dialog_during,min_level,max_level,next_quest_id,quest_type,required_quest_id,talker_vnum,target_map_id,target_map_x,target_map_y,unknown1,is_blue) +VALUES (@id,@name,@desc,@auto,@ds,@df,@dd,@min,@max,@next,@qt,@req,@talk,@tmid,@tmx,@tmy,@u1,@blue);", conn, tx)) + { + cmd.Parameters.AddWithValue("id", q.Id); + cmd.Parameters.AddWithValue("name", (object?)q.Name ?? DBNull.Value); + cmd.Parameters.AddWithValue("desc", (object?)q.Description ?? DBNull.Value); + cmd.Parameters.AddWithValue("auto", q.AutoFinish); + cmd.Parameters.AddWithValue("ds", q.DialogStarting); + cmd.Parameters.AddWithValue("df", q.DialogFinish); + cmd.Parameters.AddWithValue("dd", q.DialogDuring); + cmd.Parameters.AddWithValue("min", q.MinLevel); + cmd.Parameters.AddWithValue("max", q.MaxLevel); + cmd.Parameters.AddWithValue("next", q.NextQuestId); + cmd.Parameters.AddWithValue("qt", (int)q.QuestType); + cmd.Parameters.AddWithValue("req", q.RequiredQuestId); + cmd.Parameters.AddWithValue("talk", q.TalkerVnum); + cmd.Parameters.AddWithValue("tmid", q.TargetMapId); + cmd.Parameters.AddWithValue("tmx", q.TargetMapX); + cmd.Parameters.AddWithValue("tmy", q.TargetMapY); + cmd.Parameters.AddWithValue("u1", q.Unknown1); + cmd.Parameters.AddWithValue("blue", q.IsBlue); + cmd.ExecuteNonQuery(); + } + + if (q.Objectives != null) + { + foreach (QuestObjectiveDto o in q.Objectives) + { + using var cmd = new NpgsqlCommand(@"INSERT INTO quest_objectives(quest_id,data0,data1,data2,data3,objective_index) +VALUES (@qid,@d0,@d1,@d2,@d3,@idx);", conn, tx); + cmd.Parameters.AddWithValue("qid", q.Id); + cmd.Parameters.AddWithValue("d0", o.Data0); + cmd.Parameters.AddWithValue("d1", o.Data1); + cmd.Parameters.AddWithValue("d2", o.Data2); + cmd.Parameters.AddWithValue("d3", o.Data3); + cmd.Parameters.AddWithValue("idx", o.ObjectiveIndex); + cmd.ExecuteNonQuery(); + } + } + + if (q.Prizes != null) + { + foreach (QuestPrizeDto p in q.Prizes) + { + using var cmd = new NpgsqlCommand(@"INSERT INTO quest_prizes(quest_id,reward_type,data0,data1,data2,data3,data4) +VALUES (@qid,@rt,@d0,@d1,@d2,@d3,@d4);", conn, tx); + cmd.Parameters.AddWithValue("qid", q.Id); + cmd.Parameters.AddWithValue("rt", p.RewardType); + cmd.Parameters.AddWithValue("d0", p.Data0); + cmd.Parameters.AddWithValue("d1", p.Data1); + cmd.Parameters.AddWithValue("d2", p.Data2); + cmd.Parameters.AddWithValue("d3", p.Data3); + cmd.Parameters.AddWithValue("d4", p.Data4); + cmd.ExecuteNonQuery(); + } + } + } + + foreach (TutorialDto t in tutorials) + { + using var cmd = new NpgsqlCommand("INSERT INTO tutorials(tutorial_id,data,script_id,script_index,type) VALUES (@id,@data,@sid,@sidx,@type);", conn, tx); + cmd.Parameters.AddWithValue("id", t.Id); + cmd.Parameters.AddWithValue("data", t.Data); + cmd.Parameters.AddWithValue("sid", t.ScriptId); + cmd.Parameters.AddWithValue("sidx", t.ScriptIndex); + cmd.Parameters.AddWithValue("type", (int)t.Type); + cmd.ExecuteNonQuery(); + } + + foreach (QuestNpcDto n in questNpcs) + { + using var cmd = new NpgsqlCommand(@"INSERT INTO quest_npcs(quest_npc_id,quest_id,npc_vnum,level,starting_script,required_completed_script,is_main_quest,map_id) +VALUES (@id,@qid,@npc,@lvl,@start,@req,@main,@map);", conn, tx); + cmd.Parameters.AddWithValue("id", n.Id); + cmd.Parameters.AddWithValue("qid", (object?)n.QuestId ?? DBNull.Value); + cmd.Parameters.AddWithValue("npc", n.NpcVnum); + cmd.Parameters.AddWithValue("lvl", n.Level); + cmd.Parameters.AddWithValue("start", n.StartingScript); + cmd.Parameters.AddWithValue("req", n.RequiredCompletedScript); + cmd.Parameters.AddWithValue("main", n.IsMainQuest); + cmd.Parameters.AddWithValue("map", n.MapId); + cmd.ExecuteNonQuery(); + } + + tx.Commit(); + Log.Info($"[PARSER_DB_SYNC] Synced quests={quests.Count} tutorials={tutorials.Count} quest_npcs={questNpcs.Count}"); + } + catch (Exception ex) + { + Log.Error("[PARSER_DB_SYNC] Failed to sync quests", ex); + } + } +} +} + diff --git a/srcs/_plugins/Plugin.QuestImpl/Plugin.QuestImpl.csproj b/srcs/_plugins/Plugin.QuestImpl/Plugin.QuestImpl.csproj index c5ff114..332504b 100644 --- a/srcs/_plugins/Plugin.QuestImpl/Plugin.QuestImpl.csproj +++ b/srcs/_plugins/Plugin.QuestImpl/Plugin.QuestImpl.csproj @@ -5,6 +5,10 @@ + + + + diff --git a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/DropManager.cs b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/DropManager.cs index 4652f27..b9eb293 100644 --- a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/DropManager.cs +++ b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/DropManager.cs @@ -11,6 +11,7 @@ using WingsEmu.Game._enum; using WingsEmu.Game.Managers.ServerData; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects.Drops; +using WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence; namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs; @@ -37,6 +38,8 @@ public class DropManager : IDropManager drops.AddRange(dropImportExportFile.Drops.SelectMany(s => s.ToDto())); } + ParserDataPostgresSync.SyncDrops(drops); + foreach (DropDTO drop in drops) { if (drop.MonsterVNum != null) diff --git a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/ItemBoxManager.cs b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/ItemBoxManager.cs index bf4fd6b..f584f91 100644 --- a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/ItemBoxManager.cs +++ b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/ItemBoxManager.cs @@ -5,6 +5,7 @@ using WingsEmu.DTOs.ServerDatas; using WingsEmu.Game.Items; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects.ItemBoxes; +using WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence; namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs; @@ -26,6 +27,7 @@ public class ItemBoxManager : IItemBoxManager public void Initialize() { int boxesCount = 0; + var allBoxes = new List(); foreach (ItemBoxImportFile file in _itemBoxConfigurations) { ItemBoxDto box = file.ToDto(); @@ -35,6 +37,7 @@ public class ItemBoxManager : IItemBoxManager } // just the item box itself + allBoxes.Add(box); _itemBoxesCache.Set(box.Id.ToString(), box); boxesCount++; } @@ -49,11 +52,13 @@ public class ItemBoxManager : IItemBoxManager continue; } + allBoxes.Add(box); _itemBoxesCache.Set(box.Id.ToString(), box); boxesCount++; } } + ParserDataPostgresSync.SyncItemBoxes(allBoxes); Log.Info($"[ITEMBOX_MANAGER] Loaded {boxesCount} itemBoxes"); } } \ No newline at end of file diff --git a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/MapManager.cs b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/MapManager.cs index c082595..450a931 100644 --- a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/MapManager.cs +++ b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/MapManager.cs @@ -22,6 +22,7 @@ using WingsEmu.Game.Portals; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects.Maps; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects.Portals; +using WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence; namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs; @@ -95,6 +96,8 @@ public class MapManager : IMapManager count = 0; int countBaseMaps = 0; var configuredMaps = _mapConfigurations.SelectMany(s => s.Select(p => p.ToDto())).ToList(); + ParserDataPostgresSync.SyncMapsAndPortals(configuredMaps, portals.ToList()); + foreach (ServerMapDto configuredMap in configuredMaps) { // Load MapFlags at the beginning diff --git a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/MapMonsterManager.cs b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/MapMonsterManager.cs index 9967656..ba523a3 100644 --- a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/MapMonsterManager.cs +++ b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/MapMonsterManager.cs @@ -7,6 +7,7 @@ using WingsEmu.DTOs.Maps; using WingsEmu.Game.Managers.ServerData; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects.Monsters; +using WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence; namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs; @@ -30,6 +31,7 @@ public class MapMonsterManager : IMapMonsterManager s.MapId = x.MapId; return s.ToDto(); })).ToList(); + ParserDataPostgresSync.SyncMapMonsters(monsters); int count = 0; foreach (MapMonsterDTO npcDto in monsters) diff --git a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/MapNpcManager.cs b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/MapNpcManager.cs index b360146..7e3c203 100644 --- a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/MapNpcManager.cs +++ b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/MapNpcManager.cs @@ -7,6 +7,7 @@ using WingsEmu.DTOs.Maps; using WingsEmu.Game.Managers.ServerData; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects.Npcs; +using WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence; namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs; @@ -32,6 +33,7 @@ public class MapNpcManager : IMapNpcManager })); var npcs = importedNpcs.Select(s => s.ToDto()).ToList(); + ParserDataPostgresSync.SyncMapNpcs(npcs); int count = 0; foreach (MapNpcDTO npcDto in npcs) diff --git a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/Persistence/ParserDataPostgresSync.cs b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/Persistence/ParserDataPostgresSync.cs new file mode 100644 index 0000000..984e2ba --- /dev/null +++ b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/Persistence/ParserDataPostgresSync.cs @@ -0,0 +1,555 @@ +using System; +using System.Collections.Generic; +using Npgsql; +using PhoenixLib.Logging; +using WingsAPI.Data.Drops; +using WingsAPI.Data.Shops; +using WingsEmu.DTOs.Maps; +using WingsEmu.DTOs.Recipes; +using WingsEmu.DTOs.ServerDatas; +using WingsEmu.DTOs.Shops; + +namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence; + +public static class ParserDataPostgresSync +{ + private static bool Enabled => !string.Equals(Environment.GetEnvironmentVariable("PARSER_DB_SYNC"), "false", StringComparison.OrdinalIgnoreCase); + + private static string BuildConnectionString() + { + string host = Environment.GetEnvironmentVariable("DATABASE_IP") + ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_IP") + ?? "127.0.0.1"; + string port = Environment.GetEnvironmentVariable("DATABASE_PORT") + ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PORT") + ?? "5432"; + string db = Environment.GetEnvironmentVariable("DATABASE_NAME") + ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_NAME") + ?? "game"; + string user = Environment.GetEnvironmentVariable("DATABASE_USER") + ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_USER") + ?? "postgres"; + string pass = Environment.GetEnvironmentVariable("DATABASE_PASSWORD") + ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PASSWORD") + ?? "postgres"; + + return $"Host={host};Port={port};Database={db};Username={user};Password={pass}"; + } + + public static void SyncMapsAndPortals(IReadOnlyList maps, IReadOnlyList portals) + { + if (!Enabled) + { + return; + } + + try + { + using var conn = new NpgsqlConnection(BuildConnectionString()); + conn.Open(); + using var tx = conn.BeginTransaction(); + + using (var cmd = new NpgsqlCommand(@" +CREATE TABLE IF NOT EXISTS server_maps ( + map_id INT PRIMARY KEY, + map_vnum INT, + map_name_id INT, + map_music_id INT +); +CREATE TABLE IF NOT EXISTS server_map_flags ( + map_id INT NOT NULL, + flag TEXT NOT NULL, + PRIMARY KEY(map_id, flag) +); +CREATE TABLE IF NOT EXISTS map_portals ( + id BIGSERIAL PRIMARY KEY, + destination_map_id INT, + destination_map_x INT, + destination_map_y INT, + source_map_id INT, + source_map_x INT, + source_map_y INT, + type INT +);", conn, tx)) + { + cmd.ExecuteNonQuery(); + } + + using (var cmd = new NpgsqlCommand("TRUNCATE TABLE server_map_flags, server_maps, map_portals RESTART IDENTITY;", conn, tx)) + { + cmd.ExecuteNonQuery(); + } + + foreach (ServerMapDto map in maps) + { + using var cmd = new NpgsqlCommand("INSERT INTO server_maps(map_id,map_vnum,map_name_id,map_music_id) VALUES (@id,@vnum,@name,@music);", conn, tx); + cmd.Parameters.AddWithValue("id", map.Id); + cmd.Parameters.AddWithValue("vnum", map.MapVnum); + cmd.Parameters.AddWithValue("name", map.NameId); + cmd.Parameters.AddWithValue("music", map.MusicId); + cmd.ExecuteNonQuery(); + + if (map.Flags == null) + { + continue; + } + + foreach (MapFlags flag in map.Flags) + { + using var fcmd = new NpgsqlCommand("INSERT INTO server_map_flags(map_id,flag) VALUES (@id,@flag);", conn, tx); + fcmd.Parameters.AddWithValue("id", map.Id); + fcmd.Parameters.AddWithValue("flag", flag.ToString()); + fcmd.ExecuteNonQuery(); + } + } + + foreach (PortalDTO portal in portals) + { + using var cmd = new NpgsqlCommand(@"INSERT INTO map_portals(destination_map_id,destination_map_x,destination_map_y,source_map_id,source_map_x,source_map_y,type) +VALUES (@dmid,@dmx,@dmy,@smid,@smx,@smy,@type);", conn, tx); + cmd.Parameters.AddWithValue("dmid", portal.DestinationMapId); + cmd.Parameters.AddWithValue("dmx", portal.DestinationX); + cmd.Parameters.AddWithValue("dmy", portal.DestinationY); + cmd.Parameters.AddWithValue("smid", portal.SourceMapId); + cmd.Parameters.AddWithValue("smx", portal.SourceX); + cmd.Parameters.AddWithValue("smy", portal.SourceY); + cmd.Parameters.AddWithValue("type", portal.Type); + cmd.ExecuteNonQuery(); + } + + tx.Commit(); + Log.Info($"[PARSER_DB_SYNC] Synced maps={maps.Count} portals={portals.Count}"); + } + catch (Exception ex) + { + Log.Error("[PARSER_DB_SYNC] Failed to sync maps/portals", ex); + } + } + + public static void SyncMapNpcs(IReadOnlyList npcs) + { + if (!Enabled) + { + return; + } + + try + { + using var conn = new NpgsqlConnection(BuildConnectionString()); + conn.Open(); + using var tx = conn.BeginTransaction(); + + using (var cmd = new NpgsqlCommand(@" +CREATE TABLE IF NOT EXISTS map_npcs ( + id BIGSERIAL PRIMARY KEY, + map_npc_id INT, + map_id INT, + vnum INT, + pos_x INT, + pos_y INT, + effect_vnum INT, + effect_delay INT, + dialog_id INT, + direction_facing INT +);", conn, tx)) + { + cmd.ExecuteNonQuery(); + } + + using (var cmd = new NpgsqlCommand("TRUNCATE TABLE map_npcs RESTART IDENTITY;", conn, tx)) + { + cmd.ExecuteNonQuery(); + } + + foreach (MapNpcDTO npc in npcs) + { + using var cmd = new NpgsqlCommand(@"INSERT INTO map_npcs(map_npc_id,map_id,vnum,pos_x,pos_y,effect_vnum,effect_delay,dialog_id,direction_facing) +VALUES (@id,@map,@vnum,@x,@y,@ev,@ed,@dialog,@dir);", conn, tx); + cmd.Parameters.AddWithValue("id", npc.Id); + cmd.Parameters.AddWithValue("map", npc.MapId); + cmd.Parameters.AddWithValue("vnum", npc.NpcVNum); + cmd.Parameters.AddWithValue("x", npc.MapX); + cmd.Parameters.AddWithValue("y", npc.MapY); + cmd.Parameters.AddWithValue("ev", npc.Effect); + cmd.Parameters.AddWithValue("ed", npc.EffectDelay); + cmd.Parameters.AddWithValue("dialog", npc.Dialog); + cmd.Parameters.AddWithValue("dir", npc.Direction); + cmd.ExecuteNonQuery(); + } + + tx.Commit(); + Log.Info($"[PARSER_DB_SYNC] Synced map_npcs={npcs.Count}"); + } + catch (Exception ex) + { + Log.Error("[PARSER_DB_SYNC] Failed to sync map_npcs", ex); + } + } + + public static void SyncMapMonsters(IReadOnlyList monsters) + { + if (!Enabled) + { + return; + } + + try + { + using var conn = new NpgsqlConnection(BuildConnectionString()); + conn.Open(); + using var tx = conn.BeginTransaction(); + + using (var cmd = new NpgsqlCommand(@" +CREATE TABLE IF NOT EXISTS map_monsters ( + id BIGSERIAL PRIMARY KEY, + map_monster_id INT, + map_id INT, + vnum INT, + map_x INT, + map_y INT, + direction INT, + can_move BOOLEAN +); +ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS direction INT; +ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS can_move BOOLEAN; +ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS map_monster_id INT; +ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS map_id INT; +ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS vnum INT; +ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS map_x INT; +ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS map_y INT;", conn, tx)) + { + cmd.ExecuteNonQuery(); + } + + using (var cmd = new NpgsqlCommand("TRUNCATE TABLE map_monsters RESTART IDENTITY;", conn, tx)) + { + cmd.ExecuteNonQuery(); + } + + foreach (MapMonsterDTO monster in monsters) + { + using var cmd = new NpgsqlCommand(@"INSERT INTO map_monsters(map_monster_id,map_id,vnum,map_x,map_y,direction,can_move) +VALUES (@id,@map,@vnum,@x,@y,@dir,@move);", conn, tx); + cmd.Parameters.AddWithValue("id", monster.Id); + cmd.Parameters.AddWithValue("map", monster.MapId); + cmd.Parameters.AddWithValue("vnum", monster.MonsterVNum); + cmd.Parameters.AddWithValue("x", monster.MapX); + cmd.Parameters.AddWithValue("y", monster.MapY); + cmd.Parameters.AddWithValue("dir", monster.Direction); + cmd.Parameters.AddWithValue("move", monster.IsMoving); + cmd.ExecuteNonQuery(); + } + + tx.Commit(); + Log.Info($"[PARSER_DB_SYNC] Synced map_monsters={monsters.Count}"); + } + catch (Exception ex) + { + Log.Error("[PARSER_DB_SYNC] Failed to sync map_monsters", ex); + } + } + + public static void SyncTeleporters(IReadOnlyList teleporters) + { + if (!Enabled) return; + try + { + using var conn = new NpgsqlConnection(BuildConnectionString()); + conn.Open(); + using var tx = conn.BeginTransaction(); + using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS map_teleporters ( + id BIGSERIAL PRIMARY KEY, + teleporter_id INT, + idx INT, + type INT, + map_id INT, + map_npc_id INT, + map_x INT, + map_y INT +);", conn, tx)) cmd.ExecuteNonQuery(); + using (var cmd = new NpgsqlCommand("TRUNCATE TABLE map_teleporters RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery(); + + foreach (var t in teleporters) + { + using var cmd = new NpgsqlCommand(@"INSERT INTO map_teleporters(teleporter_id,idx,type,map_id,map_npc_id,map_x,map_y) +VALUES (@id,@idx,@type,@map,@npc,@x,@y);", conn, tx); + cmd.Parameters.AddWithValue("id", t.Id); + cmd.Parameters.AddWithValue("idx", t.Index); + cmd.Parameters.AddWithValue("type", (int)t.Type); + cmd.Parameters.AddWithValue("map", t.MapId); + cmd.Parameters.AddWithValue("npc", t.MapNpcId); + cmd.Parameters.AddWithValue("x", t.MapX); + cmd.Parameters.AddWithValue("y", t.MapY); + cmd.ExecuteNonQuery(); + } + + tx.Commit(); + Log.Info($"[PARSER_DB_SYNC] Synced map_teleporters={teleporters.Count}"); + } + catch (Exception ex) + { + Log.Error("[PARSER_DB_SYNC] Failed to sync map_teleporters", ex); + } + } + + public static void SyncShops(IReadOnlyList shops) + { + if (!Enabled) return; + try + { + using var conn = new NpgsqlConnection(BuildConnectionString()); + conn.Open(); + using var tx = conn.BeginTransaction(); + using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS shops ( + id BIGSERIAL PRIMARY KEY, + map_npc_id INT, + menu_type INT, + shop_type INT, + name TEXT +); +CREATE TABLE IF NOT EXISTS shop_items ( + id BIGSERIAL PRIMARY KEY, + map_npc_id INT, + slot INT, + color INT, + item_vnum INT, + rare INT, + type INT, + upgrade INT, + price INT +); +CREATE TABLE IF NOT EXISTS shop_skills ( + id BIGSERIAL PRIMARY KEY, + map_npc_id INT, + skill_vnum INT, + slot INT, + type INT +);", conn, tx)) cmd.ExecuteNonQuery(); + using (var cmd = new NpgsqlCommand("TRUNCATE TABLE shops, shop_items, shop_skills RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery(); + + foreach (var s in shops) + { + using (var cmd = new NpgsqlCommand("INSERT INTO shops(map_npc_id,menu_type,shop_type,name) VALUES (@npc,@menu,@type,@name);", conn, tx)) + { + cmd.Parameters.AddWithValue("npc", s.MapNpcId); + cmd.Parameters.AddWithValue("menu", s.MenuType); + cmd.Parameters.AddWithValue("type", s.ShopType); + cmd.Parameters.AddWithValue("name", (object?)s.Name ?? DBNull.Value); + cmd.ExecuteNonQuery(); + } + + if (s.Items != null) + { + foreach (ShopItemDTO it in s.Items) + { + using var cmd = new NpgsqlCommand(@"INSERT INTO shop_items(map_npc_id,slot,color,item_vnum,rare,type,upgrade,price) +VALUES (@npc,@slot,@color,@vnum,@rare,@type,@upg,@price);", conn, tx); + cmd.Parameters.AddWithValue("npc", s.MapNpcId); + cmd.Parameters.AddWithValue("slot", it.Slot); + cmd.Parameters.AddWithValue("color", it.Color); + cmd.Parameters.AddWithValue("vnum", it.ItemVNum); + cmd.Parameters.AddWithValue("rare", it.Rare); + cmd.Parameters.AddWithValue("type", it.Type); + cmd.Parameters.AddWithValue("upg", it.Upgrade); + cmd.Parameters.AddWithValue("price", (object?)it.Price ?? DBNull.Value); + cmd.ExecuteNonQuery(); + } + } + + if (s.Skills != null) + { + foreach (ShopSkillDTO sk in s.Skills) + { + using var cmd = new NpgsqlCommand("INSERT INTO shop_skills(map_npc_id,skill_vnum,slot,type) VALUES (@npc,@skill,@slot,@type);", conn, tx); + cmd.Parameters.AddWithValue("npc", s.MapNpcId); + cmd.Parameters.AddWithValue("skill", sk.SkillVNum); + cmd.Parameters.AddWithValue("slot", sk.Slot); + cmd.Parameters.AddWithValue("type", sk.Type); + cmd.ExecuteNonQuery(); + } + } + } + + tx.Commit(); + Log.Info($"[PARSER_DB_SYNC] Synced shops={shops.Count}"); + } + catch (Exception ex) + { + Log.Error("[PARSER_DB_SYNC] Failed to sync shops", ex); + } + } + + public static void SyncItemBoxes(IReadOnlyList itemBoxes) + { + if (!Enabled) return; + try + { + using var conn = new NpgsqlConnection(BuildConnectionString()); + conn.Open(); + using var tx = conn.BeginTransaction(); + using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS item_boxes ( + id BIGSERIAL PRIMARY KEY, + item_vnum INT, + box_type INT, + min_rewards INT, + max_rewards INT, + shows_raid_panel BOOLEAN +); +CREATE TABLE IF NOT EXISTS item_box_items ( + id BIGSERIAL PRIMARY KEY, + item_vnum INT, + probability INT, + min_original_rare INT, + max_original_rare INT, + generated_amount INT, + generated_vnum INT, + generated_random_rarity BOOLEAN, + generated_upgrade INT +);", conn, tx)) cmd.ExecuteNonQuery(); + using (var cmd = new NpgsqlCommand("TRUNCATE TABLE item_boxes, item_box_items RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery(); + + foreach (var b in itemBoxes) + { + using (var cmd = new NpgsqlCommand(@"INSERT INTO item_boxes(item_vnum,box_type,min_rewards,max_rewards,shows_raid_panel) +VALUES (@vnum,@type,@min,@max,@raid);", conn, tx)) + { + cmd.Parameters.AddWithValue("vnum", b.Id); + cmd.Parameters.AddWithValue("type", (int)b.ItemBoxType); + cmd.Parameters.AddWithValue("min", (object?)b.MinimumRewards ?? DBNull.Value); + cmd.Parameters.AddWithValue("max", (object?)b.MaximumRewards ?? DBNull.Value); + cmd.Parameters.AddWithValue("raid", b.ShowsRaidBoxPanelOnOpen); + cmd.ExecuteNonQuery(); + } + + if (b.Items == null) continue; + foreach (var it in b.Items) + { + using var cmd = new NpgsqlCommand(@"INSERT INTO item_box_items(item_vnum,probability,min_original_rare,max_original_rare,generated_amount,generated_vnum,generated_random_rarity,generated_upgrade) +VALUES (@vnum,@prob,@minr,@maxr,@amt,@gv,@rr,@upg);", conn, tx); + cmd.Parameters.AddWithValue("vnum", b.Id); + cmd.Parameters.AddWithValue("prob", it.Probability); + cmd.Parameters.AddWithValue("minr", it.MinimumOriginalItemRare); + cmd.Parameters.AddWithValue("maxr", it.MaximumOriginalItemRare); + cmd.Parameters.AddWithValue("amt", it.ItemGeneratedAmount); + cmd.Parameters.AddWithValue("gv", it.ItemGeneratedVNum); + cmd.Parameters.AddWithValue("rr", it.ItemGeneratedRandomRarity); + cmd.Parameters.AddWithValue("upg", it.ItemGeneratedUpgrade); + cmd.ExecuteNonQuery(); + } + } + + tx.Commit(); + Log.Info($"[PARSER_DB_SYNC] Synced item_boxes={itemBoxes.Count}"); + } + catch (Exception ex) + { + Log.Error("[PARSER_DB_SYNC] Failed to sync item_boxes", ex); + } + } + + public static void SyncDrops(IReadOnlyList drops) + { + if (!Enabled) return; + try + { + using var conn = new NpgsqlConnection(BuildConnectionString()); + conn.Open(); + using var tx = conn.BeginTransaction(); + using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS drops ( + id BIGSERIAL PRIMARY KEY, + drop_id INT, + amount INT, + drop_chance INT, + item_vnum INT, + map_id INT, + monster_vnum INT, + race_type INT, + race_sub_type INT +);", conn, tx)) cmd.ExecuteNonQuery(); + using (var cmd = new NpgsqlCommand("TRUNCATE TABLE drops RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery(); + + foreach (var d in drops) + { + using var cmd = new NpgsqlCommand(@"INSERT INTO drops(drop_id,amount,drop_chance,item_vnum,map_id,monster_vnum,race_type,race_sub_type) +VALUES (@id,@amount,@chance,@item,@map,@mon,@race,@subrace);", conn, tx); + cmd.Parameters.AddWithValue("id", d.Id); + cmd.Parameters.AddWithValue("amount", d.Amount); + cmd.Parameters.AddWithValue("chance", d.DropChance); + cmd.Parameters.AddWithValue("item", d.ItemVNum); + cmd.Parameters.AddWithValue("map", (object?)d.MapId ?? DBNull.Value); + cmd.Parameters.AddWithValue("mon", (object?)d.MonsterVNum ?? DBNull.Value); + cmd.Parameters.AddWithValue("race", (object?)d.RaceType ?? DBNull.Value); + cmd.Parameters.AddWithValue("subrace", (object?)d.RaceSubType ?? DBNull.Value); + cmd.ExecuteNonQuery(); + } + + tx.Commit(); + Log.Info($"[PARSER_DB_SYNC] Synced drops={drops.Count}"); + } + catch (Exception ex) + { + Log.Error("[PARSER_DB_SYNC] Failed to sync drops", ex); + } + } + + public static void SyncRecipes(IReadOnlyList recipes) + { + if (!Enabled) return; + try + { + using var conn = new NpgsqlConnection(BuildConnectionString()); + conn.Open(); + using var tx = conn.BeginTransaction(); + using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS recipes ( + id BIGSERIAL PRIMARY KEY, + recipe_id INT, + amount INT, + producer_map_npc_id INT, + produced_item_vnum INT, + producer_item_vnum INT, + producer_npc_vnum INT +); +CREATE TABLE IF NOT EXISTS recipe_items ( + id BIGSERIAL PRIMARY KEY, + recipe_id INT, + slot INT, + item_vnum INT, + amount INT +);", conn, tx)) cmd.ExecuteNonQuery(); + using (var cmd = new NpgsqlCommand("TRUNCATE TABLE recipes, recipe_items RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery(); + + foreach (var r in recipes) + { + using (var cmd = new NpgsqlCommand(@"INSERT INTO recipes(recipe_id,amount,producer_map_npc_id,produced_item_vnum,producer_item_vnum,producer_npc_vnum) +VALUES (@id,@amount,@mapNpc,@produced,@prodItem,@prodNpc);", conn, tx)) + { + cmd.Parameters.AddWithValue("id", r.Id); + cmd.Parameters.AddWithValue("amount", r.Amount); + cmd.Parameters.AddWithValue("mapNpc", (object?)r.ProducerMapNpcId ?? DBNull.Value); + cmd.Parameters.AddWithValue("produced", r.ProducedItemVnum); + cmd.Parameters.AddWithValue("prodItem", (object?)r.ProducerItemVnum ?? DBNull.Value); + cmd.Parameters.AddWithValue("prodNpc", (object?)r.ProducerNpcVnum ?? DBNull.Value); + cmd.ExecuteNonQuery(); + } + + if (r.Items == null) continue; + foreach (RecipeItemDTO it in r.Items) + { + using var cmd = new NpgsqlCommand("INSERT INTO recipe_items(recipe_id,slot,item_vnum,amount) VALUES (@id,@slot,@item,@amount);", conn, tx); + cmd.Parameters.AddWithValue("id", r.Id); + cmd.Parameters.AddWithValue("slot", it.Slot); + cmd.Parameters.AddWithValue("item", it.ItemVNum); + cmd.Parameters.AddWithValue("amount", it.Amount); + cmd.ExecuteNonQuery(); + } + } + + tx.Commit(); + Log.Info($"[PARSER_DB_SYNC] Synced recipes={recipes.Count}"); + } + catch (Exception ex) + { + Log.Error("[PARSER_DB_SYNC] Failed to sync recipes", ex); + } + } +} diff --git a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/RecipeManager.cs b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/RecipeManager.cs index a33f5d6..fbb9c57 100644 --- a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/RecipeManager.cs +++ b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/RecipeManager.cs @@ -10,6 +10,7 @@ using WingsEmu.Game.Managers.ServerData; using WingsEmu.Game.Managers.StaticData; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects.Recipes; +using WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence; namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs; @@ -76,6 +77,8 @@ public class RecipeManager : IRecipeManager recipes.Add(recipe); } + ParserDataPostgresSync.SyncRecipes(recipes); + int count = 0; foreach (RecipeDTO recipe in recipes) { diff --git a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/ShopManager.cs b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/ShopManager.cs index a4c300d..afbdc0e 100644 --- a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/ShopManager.cs +++ b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/ShopManager.cs @@ -15,6 +15,7 @@ using WingsEmu.Game.Managers.ServerData; using WingsEmu.Game.Shops; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects.Npcs; +using WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence; namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs; @@ -43,6 +44,7 @@ public class ShopManager : IShopManager int shopItemsCount = 0; int shopSkillsCount = 0; int count = 0; + var allShops = new List(); foreach (MapNpcObject npc in importedNpcs) { @@ -86,6 +88,7 @@ public class ShopManager : IShopManager } } + allShops.Add(shop); _shopsByNpcId.Set(shop.MapNpcId, _shopFactory.CreateShop(shop)); shopItemsCount += shop.Items?.Count ?? 0; shopSkillsCount += shop.Skills?.Count ?? 0; @@ -97,6 +100,7 @@ public class ShopManager : IShopManager } } + ParserDataPostgresSync.SyncShops(allShops); Log.Info($"[SHOP_MANAGER] Loaded {count.ToString()} shops."); Log.Info($"[SHOP_MANAGER] Loaded {shopItemsCount.ToString()} shops items."); Log.Info($"[SHOP_MANAGER] Loaded {shopSkillsCount.ToString()} shops skills."); diff --git a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/TeleporterManager.cs b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/TeleporterManager.cs index a99c929..a321ae7 100644 --- a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/TeleporterManager.cs +++ b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/ServerConfigs/TeleporterManager.cs @@ -11,6 +11,7 @@ using WingsEmu.DTOs.ServerDatas; using WingsEmu.Game.Battle; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects.Teleporters; +using WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence; namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs; @@ -25,6 +26,7 @@ public class TeleporterManager : ITeleporterManager public async Task InitializeAsync() { int count = 0; + var allTeleporters = new List(); foreach (TeleporterImportFile file in _teleporterConfigurations) { var teleporters = file.Teleporters.Select(s => @@ -33,6 +35,7 @@ public class TeleporterManager : ITeleporterManager count++; return s.ToDto(); }).ToList(); + allTeleporters.AddRange(teleporters); _teleporters[file.MapId] = teleporters; foreach (TeleporterDTO teleporter in teleporters) { @@ -46,6 +49,7 @@ public class TeleporterManager : ITeleporterManager } } + ParserDataPostgresSync.SyncTeleporters(allTeleporters); Log.Info($"[DATABASE] Loaded {count.ToString()} teleporters."); } diff --git a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/WingsEmu.Plugins.BasicImplementations.csproj b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/WingsEmu.Plugins.BasicImplementations.csproj index b835af4..70702ac 100644 --- a/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/WingsEmu.Plugins.BasicImplementations.csproj +++ b/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/WingsEmu.Plugins.BasicImplementations.csproj @@ -8,6 +8,7 @@ +