Add DB-first runtime loading and strict PostgreSQL-backed config/resources paths
This commit is contained in:
parent
aafa4585f9
commit
18d24f3cbe
29 changed files with 1753 additions and 164 deletions
|
|
@ -192,9 +192,11 @@ services:
|
|||
WINGSEMU_MONGO_DB: wingsemu_logs
|
||||
WINGSEMU_MONGO_USERNAME: ${MONGO_ROOT_USERNAME}
|
||||
WINGSEMU_MONGO_PWD: ${MONGO_ROOT_PASSWORD}
|
||||
DB_FIRST: "false"
|
||||
STRICT_DB_ONLY: "false"
|
||||
command: ["/app/GameChannel.dll"]
|
||||
volumes:
|
||||
- ./resources:/app/resources:ro
|
||||
- ./resources:/app/resources
|
||||
- ./config:/app/config
|
||||
ports:
|
||||
- "8000:8000"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using FluentValidation.Results;
|
||||
using MoonSharp.Interpreter;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using Plugin.Act4.Scripting.Validator;
|
||||
using WingsAPI.Scripting;
|
||||
|
|
@ -32,6 +33,9 @@ public class DungeonScriptManager : IDungeonScriptManager
|
|||
|
||||
public void Load()
|
||||
{
|
||||
EnsureDungeonScriptsDirectoryHydrated();
|
||||
Directory.CreateDirectory(_scriptFactoryConfiguration.DungeonsDirectory);
|
||||
|
||||
IEnumerable<string> files = Directory.GetFiles(_scriptFactoryConfiguration.DungeonsDirectory, "*.lua");
|
||||
foreach (string file in files)
|
||||
{
|
||||
|
|
@ -69,4 +73,63 @@ public class DungeonScriptManager : IDungeonScriptManager
|
|||
|
||||
Log.Info($"Loaded {_cache.Count} dungeons from scripts");
|
||||
}
|
||||
|
||||
private void EnsureDungeonScriptsDirectoryHydrated()
|
||||
{
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
if (!dbFirst)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Directory.Exists(_scriptFactoryConfiguration.DungeonsDirectory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
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";
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
|
||||
using var cmd = new NpgsqlCommand("SELECT relative_path, content FROM resource_files WHERE category='config_scripts' AND relative_path LIKE 'config/scripts/dungeons/%';", conn);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
int count = 0;
|
||||
while (reader.Read())
|
||||
{
|
||||
string relative = reader.GetString(0).Replace('/', Path.DirectorySeparatorChar);
|
||||
byte[] bytes = (byte[])reader[1];
|
||||
string fullPath = Path.Combine(Directory.GetCurrentDirectory(), relative);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath) ?? _scriptFactoryConfiguration.DungeonsDirectory);
|
||||
File.WriteAllBytes(fullPath, bytes);
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
Log.Info($"[DB_FIRST] Hydrated {count} dungeon scripts from resource_files");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("[DB_FIRST] Could not hydrate dungeon scripts from database", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="5.0.18" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="5.0.18" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using FluentValidation.Results;
|
||||
using MoonSharp.Interpreter;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using Plugin.Raids.Scripting.Validator.Raid;
|
||||
using WingsAPI.Scripting;
|
||||
|
|
@ -32,6 +33,9 @@ public sealed class RaidScriptManager : IRaidScriptManager
|
|||
|
||||
public void Load()
|
||||
{
|
||||
EnsureRaidScriptsDirectoryHydrated();
|
||||
Directory.CreateDirectory(_scriptFactoryConfiguration.RaidsDirectory);
|
||||
|
||||
IEnumerable<string> files = Directory.GetFiles(_scriptFactoryConfiguration.RaidsDirectory, "*.lua");
|
||||
foreach (string file in files)
|
||||
{
|
||||
|
|
@ -69,4 +73,63 @@ public sealed class RaidScriptManager : IRaidScriptManager
|
|||
|
||||
Log.Info($"Loaded {_cache.Count} raids from scripts");
|
||||
}
|
||||
|
||||
private void EnsureRaidScriptsDirectoryHydrated()
|
||||
{
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
if (!dbFirst)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Directory.Exists(_scriptFactoryConfiguration.RaidsDirectory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
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";
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
|
||||
using var cmd = new NpgsqlCommand("SELECT relative_path, content FROM resource_files WHERE category='config_scripts' AND relative_path LIKE 'config/scripts/raids/%';", conn);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
int count = 0;
|
||||
while (reader.Read())
|
||||
{
|
||||
string relative = reader.GetString(0).Replace('/', Path.DirectorySeparatorChar);
|
||||
byte[] bytes = (byte[])reader[1];
|
||||
string fullPath = Path.Combine(Directory.GetCurrentDirectory(), relative);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath) ?? _scriptFactoryConfiguration.RaidsDirectory);
|
||||
File.WriteAllBytes(fullPath, bytes);
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
Log.Info($"[DB_FIRST] Hydrated {count} raid scripts from resource_files");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("[DB_FIRST] Could not hydrate raid scripts from database", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ namespace Plugin.ResourceLoader
|
|||
services.AddSingleton<IGameDataLanguageService, InMemoryGameDataLanguageService>();
|
||||
|
||||
services.AddSingleton<IBattleEntityAlgorithmService, BattleEntityAlgorithmService>();
|
||||
services.AddHostedService<DbFirstResourceHydratorService>();
|
||||
services.AddHostedService<ResourceFilesPostgresSyncService>();
|
||||
}
|
||||
}
|
||||
|
|
@ -53,7 +54,6 @@ namespace Plugin.ResourceLoader
|
|||
{
|
||||
services.AddSingleton<IResourceLoader<GenericTranslationDto>, GenericTranslationGrpcLoader>();
|
||||
services.AddSingleton<IGameLanguageService, InMemoryMultilanguageService>();
|
||||
services.AddHostedService<ResourceFilesPostgresSyncService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using WingsAPI.Data.GameData;
|
||||
using WingsEmu.DTOs.BCards;
|
||||
|
|
@ -22,6 +23,16 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
{
|
||||
string filePath = Path.Combine(_config.GameDataPath, "Card.dat");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
if (dbFirst)
|
||||
{
|
||||
TryHydrateDatFileFromDatabase("Card.dat", filePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException($"{filePath} should be present");
|
||||
|
|
@ -199,5 +210,45 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
Log.Info($"[RESOURCE_LOADER] {cards.Count.ToString()} act desc loaded");
|
||||
return cards;
|
||||
}
|
||||
|
||||
private void TryHydrateDatFileFromDatabase(string datFileName, string targetPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
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";
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
|
||||
using var cmd = new NpgsqlCommand("SELECT content FROM resource_files WHERE category='dat' AND relative_path=@path LIMIT 1;", conn);
|
||||
cmd.Parameters.AddWithValue("path", $"dat/{datFileName}");
|
||||
object result = cmd.ExecuteScalar();
|
||||
|
||||
if (result is byte[] bytes && bytes.Length > 0)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetPath) ?? _config.GameDataPath);
|
||||
File.WriteAllBytes(targetPath, bytes);
|
||||
Log.Info($"[DB_FIRST] Hydrated {datFileName} from resource_files");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[DB_FIRST] Could not hydrate {datFileName} from database", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using PhoenixLib.MultiLanguage;
|
||||
using WingsAPI.Data.GameData;
|
||||
|
|
@ -123,10 +124,30 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
continue;
|
||||
}
|
||||
|
||||
string fileLang = $"{_config.GameLanguagePath}/{string.Format(fileToParse, ToNostaleRegionKey(lang))}";
|
||||
using var langFileStream = new StreamReader(fileLang, GetEncoding(lang));
|
||||
string line;
|
||||
while ((line = await langFileStream.ReadLineAsync()) != null)
|
||||
string fileName = string.Format(fileToParse, ToNostaleRegionKey(lang));
|
||||
string fileLang = $"{_config.GameLanguagePath}/{fileName}";
|
||||
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
IEnumerable<string> lines = null;
|
||||
|
||||
if (File.Exists(fileLang))
|
||||
{
|
||||
lines = await File.ReadAllLinesAsync(fileLang, GetEncoding(lang));
|
||||
}
|
||||
else if (dbFirst)
|
||||
{
|
||||
lines = TryLoadLangLinesFromDatabase(fileName, GetEncoding(lang));
|
||||
}
|
||||
|
||||
if (lines == null)
|
||||
{
|
||||
Log.Warn($"[DB_FIRST] Missing language source '{fileLang}', skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
string[] lineSave = line.Split('\t');
|
||||
if (lineSave.Length <= 1)
|
||||
|
|
@ -153,5 +174,38 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
|
||||
return translations;
|
||||
}
|
||||
|
||||
private IEnumerable<string> TryLoadLangLinesFromDatabase(string langFileName, Encoding encoding)
|
||||
{
|
||||
try
|
||||
{
|
||||
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";
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
|
||||
using var cmd = new NpgsqlCommand("SELECT content FROM resource_files WHERE category='lang' AND (relative_path=@path OR lower(relative_path)=lower(@path) OR lower(relative_path) LIKE lower('%/' || @name)) LIMIT 1;", conn);
|
||||
cmd.Parameters.AddWithValue("path", $"lang/{langFileName}");
|
||||
cmd.Parameters.AddWithValue("name", langFileName);
|
||||
object result = cmd.ExecuteScalar();
|
||||
|
||||
if (result is byte[] bytes && bytes.Length > 0)
|
||||
{
|
||||
string text = encoding.GetString(bytes);
|
||||
Log.Info($"[DB_FIRST] Loaded lang file {langFileName} from database");
|
||||
return text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[DB_FIRST] Could not load lang file {langFileName} from database", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using WingsAPI.Data.GameData;
|
||||
using WingsAPI.Packets.Enums.Shells;
|
||||
|
|
@ -32,6 +33,16 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
|
||||
string filePath = Path.Combine(_configuration.GameDataPath, FILE_NAME);
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
if (dbFirst)
|
||||
{
|
||||
TryHydrateItemDatFromDatabase(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException($"{filePath} should be present");
|
||||
|
|
@ -654,6 +665,43 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
item.DarkResistance = Convert.ToByte(currentLine[11]);
|
||||
}
|
||||
|
||||
private void TryHydrateItemDatFromDatabase(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
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";
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
using var cmd = new NpgsqlCommand("SELECT content FROM resource_files WHERE category='dat' AND (relative_path='dat/Item.dat' OR relative_path='Item.dat') LIMIT 1;", conn);
|
||||
object result = cmd.ExecuteScalar();
|
||||
if (result is byte[] bytes && bytes.Length > 0)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(filePath) ?? _configuration.GameDataPath);
|
||||
File.WriteAllBytes(filePath, bytes);
|
||||
Log.Info("[DB_FIRST] Hydrated Item.dat from resource_files");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("[DB_FIRST] Could not hydrate Item.dat from database", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void FillMorphAndIndexValues(string[] currentLine, ItemDTO item)
|
||||
{
|
||||
switch (Convert.ToByte(currentLine[2]))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using WingsAPI.Data.GameData;
|
||||
using WingsEmu.DTOs.Maps;
|
||||
|
|
@ -21,6 +22,17 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
{
|
||||
string filePath = Path.Combine(_config.GameDataPath, "MapIDData.dat");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
if (dbFirst)
|
||||
{
|
||||
TryHydrateDatFileFromDatabase("MapIDData.dat", filePath);
|
||||
TryHydrateMapFilesFromDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException($"{filePath} should be present");
|
||||
|
|
@ -53,6 +65,16 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
}
|
||||
}
|
||||
|
||||
if (!Directory.Exists(_config.GameMapsPath) || !new DirectoryInfo(_config.GameMapsPath).GetFiles().Any())
|
||||
{
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
if (dbFirst)
|
||||
{
|
||||
TryHydrateMapFilesFromDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (FileInfo file in new DirectoryInfo(_config.GameMapsPath).GetFiles())
|
||||
{
|
||||
string name = string.Empty;
|
||||
|
|
@ -81,5 +103,70 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
Log.Info($"[RESOURCE_LOADER] {maps.Count} Maps loaded");
|
||||
return maps;
|
||||
}
|
||||
|
||||
private void TryHydrateDatFileFromDatabase(string datFileName, string targetPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var conn = OpenConnection();
|
||||
using var cmd = new NpgsqlCommand("SELECT content FROM resource_files WHERE category='dat' AND relative_path=@path LIMIT 1;", conn);
|
||||
cmd.Parameters.AddWithValue("path", $"dat/{datFileName}");
|
||||
object result = cmd.ExecuteScalar();
|
||||
|
||||
if (result is byte[] bytes && bytes.Length > 0)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetPath) ?? _config.GameDataPath);
|
||||
File.WriteAllBytes(targetPath, bytes);
|
||||
Log.Info($"[DB_FIRST] Hydrated {datFileName} from resource_files");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[DB_FIRST] Could not hydrate {datFileName} from database", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void TryHydrateMapFilesFromDatabase()
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(_config.GameMapsPath);
|
||||
using var conn = OpenConnection();
|
||||
using var cmd = new NpgsqlCommand("SELECT relative_path, content FROM resource_files WHERE category='maps';", conn);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
int count = 0;
|
||||
while (reader.Read())
|
||||
{
|
||||
string relative = reader.GetString(0).Replace('/', Path.DirectorySeparatorChar);
|
||||
byte[] content = (byte[])reader[1];
|
||||
string path = Path.Combine(_config.ResourcePaths, relative);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path) ?? _config.GameMapsPath);
|
||||
File.WriteAllBytes(path, content);
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
Log.Info($"[DB_FIRST] Hydrated {count} map files from resource_files");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("[DB_FIRST] Could not hydrate map files from database", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static NpgsqlConnection OpenConnection()
|
||||
{
|
||||
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";
|
||||
|
||||
var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
return conn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using WingsAPI.Data.Drops;
|
||||
using WingsAPI.Data.GameData;
|
||||
|
|
@ -40,6 +41,16 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
|
||||
string filePath = Path.Combine(_config.GameDataPath, "monster.dat");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
if (dbFirst)
|
||||
{
|
||||
TryHydrateDatFileFromDatabase("monster.dat", filePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException($"{filePath} should be present");
|
||||
|
|
@ -468,6 +479,46 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
};
|
||||
}
|
||||
|
||||
private void TryHydrateDatFileFromDatabase(string datFileName, string targetPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
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";
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
|
||||
using var cmd = new NpgsqlCommand("SELECT content FROM resource_files WHERE category='dat' AND relative_path=@path LIMIT 1;", conn);
|
||||
cmd.Parameters.AddWithValue("path", $"dat/{datFileName}");
|
||||
object result = cmd.ExecuteScalar();
|
||||
|
||||
if (result is byte[] bytes && bytes.Length > 0)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetPath) ?? _config.GameDataPath);
|
||||
File.WriteAllBytes(targetPath, bytes);
|
||||
Log.Info($"[DB_FIRST] Hydrated {datFileName} from resource_files");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[DB_FIRST] Could not hydrate {datFileName} from database", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private enum MobFlag : long
|
||||
{
|
||||
CANT_WALK = 1,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using WingsAPI.Data.GameData;
|
||||
using WingsEmu.DTOs.Quests;
|
||||
|
|
@ -18,6 +20,16 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
{
|
||||
string filePath = Path.Combine(_configuration.GameDataPath, "qstnpc.dat");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
if (dbFirst)
|
||||
{
|
||||
TryHydrateDatFileFromDatabase("qstnpc.dat", filePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException($"{filePath} should be present");
|
||||
|
|
@ -66,5 +78,45 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
Log.Info($"[RESOURCE_LOADER] {npcQuests.Count.ToString()} NPC quests loaded");
|
||||
return npcQuests;
|
||||
}
|
||||
|
||||
private void TryHydrateDatFileFromDatabase(string datFileName, string targetPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
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";
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
|
||||
using var cmd = new NpgsqlCommand("SELECT content FROM resource_files WHERE category='dat' AND relative_path=@path LIMIT 1;", conn);
|
||||
cmd.Parameters.AddWithValue("path", $"dat/{datFileName}");
|
||||
object result = cmd.ExecuteScalar();
|
||||
|
||||
if (result is byte[] bytes && bytes.Length > 0)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetPath) ?? _configuration.GameDataPath);
|
||||
File.WriteAllBytes(targetPath, bytes);
|
||||
Log.Info($"[DB_FIRST] Hydrated {datFileName} from resource_files");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[DB_FIRST] Could not hydrate {datFileName} from database", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CloneExtensions;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using WingsAPI.Data.GameData;
|
||||
using WingsEmu.DTOs.Quests;
|
||||
|
|
@ -29,6 +30,17 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
string fileQuestPath = Path.Combine(_configuration.GameDataPath, "quest.dat");
|
||||
string fileRewardsPath = Path.Combine(_configuration.GameDataPath, "qstprize.dat");
|
||||
|
||||
if (!File.Exists(fileQuestPath) || !File.Exists(fileRewardsPath))
|
||||
{
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
if (dbFirst)
|
||||
{
|
||||
TryHydrateDatFileFromDatabase("quest.dat", fileQuestPath);
|
||||
TryHydrateDatFileFromDatabase("qstprize.dat", fileRewardsPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(fileQuestPath))
|
||||
{
|
||||
throw new FileNotFoundException($"{fileQuestPath} should be present");
|
||||
|
|
@ -144,6 +156,46 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
return _quests;
|
||||
}
|
||||
|
||||
private void TryHydrateDatFileFromDatabase(string datFileName, string targetPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
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";
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
|
||||
using var cmd = new NpgsqlCommand("SELECT content FROM resource_files WHERE category='dat' AND relative_path=@path LIMIT 1;", conn);
|
||||
cmd.Parameters.AddWithValue("path", $"dat/{datFileName}");
|
||||
object result = cmd.ExecuteScalar();
|
||||
|
||||
if (result is byte[] bytes && bytes.Length > 0)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetPath) ?? _configuration.GameDataPath);
|
||||
File.WriteAllBytes(targetPath, bytes);
|
||||
Log.Info($"[DB_FIRST] Hydrated {datFileName} from resource_files");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[DB_FIRST] Could not hydrate {datFileName} from database", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void FillRewards(string fileRewardsPath, Dictionary<long, QuestPrizeDto> dictionaryRewards)
|
||||
{
|
||||
using var questRewardStream = new StreamReader(fileRewardsPath, Encoding.GetEncoding(1252));
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using WingsAPI.Data.GameData;
|
||||
using WingsEmu.DTOs.BCards;
|
||||
|
|
@ -33,6 +34,25 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
return _skills;
|
||||
}
|
||||
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
bool strictDbOnly = string.Equals(Environment.GetEnvironmentVariable("STRICT_DB_ONLY"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (dbFirst)
|
||||
{
|
||||
int loadedFromDb = LoadSkillsFromDatabase();
|
||||
if (loadedFromDb > 0)
|
||||
{
|
||||
Log.Info($"[RESOURCE_LOADER] {loadedFromDb} Skills loaded from database");
|
||||
return _skills;
|
||||
}
|
||||
|
||||
if (strictDbOnly)
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but no skills were loaded from database.");
|
||||
}
|
||||
}
|
||||
|
||||
string filePath = Path.Combine(_config.GameDataPath, "Skill.dat");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
|
|
@ -187,6 +207,66 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
return _skills;
|
||||
}
|
||||
|
||||
private int LoadSkillsFromDatabase()
|
||||
{
|
||||
try
|
||||
{
|
||||
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";
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
|
||||
using var cmd = new NpgsqlCommand(@"SELECT id, name, class, cast_id, cast_time, cooldown, mp_cost, cp_cost, skill_type, attack_type, hit_type, target_type, range, aoe_range, level_minimum, element
|
||||
FROM skills ORDER BY id;", conn);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
var skill = new SkillDTO
|
||||
{
|
||||
Id = reader.GetInt32(0),
|
||||
Name = reader.IsDBNull(1) ? string.Empty : reader.GetString(1),
|
||||
Class = Convert.ToByte(reader.GetInt32(2)),
|
||||
CastId = Convert.ToInt16(reader.GetInt32(3)),
|
||||
CastTime = Convert.ToInt16(reader.GetInt32(4)),
|
||||
Cooldown = Convert.ToInt16(reader.GetInt32(5)),
|
||||
MpCost = Convert.ToInt16(reader.GetInt32(6)),
|
||||
CPCost = Convert.ToByte(reader.GetInt32(7)),
|
||||
SkillType = (SkillType)reader.GetInt32(8),
|
||||
AttackType = (AttackType)reader.GetInt32(9),
|
||||
HitType = (TargetHitType)reader.GetInt32(10),
|
||||
TargetType = (TargetType)reader.GetInt32(11),
|
||||
Range = Convert.ToByte(reader.GetInt32(12)),
|
||||
AoERange = Convert.ToInt16(reader.GetInt32(13)),
|
||||
LevelMinimum = Convert.ToByte(reader.GetInt32(14)),
|
||||
Element = Convert.ToByte(reader.GetInt32(15))
|
||||
};
|
||||
|
||||
_skills.Add(skill);
|
||||
}
|
||||
|
||||
return _skills.Count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Could not load skills from database, falling back to file parser");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void FillLevelInformation(SkillDTO skill, IReadOnlyList<string> currentLine, IReadOnlyCollection<SkillDTO> skills)
|
||||
{
|
||||
skill.LevelMinimum = currentLine[2] != "-1" ? byte.Parse(currentLine[2]) : (byte)0;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using WingsAPI.Data.GameData;
|
||||
using WingsEmu.DTOs.Quests;
|
||||
|
|
@ -20,6 +21,16 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
{
|
||||
string filePath = Path.Combine(_configuration.GameDataPath, "tutorial.dat");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
if (dbFirst)
|
||||
{
|
||||
TryHydrateDatFileFromDatabase("tutorial.dat", filePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException($"{filePath} should be present");
|
||||
|
|
@ -99,5 +110,45 @@ namespace Plugin.ResourceLoader.Loaders
|
|||
Log.Info($"[RESOURCE_LOADER] {scriptDatas.Count.ToString()} Tutorial Scripts loaded");
|
||||
return scriptDatas;
|
||||
}
|
||||
|
||||
private void TryHydrateDatFileFromDatabase(string datFileName, string targetPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
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";
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
|
||||
using var cmd = new NpgsqlCommand("SELECT content FROM resource_files WHERE category='dat' AND relative_path=@path LIMIT 1;", conn);
|
||||
cmd.Parameters.AddWithValue("path", $"dat/{datFileName}");
|
||||
object result = cmd.ExecuteScalar();
|
||||
|
||||
if (result is byte[] bytes && bytes.Length > 0)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetPath) ?? _configuration.GameDataPath);
|
||||
File.WriteAllBytes(targetPath, bytes);
|
||||
Log.Info($"[DB_FIRST] Hydrated {datFileName} from resource_files");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[DB_FIRST] Could not hydrate {datFileName} from database", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
|
||||
namespace Plugin.ResourceLoader.Services
|
||||
{
|
||||
public class DbFirstResourceHydratorService : IHostedService
|
||||
{
|
||||
private readonly ResourceLoadingConfiguration _configuration;
|
||||
|
||||
public DbFirstResourceHydratorService(ResourceLoadingConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
if (!dbFirst)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
bool strictDbOnly = string.Equals(Environment.GetEnvironmentVariable("STRICT_DB_ONLY"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
try
|
||||
{
|
||||
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";
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
|
||||
var counters = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["dat"] = 0,
|
||||
["lang"] = 0,
|
||||
["maps"] = 0,
|
||||
["config_scripts"] = 0
|
||||
};
|
||||
|
||||
using var cmd = new NpgsqlCommand("SELECT category, relative_path, content FROM resource_files;", conn);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
string category = reader.GetString(0);
|
||||
string relativePath = reader.GetString(1).Replace('/', Path.DirectorySeparatorChar);
|
||||
byte[] content = (byte[])reader[2];
|
||||
|
||||
string? destination = category switch
|
||||
{
|
||||
"dat" => Path.Combine(_configuration.ResourcePaths, relativePath),
|
||||
"lang" => Path.Combine(_configuration.ResourcePaths, relativePath),
|
||||
"maps" => Path.Combine(_configuration.ResourcePaths, relativePath),
|
||||
"config_scripts" => Path.Combine(Directory.GetCurrentDirectory(), relativePath),
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (destination == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string? dir = Path.GetDirectoryName(destination);
|
||||
if (!string.IsNullOrWhiteSpace(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
File.WriteAllBytes(destination, content);
|
||||
if (counters.ContainsKey(category))
|
||||
{
|
||||
counters[category]++;
|
||||
}
|
||||
}
|
||||
|
||||
if (strictDbOnly && (counters["dat"] == 0 || counters["lang"] == 0 || counters["maps"] == 0))
|
||||
{
|
||||
throw new InvalidOperationException("STRICT_DB_ONLY enabled but one or more required resource categories are missing in resource_files.");
|
||||
}
|
||||
|
||||
Log.Info($"[DB_FIRST] Hydrated files from DB: dat={counters["dat"]} lang={counters["lang"]} maps={counters["maps"]} scripts={counters["config_scripts"]}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("[DB_FIRST] Failed to hydrate resources from database", ex);
|
||||
if (strictDbOnly)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
|
|
@ -7,16 +8,21 @@ using System.Threading.Tasks;
|
|||
using Microsoft.Extensions.Hosting;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using WingsAPI.Data.GameData;
|
||||
using WingsEmu.DTOs.BCards;
|
||||
using WingsEmu.DTOs.Skills;
|
||||
|
||||
namespace Plugin.ResourceLoader.Services
|
||||
{
|
||||
public class ResourceFilesPostgresSyncService : IHostedService
|
||||
{
|
||||
private readonly ResourceLoadingConfiguration _configuration;
|
||||
private readonly IResourceLoader<SkillDTO> _skillLoader;
|
||||
|
||||
public ResourceFilesPostgresSyncService(ResourceLoadingConfiguration configuration)
|
||||
public ResourceFilesPostgresSyncService(ResourceLoadingConfiguration configuration, IResourceLoader<SkillDTO> skillLoader)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_skillLoader = skillLoader;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
|
|
@ -47,6 +53,8 @@ namespace Plugin.ResourceLoader.Services
|
|||
string root = _configuration.ResourcePaths;
|
||||
string datPath = _configuration.GameDataPath;
|
||||
string langPath = _configuration.GameLanguagePath;
|
||||
string mapsPath = _configuration.GameMapsPath;
|
||||
string configScriptsPath = Path.Combine(Directory.GetCurrentDirectory(), "config", "scripts");
|
||||
|
||||
string[] datFiles = Directory.Exists(datPath)
|
||||
? Directory.GetFiles(datPath, "*", SearchOption.AllDirectories)
|
||||
|
|
@ -54,6 +62,12 @@ namespace Plugin.ResourceLoader.Services
|
|||
string[] langFiles = Directory.Exists(langPath)
|
||||
? Directory.GetFiles(langPath, "*", SearchOption.AllDirectories)
|
||||
: Array.Empty<string>();
|
||||
string[] mapFiles = Directory.Exists(mapsPath)
|
||||
? Directory.GetFiles(mapsPath, "*", SearchOption.AllDirectories)
|
||||
: Array.Empty<string>();
|
||||
string[] scriptFiles = Directory.Exists(configScriptsPath)
|
||||
? Directory.GetFiles(configScriptsPath, "*", SearchOption.AllDirectories)
|
||||
: Array.Empty<string>();
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
|
|
@ -68,6 +82,42 @@ namespace Plugin.ResourceLoader.Services
|
|||
size_bytes INT NOT NULL,
|
||||
synced_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(category, relative_path)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS skills (
|
||||
id INT PRIMARY KEY,
|
||||
name TEXT,
|
||||
class INT,
|
||||
cast_id INT,
|
||||
cast_time INT,
|
||||
cooldown INT,
|
||||
mp_cost INT,
|
||||
cp_cost INT,
|
||||
skill_type INT,
|
||||
attack_type INT,
|
||||
hit_type INT,
|
||||
target_type INT,
|
||||
range INT,
|
||||
aoe_range INT,
|
||||
level_minimum INT,
|
||||
element INT,
|
||||
data_json JSONB,
|
||||
synced_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS skill_bcards (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
skill_id INT NOT NULL,
|
||||
bcard_type INT,
|
||||
bcard_subtype INT,
|
||||
first_data INT,
|
||||
second_data INT,
|
||||
cast_type INT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS skill_combos (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
skill_id INT NOT NULL,
|
||||
hit INT,
|
||||
animation INT,
|
||||
effect INT
|
||||
);", conn, tx))
|
||||
{
|
||||
cmd.ExecuteNonQuery();
|
||||
|
|
@ -75,9 +125,13 @@ namespace Plugin.ResourceLoader.Services
|
|||
|
||||
UpsertFiles(conn, tx, root, "dat", datFiles);
|
||||
UpsertFiles(conn, tx, root, "lang", langFiles);
|
||||
UpsertFiles(conn, tx, root, "maps", mapFiles);
|
||||
UpsertFiles(conn, tx, Directory.GetCurrentDirectory(), "config_scripts", scriptFiles);
|
||||
|
||||
SyncSkills(conn, tx, _skillLoader.LoadAsync().GetAwaiter().GetResult());
|
||||
|
||||
tx.Commit();
|
||||
Log.Info($"[PARSER_DB_SYNC] Synced resource_files dat={datFiles.Length} lang={langFiles.Length}");
|
||||
Log.Info($"[PARSER_DB_SYNC] Synced resource_files dat={datFiles.Length} lang={langFiles.Length} maps={mapFiles.Length} scripts={scriptFiles.Length} skills=ok");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -114,5 +168,61 @@ DO UPDATE SET sha256=EXCLUDED.sha256, content=EXCLUDED.content, size_bytes=EXCLU
|
|||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
private static void SyncSkills(NpgsqlConnection conn, NpgsqlTransaction tx, IReadOnlyList<SkillDTO> skills)
|
||||
{
|
||||
using (var cmd = new NpgsqlCommand("TRUNCATE TABLE skills, skill_bcards, skill_combos RESTART IDENTITY;", conn, tx))
|
||||
{
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
foreach (SkillDTO s in skills)
|
||||
{
|
||||
using (var cmd = new NpgsqlCommand(@"INSERT INTO skills(id,name,class,cast_id,cast_time,cooldown,mp_cost,cp_cost,skill_type,attack_type,hit_type,target_type,range,aoe_range,level_minimum,element,data_json,synced_at)
|
||||
VALUES (@id,@name,@class,@castId,@castTime,@cooldown,@mp,@cp,@stype,@atype,@htype,@ttype,@range,@aoe,@lvl,@element,@json::jsonb,NOW());", conn, tx))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("id", s.Id);
|
||||
cmd.Parameters.AddWithValue("name", (object?)s.Name ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("class", s.Class);
|
||||
cmd.Parameters.AddWithValue("castId", s.CastId);
|
||||
cmd.Parameters.AddWithValue("castTime", s.CastTime);
|
||||
cmd.Parameters.AddWithValue("cooldown", s.Cooldown);
|
||||
cmd.Parameters.AddWithValue("mp", s.MpCost);
|
||||
cmd.Parameters.AddWithValue("cp", s.CPCost);
|
||||
cmd.Parameters.AddWithValue("stype", (int)s.SkillType);
|
||||
cmd.Parameters.AddWithValue("atype", (int)s.AttackType);
|
||||
cmd.Parameters.AddWithValue("htype", (int)s.HitType);
|
||||
cmd.Parameters.AddWithValue("ttype", (int)s.TargetType);
|
||||
cmd.Parameters.AddWithValue("range", s.Range);
|
||||
cmd.Parameters.AddWithValue("aoe", s.AoERange);
|
||||
cmd.Parameters.AddWithValue("lvl", s.LevelMinimum);
|
||||
cmd.Parameters.AddWithValue("element", s.Element);
|
||||
cmd.Parameters.AddWithValue("json", System.Text.Json.JsonSerializer.Serialize(s));
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
foreach (BCardDTO b in s.BCards ?? Enumerable.Empty<BCardDTO>())
|
||||
{
|
||||
using var bcmd = new NpgsqlCommand("INSERT INTO skill_bcards(skill_id,bcard_type,bcard_subtype,first_data,second_data,cast_type) VALUES (@sid,@t,@st,@f,@sec,@c);", conn, tx);
|
||||
bcmd.Parameters.AddWithValue("sid", s.Id);
|
||||
bcmd.Parameters.AddWithValue("t", b.Type);
|
||||
bcmd.Parameters.AddWithValue("st", b.SubType);
|
||||
bcmd.Parameters.AddWithValue("f", b.FirstData);
|
||||
bcmd.Parameters.AddWithValue("sec", b.SecondData);
|
||||
bcmd.Parameters.AddWithValue("c", b.CastType);
|
||||
bcmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
foreach (ComboDTO c in s.Combos ?? Enumerable.Empty<ComboDTO>())
|
||||
{
|
||||
using var ccmd = new NpgsqlCommand("INSERT INTO skill_combos(skill_id,hit,animation,effect) VALUES (@sid,@h,@a,@e);", conn, tx);
|
||||
ccmd.Parameters.AddWithValue("sid", s.Id);
|
||||
ccmd.Parameters.AddWithValue("h", c.Hit);
|
||||
ccmd.Parameters.AddWithValue("a", c.Animation);
|
||||
ccmd.Parameters.AddWithValue("e", c.Effect);
|
||||
ccmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using MoonSharp.Interpreter;
|
||||
using Npgsql;
|
||||
using PhoenixLib.Logging;
|
||||
using WingsAPI.Scripting;
|
||||
using WingsAPI.Scripting.Attribute;
|
||||
|
|
@ -34,6 +35,9 @@ public class LuaTimeSpaceScriptManager : ITimeSpaceScriptManager
|
|||
|
||||
public void Load()
|
||||
{
|
||||
EnsureTimespaceScriptsDirectoryHydrated();
|
||||
Directory.CreateDirectory(_scriptFactoryConfiguration.TimeSpacesDirectory);
|
||||
|
||||
IEnumerable<string> files = Directory.GetFiles(_scriptFactoryConfiguration.TimeSpacesDirectory, "*.lua");
|
||||
foreach (string file in files)
|
||||
{
|
||||
|
|
@ -75,4 +79,63 @@ public class LuaTimeSpaceScriptManager : ITimeSpaceScriptManager
|
|||
|
||||
return timeSpace;
|
||||
}
|
||||
|
||||
private void EnsureTimespaceScriptsDirectoryHydrated()
|
||||
{
|
||||
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
if (!dbFirst)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Directory.Exists(_scriptFactoryConfiguration.TimeSpacesDirectory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
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";
|
||||
|
||||
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||
conn.Open();
|
||||
|
||||
using var cmd = new NpgsqlCommand("SELECT relative_path, content FROM resource_files WHERE category='config_scripts' AND relative_path LIKE 'config/scripts/timespaces/%';", conn);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
int count = 0;
|
||||
while (reader.Read())
|
||||
{
|
||||
string relative = reader.GetString(0).Replace('/', Path.DirectorySeparatorChar);
|
||||
byte[] bytes = (byte[])reader[1];
|
||||
string fullPath = Path.Combine(Directory.GetCurrentDirectory(), relative);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath) ?? _scriptFactoryConfiguration.TimeSpacesDirectory);
|
||||
File.WriteAllBytes(fullPath, bytes);
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
Log.Info($"[DB_FIRST] Hydrated {count} timespace scripts from resource_files");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("[DB_FIRST] Could not hydrate timespace scripts from database", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="5.0.18" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
//
|
||||
// Developed by NosWings Team
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using PhoenixLib.Caching;
|
||||
using PhoenixLib.Logging;
|
||||
using WingsAPI.Data.Drops;
|
||||
using WingsEmu.Game._enum;
|
||||
using WingsEmu.Game.Managers.ServerData;
|
||||
|
|
@ -31,14 +33,43 @@ public class DropManager : IDropManager
|
|||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
var drops = new List<DropDTO>();
|
||||
List<DropDTO> drops = null;
|
||||
bool dbFirst = ParserDataPostgresReader.DbFirstEnabled;
|
||||
bool strictDbOnly = ParserDataPostgresReader.StrictDbOnlyEnabled;
|
||||
|
||||
foreach (DropImportFile dropImportExportFile in _dropConfigurations)
|
||||
if (dbFirst)
|
||||
{
|
||||
drops.AddRange(dropImportExportFile.Drops.SelectMany(s => s.ToDto()));
|
||||
try
|
||||
{
|
||||
drops = ParserDataPostgresReader.LoadDrops();
|
||||
Log.Info($"[DB_FIRST] Loaded {drops.Count} drops from database");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (strictDbOnly)
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but failed to load drops from database.", e);
|
||||
}
|
||||
|
||||
Log.Error("[DB_FIRST] Could not load drops from database", e);
|
||||
}
|
||||
|
||||
if (strictDbOnly && (drops == null || drops.Count == 0))
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but no drops were loaded from database.");
|
||||
}
|
||||
}
|
||||
|
||||
ParserDataPostgresSync.SyncDrops(drops);
|
||||
if (drops == null || drops.Count == 0)
|
||||
{
|
||||
drops = new List<DropDTO>();
|
||||
foreach (DropImportFile dropImportExportFile in _dropConfigurations)
|
||||
{
|
||||
drops.AddRange(dropImportExportFile.Drops.SelectMany(s => s.ToDto()));
|
||||
}
|
||||
|
||||
ParserDataPostgresSync.SyncDrops(drops);
|
||||
}
|
||||
|
||||
foreach (DropDTO drop in drops)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PhoenixLib.Caching;
|
||||
using PhoenixLib.Logging;
|
||||
|
|
@ -26,39 +27,69 @@ public class ItemBoxManager : IItemBoxManager
|
|||
|
||||
public void Initialize()
|
||||
{
|
||||
int boxesCount = 0;
|
||||
var allBoxes = new List<ItemBoxDto>();
|
||||
foreach (ItemBoxImportFile file in _itemBoxConfigurations)
|
||||
List<ItemBoxDto> allBoxes = null;
|
||||
bool dbFirst = ParserDataPostgresReader.DbFirstEnabled;
|
||||
bool strictDbOnly = ParserDataPostgresReader.StrictDbOnlyEnabled;
|
||||
|
||||
if (dbFirst)
|
||||
{
|
||||
ItemBoxDto box = file.ToDto();
|
||||
if (box == null)
|
||||
try
|
||||
{
|
||||
continue;
|
||||
allBoxes = ParserDataPostgresReader.LoadItemBoxes();
|
||||
Log.Info($"[DB_FIRST] Loaded {allBoxes.Count} item_boxes from database");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (strictDbOnly)
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but failed to load item_boxes from database.", e);
|
||||
}
|
||||
|
||||
Log.Error("[DB_FIRST] Could not load item_boxes from database", e);
|
||||
}
|
||||
|
||||
// just the item box itself
|
||||
allBoxes.Add(box);
|
||||
_itemBoxesCache.Set(box.Id.ToString(), box);
|
||||
boxesCount++;
|
||||
if (strictDbOnly && (allBoxes == null || allBoxes.Count == 0))
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but no item_boxes were loaded from database.");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (RandomBoxImportFile file in _randomBoxConfigurations)
|
||||
if (allBoxes == null || allBoxes.Count == 0)
|
||||
{
|
||||
foreach (RandomBoxObject obj in file.Items)
|
||||
allBoxes = new List<ItemBoxDto>();
|
||||
foreach (ItemBoxImportFile file in _itemBoxConfigurations)
|
||||
{
|
||||
ItemBoxDto box = obj.ToDtos();
|
||||
ItemBoxDto box = file.ToDto();
|
||||
if (box == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
allBoxes.Add(box);
|
||||
_itemBoxesCache.Set(box.Id.ToString(), box);
|
||||
boxesCount++;
|
||||
}
|
||||
|
||||
foreach (RandomBoxImportFile file in _randomBoxConfigurations)
|
||||
{
|
||||
foreach (RandomBoxObject obj in file.Items)
|
||||
{
|
||||
ItemBoxDto box = obj.ToDtos();
|
||||
if (box == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
allBoxes.Add(box);
|
||||
}
|
||||
}
|
||||
|
||||
ParserDataPostgresSync.SyncItemBoxes(allBoxes);
|
||||
}
|
||||
|
||||
ParserDataPostgresSync.SyncItemBoxes(allBoxes);
|
||||
Log.Info($"[ITEMBOX_MANAGER] Loaded {boxesCount} itemBoxes");
|
||||
foreach (ItemBoxDto box in allBoxes)
|
||||
{
|
||||
_itemBoxesCache.Set(box.Id.ToString(), box);
|
||||
}
|
||||
|
||||
Log.Info($"[ITEMBOX_MANAGER] Loaded {allBoxes.Count} itemBoxes");
|
||||
}
|
||||
}
|
||||
|
|
@ -68,6 +68,8 @@ public class MapManager : IMapManager
|
|||
|
||||
public async Task Initialize()
|
||||
{
|
||||
bool dbFirst = ParserDataPostgresReader.DbFirstEnabled;
|
||||
bool strictDbOnly = ParserDataPostgresReader.StrictDbOnlyEnabled;
|
||||
int count = 0;
|
||||
IEnumerable<MapDataDTO> maps = await _mapLoader.LoadAsync();
|
||||
foreach (MapDataDTO map in maps)
|
||||
|
|
@ -79,7 +81,39 @@ public class MapManager : IMapManager
|
|||
Log.Info($"[MAP_MANAGER] Loaded {count.ToString()} MapClientData");
|
||||
|
||||
count = 0;
|
||||
IEnumerable<PortalDTO> portals = _portalConfigurationFiles.SelectMany(s => s.Portals.Select(p => p.ToDto())).ToList();
|
||||
List<PortalDTO> portals = null;
|
||||
List<ServerMapDto> configuredMaps = null;
|
||||
if (dbFirst)
|
||||
{
|
||||
try
|
||||
{
|
||||
configuredMaps = ParserDataPostgresReader.LoadServerMaps();
|
||||
portals = ParserDataPostgresReader.LoadMapPortals();
|
||||
Log.Info($"[DB_FIRST] Loaded maps={configuredMaps.Count} portals={portals.Count} from database");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (strictDbOnly)
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but failed to load server maps/portals from database.", e);
|
||||
}
|
||||
|
||||
Log.Error("[DB_FIRST] Could not load server maps/portals from database", e);
|
||||
}
|
||||
|
||||
if (strictDbOnly && (configuredMaps == null || configuredMaps.Count == 0))
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but no server_maps were loaded from database.");
|
||||
}
|
||||
}
|
||||
|
||||
if (portals == null || configuredMaps == null || configuredMaps.Count == 0)
|
||||
{
|
||||
portals = _portalConfigurationFiles.SelectMany(s => s.Portals.Select(p => p.ToDto())).ToList();
|
||||
configuredMaps = _mapConfigurations.SelectMany(s => s.Select(p => p.ToDto())).ToList();
|
||||
ParserDataPostgresSync.SyncMapsAndPortals(configuredMaps, portals.ToList());
|
||||
}
|
||||
|
||||
foreach (PortalDTO portal in portals)
|
||||
{
|
||||
if (!_portalDataByMapId.TryGetValue(portal.SourceMapId, out List<PortalDTO> portalDtos))
|
||||
|
|
@ -95,8 +129,6 @@ public class MapManager : IMapManager
|
|||
DateTime initTime = DateTime.UtcNow;
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -26,12 +27,42 @@ public class MapMonsterManager : IMapMonsterManager
|
|||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
var monsters = _files.SelectMany(x => x.Monsters.Select(s =>
|
||||
List<MapMonsterDTO> monsters = null;
|
||||
bool dbFirst = ParserDataPostgresReader.DbFirstEnabled;
|
||||
bool strictDbOnly = ParserDataPostgresReader.StrictDbOnlyEnabled;
|
||||
|
||||
if (dbFirst)
|
||||
{
|
||||
s.MapId = x.MapId;
|
||||
return s.ToDto();
|
||||
})).ToList();
|
||||
ParserDataPostgresSync.SyncMapMonsters(monsters);
|
||||
try
|
||||
{
|
||||
monsters = ParserDataPostgresReader.LoadMapMonsters();
|
||||
Log.Info($"[DB_FIRST] Loaded {monsters.Count} map_monsters from database");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (strictDbOnly)
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but failed to load map_monsters from database.", e);
|
||||
}
|
||||
|
||||
Log.Error("[DB_FIRST] Could not load map_monsters from database", e);
|
||||
}
|
||||
|
||||
if (strictDbOnly && (monsters == null || monsters.Count == 0))
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but no map_monsters were loaded from database.");
|
||||
}
|
||||
}
|
||||
|
||||
if (monsters == null || monsters.Count == 0)
|
||||
{
|
||||
monsters = _files.SelectMany(x => x.Monsters.Select(s =>
|
||||
{
|
||||
s.MapId = x.MapId;
|
||||
return s.ToDto();
|
||||
})).ToList();
|
||||
ParserDataPostgresSync.SyncMapMonsters(monsters);
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
foreach (MapMonsterDTO npcDto in monsters)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -26,14 +27,44 @@ public class MapNpcManager : IMapNpcManager
|
|||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
IEnumerable<MapNpcObject> importedNpcs = _mapNpcConfigurations.SelectMany(x => x.Npcs.Select(s =>
|
||||
{
|
||||
s.MapId = x.MapId;
|
||||
return s;
|
||||
}));
|
||||
List<MapNpcDTO> npcs = null;
|
||||
bool dbFirst = ParserDataPostgresReader.DbFirstEnabled;
|
||||
bool strictDbOnly = ParserDataPostgresReader.StrictDbOnlyEnabled;
|
||||
|
||||
var npcs = importedNpcs.Select(s => s.ToDto()).ToList();
|
||||
ParserDataPostgresSync.SyncMapNpcs(npcs);
|
||||
if (dbFirst)
|
||||
{
|
||||
try
|
||||
{
|
||||
npcs = ParserDataPostgresReader.LoadMapNpcs();
|
||||
Log.Info($"[DB_FIRST] Loaded {npcs.Count} map_npcs from database");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (strictDbOnly)
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but failed to load map_npcs from database.", e);
|
||||
}
|
||||
|
||||
Log.Error("[DB_FIRST] Could not load map_npcs from database", e);
|
||||
}
|
||||
|
||||
if (strictDbOnly && (npcs == null || npcs.Count == 0))
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but no map_npcs were loaded from database.");
|
||||
}
|
||||
}
|
||||
|
||||
if (npcs == null || npcs.Count == 0)
|
||||
{
|
||||
IEnumerable<MapNpcObject> importedNpcs = _mapNpcConfigurations.SelectMany(x => x.Npcs.Select(s =>
|
||||
{
|
||||
s.MapId = x.MapId;
|
||||
return s;
|
||||
}));
|
||||
|
||||
npcs = importedNpcs.Select(s => s.ToDto()).ToList();
|
||||
ParserDataPostgresSync.SyncMapNpcs(npcs);
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
foreach (MapNpcDTO npcDto in npcs)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,384 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Npgsql;
|
||||
using WingsAPI.Data.Drops;
|
||||
using WingsAPI.Data.Shops;
|
||||
using WingsEmu.DTOs.Maps;
|
||||
using WingsEmu.DTOs.Recipes;
|
||||
using WingsEmu.DTOs.ServerDatas;
|
||||
using WingsEmu.DTOs.Shops;
|
||||
using WingsEmu.Packets.Enums;
|
||||
|
||||
namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence;
|
||||
|
||||
public static class ParserDataPostgresReader
|
||||
{
|
||||
public static bool DbFirstEnabled => string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool StrictDbOnlyEnabled => string.Equals(Environment.GetEnvironmentVariable("STRICT_DB_ONLY"), "true", 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 List<DropDTO> LoadDrops()
|
||||
{
|
||||
var result = new List<DropDTO>();
|
||||
using var conn = new NpgsqlConnection(BuildConnectionString());
|
||||
conn.Open();
|
||||
using var cmd = new NpgsqlCommand("SELECT drop_id, amount, drop_chance, item_vnum, map_id, monster_vnum, race_type, race_sub_type FROM drops;", conn);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
result.Add(new DropDTO
|
||||
{
|
||||
Id = reader.GetInt32(0),
|
||||
Amount = reader.GetInt32(1),
|
||||
DropChance = reader.GetInt32(2),
|
||||
ItemVNum = reader.GetInt32(3),
|
||||
MapId = reader.IsDBNull(4) ? null : reader.GetInt32(4),
|
||||
MonsterVNum = reader.IsDBNull(5) ? null : reader.GetInt32(5),
|
||||
RaceType = reader.IsDBNull(6) ? null : reader.GetInt32(6),
|
||||
RaceSubType = reader.IsDBNull(7) ? null : reader.GetInt32(7)
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<MapMonsterDTO> LoadMapMonsters()
|
||||
{
|
||||
var result = new List<MapMonsterDTO>();
|
||||
using var conn = new NpgsqlConnection(BuildConnectionString());
|
||||
conn.Open();
|
||||
using var cmd = new NpgsqlCommand("SELECT map_monster_id, map_id, vnum, map_x, map_y, direction, can_move FROM map_monsters;", conn);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
result.Add(new MapMonsterDTO
|
||||
{
|
||||
Id = reader.GetInt32(0),
|
||||
MapId = reader.GetInt32(1),
|
||||
MonsterVNum = reader.GetInt32(2),
|
||||
MapX = reader.GetInt16(3),
|
||||
MapY = reader.GetInt16(4),
|
||||
Direction = reader.IsDBNull(5) ? (byte)0 : reader.GetByte(5),
|
||||
IsMoving = !reader.IsDBNull(6) && reader.GetBoolean(6)
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<MapNpcDTO> LoadMapNpcs()
|
||||
{
|
||||
var result = new List<MapNpcDTO>();
|
||||
using var conn = new NpgsqlConnection(BuildConnectionString());
|
||||
conn.Open();
|
||||
using var cmd = new NpgsqlCommand("SELECT map_npc_id, map_id, vnum, pos_x, pos_y, effect_vnum, effect_delay, dialog_id, direction_facing FROM map_npcs;", conn);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
result.Add(new MapNpcDTO
|
||||
{
|
||||
Id = reader.GetInt32(0),
|
||||
MapId = reader.GetInt32(1),
|
||||
NpcVNum = reader.GetInt32(2),
|
||||
MapX = reader.GetInt16(3),
|
||||
MapY = reader.GetInt16(4),
|
||||
Effect = reader.IsDBNull(5) ? (short)0 : reader.GetInt16(5),
|
||||
EffectDelay = reader.IsDBNull(6) ? (short)0 : reader.GetInt16(6),
|
||||
Dialog = reader.IsDBNull(7) ? (short)0 : reader.GetInt16(7),
|
||||
Direction = reader.IsDBNull(8) ? (byte)0 : reader.GetByte(8)
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<ShopDTO> LoadShops()
|
||||
{
|
||||
var shops = new Dictionary<int, ShopDTO>();
|
||||
using var conn = new NpgsqlConnection(BuildConnectionString());
|
||||
conn.Open();
|
||||
|
||||
using (var cmd = new NpgsqlCommand("SELECT map_npc_id, menu_type, shop_type, name FROM shops;", conn))
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
int mapNpcId = reader.GetInt32(0);
|
||||
shops[mapNpcId] = new ShopDTO
|
||||
{
|
||||
MapNpcId = mapNpcId,
|
||||
MenuType = (byte)reader.GetInt32(1),
|
||||
ShopType = (byte)reader.GetInt32(2),
|
||||
Name = reader.IsDBNull(3) ? null : reader.GetString(3),
|
||||
Items = new List<ShopItemDTO>(),
|
||||
Skills = new List<ShopSkillDTO>()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
using (var cmd = new NpgsqlCommand("SELECT map_npc_id, slot, color, item_vnum, rare, type, upgrade, price FROM shop_items;", conn))
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
int mapNpcId = reader.GetInt32(0);
|
||||
if (!shops.TryGetValue(mapNpcId, out ShopDTO shop))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
shop.Items ??= new List<ShopItemDTO>();
|
||||
shop.Items.Add(new ShopItemDTO
|
||||
{
|
||||
Slot = reader.GetInt16(1),
|
||||
Color = reader.GetByte(2),
|
||||
ItemVNum = reader.GetInt32(3),
|
||||
Rare = reader.GetInt16(4),
|
||||
Type = reader.GetByte(5),
|
||||
Upgrade = reader.GetByte(6),
|
||||
Price = reader.IsDBNull(7) ? null : reader.GetInt32(7)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
using (var cmd = new NpgsqlCommand("SELECT map_npc_id, skill_vnum, slot, type FROM shop_skills;", conn))
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
int mapNpcId = reader.GetInt32(0);
|
||||
if (!shops.TryGetValue(mapNpcId, out ShopDTO shop))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
shop.Skills ??= new List<ShopSkillDTO>();
|
||||
shop.Skills.Add(new ShopSkillDTO
|
||||
{
|
||||
SkillVNum = reader.GetInt16(1),
|
||||
Slot = reader.GetInt16(2),
|
||||
Type = reader.GetByte(3)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new List<ShopDTO>(shops.Values);
|
||||
}
|
||||
|
||||
public static List<RecipeDTO> LoadRecipes()
|
||||
{
|
||||
var recipes = new Dictionary<int, RecipeDTO>();
|
||||
using var conn = new NpgsqlConnection(BuildConnectionString());
|
||||
conn.Open();
|
||||
|
||||
using (var cmd = new NpgsqlCommand("SELECT recipe_id, amount, producer_map_npc_id, produced_item_vnum, producer_item_vnum, producer_npc_vnum FROM recipes;", conn))
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
int recipeId = reader.GetInt32(0);
|
||||
recipes[recipeId] = new RecipeDTO
|
||||
{
|
||||
Id = recipeId,
|
||||
Amount = reader.GetInt32(1),
|
||||
ProducerMapNpcId = reader.IsDBNull(2) ? null : reader.GetInt32(2),
|
||||
ProducedItemVnum = reader.GetInt32(3),
|
||||
ProducerItemVnum = reader.IsDBNull(4) ? null : reader.GetInt32(4),
|
||||
ProducerNpcVnum = reader.IsDBNull(5) ? null : reader.GetInt32(5),
|
||||
Items = new List<RecipeItemDTO>()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
using (var cmd = new NpgsqlCommand("SELECT recipe_id, slot, item_vnum, amount FROM recipe_items;", conn))
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
int recipeId = reader.GetInt32(0);
|
||||
if (!recipes.TryGetValue(recipeId, out RecipeDTO recipe))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
recipe.Items ??= new List<RecipeItemDTO>();
|
||||
recipe.Items.Add(new RecipeItemDTO
|
||||
{
|
||||
Slot = reader.GetInt16(1),
|
||||
ItemVNum = reader.GetInt16(2),
|
||||
Amount = reader.GetInt16(3)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new List<RecipeDTO>(recipes.Values);
|
||||
}
|
||||
|
||||
public static List<TeleporterDTO> LoadTeleporters()
|
||||
{
|
||||
var result = new List<TeleporterDTO>();
|
||||
using var conn = new NpgsqlConnection(BuildConnectionString());
|
||||
conn.Open();
|
||||
using var cmd = new NpgsqlCommand("SELECT teleporter_id, idx, type, map_id, map_npc_id, map_x, map_y FROM map_teleporters;", conn);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
result.Add(new TeleporterDTO
|
||||
{
|
||||
Id = reader.GetInt32(0),
|
||||
Index = reader.GetInt16(1),
|
||||
Type = (TeleporterType)reader.GetInt32(2),
|
||||
MapId = reader.GetInt32(3),
|
||||
MapNpcId = reader.GetInt32(4),
|
||||
MapX = reader.GetInt16(5),
|
||||
MapY = reader.GetInt16(6)
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<ItemBoxDto> LoadItemBoxes()
|
||||
{
|
||||
var boxes = new Dictionary<int, ItemBoxDto>();
|
||||
using var conn = new NpgsqlConnection(BuildConnectionString());
|
||||
conn.Open();
|
||||
|
||||
using (var cmd = new NpgsqlCommand("SELECT item_vnum, box_type, min_rewards, max_rewards, shows_raid_panel FROM item_boxes;", conn))
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
int itemVnum = reader.GetInt32(0);
|
||||
boxes[itemVnum] = new ItemBoxDto
|
||||
{
|
||||
Id = itemVnum,
|
||||
ItemBoxType = (ItemBoxType)reader.GetInt32(1),
|
||||
MinimumRewards = reader.IsDBNull(2) ? null : reader.GetInt32(2),
|
||||
MaximumRewards = reader.IsDBNull(3) ? null : reader.GetInt32(3),
|
||||
ShowsRaidBoxPanelOnOpen = !reader.IsDBNull(4) && reader.GetBoolean(4),
|
||||
Items = new List<ItemBoxItemDto>()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
using (var cmd = new NpgsqlCommand("SELECT item_vnum, probability, min_original_rare, max_original_rare, generated_amount, generated_vnum, generated_random_rarity, generated_upgrade FROM item_box_items;", conn))
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
int itemVnum = reader.GetInt32(0);
|
||||
if (!boxes.TryGetValue(itemVnum, out ItemBoxDto box))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
box.Items ??= new List<ItemBoxItemDto>();
|
||||
box.Items.Add(new ItemBoxItemDto
|
||||
{
|
||||
Probability = reader.GetInt16(1),
|
||||
MinimumOriginalItemRare = reader.GetInt16(2),
|
||||
MaximumOriginalItemRare = reader.GetInt16(3),
|
||||
ItemGeneratedAmount = reader.GetInt16(4),
|
||||
ItemGeneratedVNum = reader.GetInt32(5),
|
||||
ItemGeneratedRandomRarity = !reader.IsDBNull(6) && reader.GetBoolean(6),
|
||||
ItemGeneratedUpgrade = reader.GetByte(7)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new List<ItemBoxDto>(boxes.Values);
|
||||
}
|
||||
|
||||
public static List<ServerMapDto> LoadServerMaps()
|
||||
{
|
||||
var maps = new Dictionary<int, ServerMapDto>();
|
||||
using var conn = new NpgsqlConnection(BuildConnectionString());
|
||||
conn.Open();
|
||||
|
||||
using (var cmd = new NpgsqlCommand("SELECT map_id, map_vnum, map_name_id, map_music_id FROM server_maps;", conn))
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
int mapId = reader.GetInt32(0);
|
||||
maps[mapId] = new ServerMapDto
|
||||
{
|
||||
Id = mapId,
|
||||
MapVnum = reader.GetInt32(1),
|
||||
NameId = reader.GetInt32(2),
|
||||
MusicId = reader.GetInt32(3),
|
||||
Flags = new List<MapFlags>()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
using (var cmd = new NpgsqlCommand("SELECT map_id, flag FROM server_map_flags;", conn))
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
int mapId = reader.GetInt32(0);
|
||||
if (!maps.TryGetValue(mapId, out ServerMapDto map))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string flag = reader.GetString(1);
|
||||
if (Enum.TryParse(flag, out MapFlags parsed))
|
||||
{
|
||||
map.Flags ??= new List<MapFlags>();
|
||||
map.Flags.Add(parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new List<ServerMapDto>(maps.Values);
|
||||
}
|
||||
|
||||
public static List<PortalDTO> LoadMapPortals()
|
||||
{
|
||||
var result = new List<PortalDTO>();
|
||||
using var conn = new NpgsqlConnection(BuildConnectionString());
|
||||
conn.Open();
|
||||
using var cmd = new NpgsqlCommand("SELECT destination_map_id, destination_map_x, destination_map_y, source_map_id, source_map_x, source_map_y, type FROM map_portals;", conn);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
result.Add(new PortalDTO
|
||||
{
|
||||
DestinationMapId = reader.GetInt32(0),
|
||||
DestinationX = reader.GetInt16(1),
|
||||
DestinationY = reader.GetInt16(2),
|
||||
SourceMapId = reader.GetInt32(3),
|
||||
SourceX = reader.GetInt16(4),
|
||||
SourceY = reader.GetInt16(5),
|
||||
Type = reader.GetInt16(6),
|
||||
IsDisabled = false
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -591,12 +591,16 @@ CREATE TABLE IF NOT EXISTS recipe_items (
|
|||
);", conn, tx)) cmd.ExecuteNonQuery();
|
||||
using (var cmd = new NpgsqlCommand("TRUNCATE TABLE recipes, recipe_items RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery();
|
||||
|
||||
int fallbackRecipeId = 1;
|
||||
foreach (var r in recipes)
|
||||
{
|
||||
int effectiveRecipeId = r.Id > 0 ? r.Id : fallbackRecipeId;
|
||||
fallbackRecipeId = Math.Max(fallbackRecipeId + 1, effectiveRecipeId + 1);
|
||||
|
||||
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("id", effectiveRecipeId);
|
||||
cmd.Parameters.AddWithValue("amount", r.Amount);
|
||||
cmd.Parameters.AddWithValue("mapNpc", (object?)r.ProducerMapNpcId ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("produced", r.ProducedItemVnum);
|
||||
|
|
@ -609,7 +613,7 @@ VALUES (@id,@amount,@mapNpc,@produced,@prodItem,@prodNpc);", conn, tx))
|
|||
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("id", effectiveRecipeId);
|
||||
cmd.Parameters.AddWithValue("slot", it.Slot);
|
||||
cmd.Parameters.AddWithValue("item", it.ItemVNum);
|
||||
cmd.Parameters.AddWithValue("amount", it.Amount);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -32,52 +33,82 @@ public class RecipeManager : IRecipeManager
|
|||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
var recipes = new List<RecipeDTO>();
|
||||
foreach (RecipeObject recipeObject in _files.SelectMany(x => x.Recipes))
|
||||
List<RecipeDTO> recipes = null;
|
||||
bool dbFirst = ParserDataPostgresReader.DbFirstEnabled;
|
||||
bool strictDbOnly = ParserDataPostgresReader.StrictDbOnlyEnabled;
|
||||
|
||||
if (dbFirst)
|
||||
{
|
||||
if (recipeObject == null)
|
||||
try
|
||||
{
|
||||
continue;
|
||||
recipes = ParserDataPostgresReader.LoadRecipes();
|
||||
Log.Info($"[DB_FIRST] Loaded {recipes.Count} recipes from database");
|
||||
}
|
||||
|
||||
RecipeDTO recipe = recipeObject.ToDto();
|
||||
|
||||
IGameItem producerItem = _itemsManager.GetItem(recipe.ProducedItemVnum);
|
||||
if (producerItem is null)
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Warn("[RECIPE] Item not found: " + recipe.Id +
|
||||
$" on recipe ProducerItemVnum: {recipe.ProducerItemVnum} | ProducerNpc: {recipe.ProducerNpcVnum} | Producer: {recipe.ProducerMapNpcId}");
|
||||
}
|
||||
|
||||
List<RecipeItemDTO> items = new();
|
||||
if (recipeObject.Items != null)
|
||||
{
|
||||
short slot = 0;
|
||||
foreach (RecipeItemObject recipeItem in recipeObject.Items)
|
||||
if (strictDbOnly)
|
||||
{
|
||||
if (recipeItem == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
IGameItem item = _itemsManager.GetItem(recipeItem.ItemVnum);
|
||||
if (item is null)
|
||||
{
|
||||
Log.Warn("[RECIPE] Item not found: " + recipeItem.ItemVnum +
|
||||
$" on recipe ProducerItemVnum: {recipe.ProducerItemVnum} | ProducerNpc: {recipe.ProducerNpcVnum} | Producer: {recipe.ProducerMapNpcId}");
|
||||
continue;
|
||||
}
|
||||
|
||||
items.Add(recipeItem.ToDto(slot));
|
||||
slot++;
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but failed to load recipes from database.", e);
|
||||
}
|
||||
|
||||
Log.Error("[DB_FIRST] Could not load recipes from database", e);
|
||||
}
|
||||
|
||||
recipe.Items = items;
|
||||
recipes.Add(recipe);
|
||||
if (strictDbOnly && (recipes == null || recipes.Count == 0))
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but no recipes were loaded from database.");
|
||||
}
|
||||
}
|
||||
|
||||
ParserDataPostgresSync.SyncRecipes(recipes);
|
||||
if (recipes == null || recipes.Count == 0)
|
||||
{
|
||||
recipes = new List<RecipeDTO>();
|
||||
foreach (RecipeObject recipeObject in _files.SelectMany(x => x.Recipes))
|
||||
{
|
||||
if (recipeObject == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RecipeDTO recipe = recipeObject.ToDto();
|
||||
|
||||
IGameItem producerItem = _itemsManager.GetItem(recipe.ProducedItemVnum);
|
||||
if (producerItem is null)
|
||||
{
|
||||
Log.Warn("[RECIPE] Item not found: " + recipe.Id +
|
||||
$" on recipe ProducerItemVnum: {recipe.ProducerItemVnum} | ProducerNpc: {recipe.ProducerNpcVnum} | Producer: {recipe.ProducerMapNpcId}");
|
||||
}
|
||||
|
||||
List<RecipeItemDTO> items = new();
|
||||
if (recipeObject.Items != null)
|
||||
{
|
||||
short slot = 0;
|
||||
foreach (RecipeItemObject recipeItem in recipeObject.Items)
|
||||
{
|
||||
if (recipeItem == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
IGameItem item = _itemsManager.GetItem(recipeItem.ItemVnum);
|
||||
if (item is null)
|
||||
{
|
||||
Log.Warn("[RECIPE] Item not found: " + recipeItem.ItemVnum +
|
||||
$" on recipe ProducerItemVnum: {recipe.ProducerItemVnum} | ProducerNpc: {recipe.ProducerNpcVnum} | Producer: {recipe.ProducerMapNpcId}");
|
||||
continue;
|
||||
}
|
||||
|
||||
items.Add(recipeItem.ToDto(slot));
|
||||
slot++;
|
||||
}
|
||||
}
|
||||
|
||||
recipe.Items = items;
|
||||
recipes.Add(recipe);
|
||||
}
|
||||
|
||||
ParserDataPostgresSync.SyncRecipes(recipes);
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
foreach (RecipeDTO recipe in recipes)
|
||||
|
|
|
|||
|
|
@ -35,73 +35,106 @@ public class ShopManager : IShopManager
|
|||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
IEnumerable<MapNpcObject> importedNpcs = _importFile.SelectMany(x => x.Npcs.Select(s =>
|
||||
{
|
||||
s.MapId = x.MapId;
|
||||
return s;
|
||||
})).ToList();
|
||||
List<ShopDTO> allShops = null;
|
||||
bool dbFirst = ParserDataPostgresReader.DbFirstEnabled;
|
||||
bool strictDbOnly = ParserDataPostgresReader.StrictDbOnlyEnabled;
|
||||
|
||||
int shopItemsCount = 0;
|
||||
int shopSkillsCount = 0;
|
||||
int count = 0;
|
||||
var allShops = new List<ShopDTO>();
|
||||
|
||||
foreach (MapNpcObject npc in importedNpcs)
|
||||
if (dbFirst)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (npc.ItemShop == null && npc.SkillShop == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ShopDTO shop = npc.SkillShop?.ToDto() ?? npc.ItemShop.ToDto();
|
||||
|
||||
shop.MapNpcId = npc.MapNpcId;
|
||||
|
||||
if (shop.MenuType == 1)
|
||||
{
|
||||
shop.Skills = new List<ShopSkillDTO>();
|
||||
foreach (MapNpcShopTabObject<MapNpcShopSkillObject> tabs in npc.SkillShop.ShopTabs.Where(x => x.Items != null))
|
||||
{
|
||||
short index = 0;
|
||||
shop.Skills.AddRange(tabs.Items.Select(x =>
|
||||
{
|
||||
ShopSkillDTO tpp = x.ToDto((byte)tabs.ShopTabId, index);
|
||||
index++;
|
||||
return tpp;
|
||||
}));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
short i = 0;
|
||||
shop.Items = new List<ShopItemDTO>();
|
||||
foreach (MapNpcShopTabObject<MapNpcShopItemObject> tabs in npc.ItemShop.ShopTabs.Where(tabs => tabs.Items != null))
|
||||
{
|
||||
shop.Items.AddRange(tabs.Items.Select(s =>
|
||||
{
|
||||
ShopItemDTO tpp = s.ToDto((byte)tabs.ShopTabId, i);
|
||||
i++;
|
||||
return tpp;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
allShops.Add(shop);
|
||||
_shopsByNpcId.Set(shop.MapNpcId, _shopFactory.CreateShop(shop));
|
||||
shopItemsCount += shop.Items?.Count ?? 0;
|
||||
shopSkillsCount += shop.Skills?.Count ?? 0;
|
||||
count++;
|
||||
allShops = ParserDataPostgresReader.LoadShops();
|
||||
Log.Info($"[DB_FIRST] Loaded {allShops.Count} shops from database");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("[MAPNPC_IMPORT] ERROR", e);
|
||||
if (strictDbOnly)
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but failed to load shops from database.", e);
|
||||
}
|
||||
|
||||
Log.Error("[DB_FIRST] Could not load shops from database", e);
|
||||
}
|
||||
|
||||
if (strictDbOnly && (allShops == null || allShops.Count == 0))
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but no shops were loaded from database.");
|
||||
}
|
||||
}
|
||||
|
||||
ParserDataPostgresSync.SyncShops(allShops);
|
||||
Log.Info($"[SHOP_MANAGER] Loaded {count.ToString()} shops.");
|
||||
if (allShops == null || allShops.Count == 0)
|
||||
{
|
||||
IEnumerable<MapNpcObject> importedNpcs = _importFile.SelectMany(x => x.Npcs.Select(s =>
|
||||
{
|
||||
s.MapId = x.MapId;
|
||||
return s;
|
||||
})).ToList();
|
||||
|
||||
allShops = new List<ShopDTO>();
|
||||
|
||||
foreach (MapNpcObject npc in importedNpcs)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (npc.ItemShop == null && npc.SkillShop == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ShopDTO shop = npc.SkillShop?.ToDto() ?? npc.ItemShop.ToDto();
|
||||
|
||||
shop.MapNpcId = npc.MapNpcId;
|
||||
|
||||
if (shop.MenuType == 1)
|
||||
{
|
||||
shop.Skills = new List<ShopSkillDTO>();
|
||||
foreach (MapNpcShopTabObject<MapNpcShopSkillObject> tabs in npc.SkillShop.ShopTabs.Where(x => x.Items != null))
|
||||
{
|
||||
short index = 0;
|
||||
shop.Skills.AddRange(tabs.Items.Select(x =>
|
||||
{
|
||||
ShopSkillDTO tpp = x.ToDto((byte)tabs.ShopTabId, index);
|
||||
index++;
|
||||
return tpp;
|
||||
}));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
short i = 0;
|
||||
shop.Items = new List<ShopItemDTO>();
|
||||
foreach (MapNpcShopTabObject<MapNpcShopItemObject> tabs in npc.ItemShop.ShopTabs.Where(tabs => tabs.Items != null))
|
||||
{
|
||||
shop.Items.AddRange(tabs.Items.Select(s =>
|
||||
{
|
||||
ShopItemDTO tpp = s.ToDto((byte)tabs.ShopTabId, i);
|
||||
i++;
|
||||
return tpp;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
allShops.Add(shop);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("[MAPNPC_IMPORT] ERROR", e);
|
||||
}
|
||||
}
|
||||
|
||||
ParserDataPostgresSync.SyncShops(allShops);
|
||||
}
|
||||
|
||||
int shopItemsCount = 0;
|
||||
int shopSkillsCount = 0;
|
||||
foreach (ShopDTO shop in allShops)
|
||||
{
|
||||
_shopsByNpcId.Set(shop.MapNpcId, _shopFactory.CreateShop(shop));
|
||||
shopItemsCount += shop.Items?.Count ?? 0;
|
||||
shopSkillsCount += shop.Skills?.Count ?? 0;
|
||||
}
|
||||
|
||||
Log.Info($"[SHOP_MANAGER] Loaded {allShops.Count.ToString()} shops.");
|
||||
Log.Info($"[SHOP_MANAGER] Loaded {shopItemsCount.ToString()} shops items.");
|
||||
Log.Info($"[SHOP_MANAGER] Loaded {shopSkillsCount.ToString()} shops skills.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
//
|
||||
// Developed by NosWings Team
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -25,32 +26,65 @@ public class TeleporterManager : ITeleporterManager
|
|||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
int count = 0;
|
||||
var allTeleporters = new List<TeleporterDTO>();
|
||||
foreach (TeleporterImportFile file in _teleporterConfigurations)
|
||||
List<TeleporterDTO> allTeleporters = null;
|
||||
bool dbFirst = ParserDataPostgresReader.DbFirstEnabled;
|
||||
bool strictDbOnly = ParserDataPostgresReader.StrictDbOnlyEnabled;
|
||||
|
||||
if (dbFirst)
|
||||
{
|
||||
var teleporters = file.Teleporters.Select(s =>
|
||||
try
|
||||
{
|
||||
s.MapId = file.MapId;
|
||||
count++;
|
||||
return s.ToDto();
|
||||
}).ToList();
|
||||
allTeleporters.AddRange(teleporters);
|
||||
_teleporters[file.MapId] = teleporters;
|
||||
foreach (TeleporterDTO teleporter in teleporters)
|
||||
allTeleporters = ParserDataPostgresReader.LoadTeleporters();
|
||||
Log.Info($"[DB_FIRST] Loaded {allTeleporters.Count} map_teleporters from database");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!_teleportersByNpcId.TryGetValue(teleporter.MapNpcId, out List<TeleporterDTO> teleporterDtos))
|
||||
if (strictDbOnly)
|
||||
{
|
||||
teleporterDtos = new List<TeleporterDTO>();
|
||||
_teleportersByNpcId[teleporter.MapNpcId] = teleporterDtos;
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but failed to load map_teleporters from database.", e);
|
||||
}
|
||||
|
||||
teleporterDtos.Add(teleporter);
|
||||
Log.Error("[DB_FIRST] Could not load map_teleporters from database", e);
|
||||
}
|
||||
|
||||
if (strictDbOnly && (allTeleporters == null || allTeleporters.Count == 0))
|
||||
{
|
||||
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but no map_teleporters were loaded from database.");
|
||||
}
|
||||
}
|
||||
|
||||
ParserDataPostgresSync.SyncTeleporters(allTeleporters);
|
||||
Log.Info($"[DATABASE] Loaded {count.ToString()} teleporters.");
|
||||
if (allTeleporters == null || allTeleporters.Count == 0)
|
||||
{
|
||||
allTeleporters = new List<TeleporterDTO>();
|
||||
foreach (TeleporterImportFile file in _teleporterConfigurations)
|
||||
{
|
||||
allTeleporters.AddRange(file.Teleporters.Select(s =>
|
||||
{
|
||||
s.MapId = file.MapId;
|
||||
return s.ToDto();
|
||||
}));
|
||||
}
|
||||
|
||||
ParserDataPostgresSync.SyncTeleporters(allTeleporters);
|
||||
}
|
||||
|
||||
foreach (IGrouping<int, TeleporterDTO> group in allTeleporters.GroupBy(s => s.MapId))
|
||||
{
|
||||
_teleporters[group.Key] = group.ToList();
|
||||
}
|
||||
|
||||
foreach (TeleporterDTO teleporter in allTeleporters)
|
||||
{
|
||||
if (!_teleportersByNpcId.TryGetValue(teleporter.MapNpcId, out List<TeleporterDTO> teleporterDtos))
|
||||
{
|
||||
teleporterDtos = new List<TeleporterDTO>();
|
||||
_teleportersByNpcId[teleporter.MapNpcId] = teleporterDtos;
|
||||
}
|
||||
|
||||
teleporterDtos.Add(teleporter);
|
||||
}
|
||||
|
||||
Log.Info($"[DATABASE] Loaded {allTeleporters.Count.ToString()} teleporters.");
|
||||
}
|
||||
|
||||
public IReadOnlyList<TeleporterDTO> GetTeleportByNpcId(long npcId) => _teleportersByNpcId.GetOrDefault(npcId);
|
||||
|
|
|
|||
Loading…
Reference in a new issue