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); } } } }