Persist minigame, gameevent and resource files (dat/lang) to PostgreSQL
This commit is contained in:
parent
59c2c9796e
commit
aafa4585f9
8 changed files with 286 additions and 0 deletions
|
|
@ -5,6 +5,7 @@
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Plugin.ResourceLoader.Loaders;
|
using Plugin.ResourceLoader.Loaders;
|
||||||
|
using Plugin.ResourceLoader.Services;
|
||||||
using WingsAPI.Data.ActDesc;
|
using WingsAPI.Data.ActDesc;
|
||||||
using WingsAPI.Data.GameData;
|
using WingsAPI.Data.GameData;
|
||||||
using WingsAPI.Plugins;
|
using WingsAPI.Plugins;
|
||||||
|
|
@ -40,6 +41,7 @@ namespace Plugin.ResourceLoader
|
||||||
services.AddSingleton<IGameDataLanguageService, InMemoryGameDataLanguageService>();
|
services.AddSingleton<IGameDataLanguageService, InMemoryGameDataLanguageService>();
|
||||||
|
|
||||||
services.AddSingleton<IBattleEntityAlgorithmService, BattleEntityAlgorithmService>();
|
services.AddSingleton<IBattleEntityAlgorithmService, BattleEntityAlgorithmService>();
|
||||||
|
services.AddHostedService<ResourceFilesPostgresSyncService>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,6 +53,7 @@ namespace Plugin.ResourceLoader
|
||||||
{
|
{
|
||||||
services.AddSingleton<IResourceLoader<GenericTranslationDto>, GenericTranslationGrpcLoader>();
|
services.AddSingleton<IResourceLoader<GenericTranslationDto>, GenericTranslationGrpcLoader>();
|
||||||
services.AddSingleton<IGameLanguageService, InMemoryMultilanguageService>();
|
services.AddSingleton<IGameLanguageService, InMemoryMultilanguageService>();
|
||||||
|
services.AddHostedService<ResourceFilesPostgresSyncService>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,11 @@
|
||||||
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||||
|
<PackageReference Include="Npgsql" Version="5.0.18" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\PhoenixLib.Caching\PhoenixLib.Caching.csproj" />
|
<ProjectReference Include="..\..\PhoenixLib.Caching\PhoenixLib.Caching.csproj" />
|
||||||
<ProjectReference Include="..\..\PhoenixLib.Multilanguage\PhoenixLib.Multilanguage.csproj" />
|
<ProjectReference Include="..\..\PhoenixLib.Multilanguage\PhoenixLib.Multilanguage.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Npgsql;
|
||||||
|
using PhoenixLib.Logging;
|
||||||
|
|
||||||
|
namespace Plugin.ResourceLoader.Services
|
||||||
|
{
|
||||||
|
public class ResourceFilesPostgresSyncService : IHostedService
|
||||||
|
{
|
||||||
|
private readonly ResourceLoadingConfiguration _configuration;
|
||||||
|
|
||||||
|
public ResourceFilesPostgresSyncService(ResourceLoadingConfiguration configuration)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (string.Equals(Environment.GetEnvironmentVariable("PARSER_DB_SYNC"), "false", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
string root = _configuration.ResourcePaths;
|
||||||
|
string datPath = _configuration.GameDataPath;
|
||||||
|
string langPath = _configuration.GameLanguagePath;
|
||||||
|
|
||||||
|
string[] datFiles = Directory.Exists(datPath)
|
||||||
|
? Directory.GetFiles(datPath, "*", SearchOption.AllDirectories)
|
||||||
|
: Array.Empty<string>();
|
||||||
|
string[] langFiles = Directory.Exists(langPath)
|
||||||
|
? Directory.GetFiles(langPath, "*", SearchOption.AllDirectories)
|
||||||
|
: Array.Empty<string>();
|
||||||
|
|
||||||
|
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
|
||||||
|
conn.Open();
|
||||||
|
using var tx = conn.BeginTransaction();
|
||||||
|
|
||||||
|
using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS resource_files (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
category TEXT NOT NULL,
|
||||||
|
relative_path TEXT NOT NULL,
|
||||||
|
sha256 TEXT NOT NULL,
|
||||||
|
content BYTEA NOT NULL,
|
||||||
|
size_bytes INT NOT NULL,
|
||||||
|
synced_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
UNIQUE(category, relative_path)
|
||||||
|
);", conn, tx))
|
||||||
|
{
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
UpsertFiles(conn, tx, root, "dat", datFiles);
|
||||||
|
UpsertFiles(conn, tx, root, "lang", langFiles);
|
||||||
|
|
||||||
|
tx.Commit();
|
||||||
|
Log.Info($"[PARSER_DB_SYNC] Synced resource_files dat={datFiles.Length} lang={langFiles.Length}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error("[PARSER_DB_SYNC] Failed to sync resource files", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
|
|
||||||
|
private static void UpsertFiles(NpgsqlConnection conn, NpgsqlTransaction tx, string root, string category, string[] files)
|
||||||
|
{
|
||||||
|
foreach (string file in files.Where(File.Exists))
|
||||||
|
{
|
||||||
|
byte[] content = File.ReadAllBytes(file);
|
||||||
|
string hash;
|
||||||
|
using (SHA256 sha = SHA256.Create())
|
||||||
|
{
|
||||||
|
hash = Convert.ToHexString(sha.ComputeHash(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
string relative = Path.GetRelativePath(root, file).Replace('\\', '/');
|
||||||
|
|
||||||
|
using var cmd = new NpgsqlCommand(@"INSERT INTO resource_files(category,relative_path,sha256,content,size_bytes,synced_at)
|
||||||
|
VALUES (@category,@path,@sha,@content,@size,NOW())
|
||||||
|
ON CONFLICT (category, relative_path)
|
||||||
|
DO UPDATE SET sha256=EXCLUDED.sha256, content=EXCLUDED.content, size_bytes=EXCLUDED.size_bytes, synced_at=NOW();", conn, tx);
|
||||||
|
cmd.Parameters.AddWithValue("category", category);
|
||||||
|
cmd.Parameters.AddWithValue("path", relative);
|
||||||
|
cmd.Parameters.AddWithValue("sha", hash);
|
||||||
|
cmd.Parameters.AddWithValue("content", content);
|
||||||
|
cmd.Parameters.AddWithValue("size", content.Length);
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ using WingsEmu.Game.Characters.Events;
|
||||||
using WingsEmu.Game.Configurations.Miniland;
|
using WingsEmu.Game.Configurations.Miniland;
|
||||||
using WingsEmu.Game.Miniland;
|
using WingsEmu.Game.Miniland;
|
||||||
using WingsEmu.Game.Networking;
|
using WingsEmu.Game.Networking;
|
||||||
|
using WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence;
|
||||||
|
|
||||||
namespace WingsEmu.Plugins.BasicImplementations.Managers;
|
namespace WingsEmu.Plugins.BasicImplementations.Managers;
|
||||||
|
|
||||||
|
|
@ -20,11 +21,14 @@ public class MinigameManager : IMinigameManager
|
||||||
{
|
{
|
||||||
_minigameConfiguration = minigameConfiguration;
|
_minigameConfiguration = minigameConfiguration;
|
||||||
_lockService = lockService;
|
_lockService = lockService;
|
||||||
|
InitializePersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> CanRefreshMinigamesFreeProductionPoints(long characterId) =>
|
public async Task<bool> CanRefreshMinigamesFreeProductionPoints(long characterId) =>
|
||||||
await _lockService.TryAddTemporaryLockAsync($"game:locks:minigame-refresh:{characterId}", DateTime.UtcNow.Date.AddDays(1));
|
await _lockService.TryAddTemporaryLockAsync($"game:locks:minigame-refresh:{characterId}", DateTime.UtcNow.Date.AddDays(1));
|
||||||
|
|
||||||
|
public void InitializePersistence() => ParserDataPostgresSync.SyncMinigames(_minigameConfiguration);
|
||||||
|
|
||||||
|
|
||||||
public MinigameScoresHolder GetScores(int minigameVnum)
|
public MinigameScoresHolder GetScores(int minigameVnum)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
using PhoenixLib.Logging;
|
using PhoenixLib.Logging;
|
||||||
using WingsAPI.Data.Drops;
|
using WingsAPI.Data.Drops;
|
||||||
|
|
@ -8,6 +9,7 @@ using WingsEmu.DTOs.Maps;
|
||||||
using WingsEmu.DTOs.Recipes;
|
using WingsEmu.DTOs.Recipes;
|
||||||
using WingsEmu.DTOs.ServerDatas;
|
using WingsEmu.DTOs.ServerDatas;
|
||||||
using WingsEmu.DTOs.Shops;
|
using WingsEmu.DTOs.Shops;
|
||||||
|
using WingsEmu.Game.Configurations.Miniland;
|
||||||
|
|
||||||
namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence;
|
namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence;
|
||||||
|
|
||||||
|
|
@ -492,6 +494,77 @@ VALUES (@id,@amount,@chance,@item,@map,@mon,@race,@subrace);", conn, tx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SyncMinigames(MinigameConfiguration minigameConfiguration)
|
||||||
|
{
|
||||||
|
if (!Enabled || minigameConfiguration == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var conn = new NpgsqlConnection(BuildConnectionString());
|
||||||
|
conn.Open();
|
||||||
|
using var tx = conn.BeginTransaction();
|
||||||
|
|
||||||
|
using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS minigame_config (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
minigame_vnum INT,
|
||||||
|
minigame_type INT,
|
||||||
|
minimum_level INT,
|
||||||
|
minimum_reputation INT,
|
||||||
|
rewards_json JSONB
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS minigame_scores_holders (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
minigame_type INT,
|
||||||
|
scores_json JSONB
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS global_minigame_config (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
config_json JSONB
|
||||||
|
);", conn, tx)) cmd.ExecuteNonQuery();
|
||||||
|
|
||||||
|
using (var cmd = new NpgsqlCommand("TRUNCATE TABLE minigame_config, minigame_scores_holders, global_minigame_config RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery();
|
||||||
|
|
||||||
|
if (minigameConfiguration.Minigames != null)
|
||||||
|
{
|
||||||
|
foreach (Minigame m in minigameConfiguration.Minigames)
|
||||||
|
{
|
||||||
|
using var cmd = new NpgsqlCommand(@"INSERT INTO minigame_config(minigame_vnum,minigame_type,minimum_level,minimum_reputation,rewards_json)
|
||||||
|
VALUES (@vnum,@type,@minLvl,@minRep,@rewards::jsonb);", conn, tx);
|
||||||
|
cmd.Parameters.AddWithValue("vnum", m.Vnum);
|
||||||
|
cmd.Parameters.AddWithValue("type", (int)m.Type);
|
||||||
|
cmd.Parameters.AddWithValue("minLvl", m.MinimumLevel);
|
||||||
|
cmd.Parameters.AddWithValue("minRep", m.MinimumReputation);
|
||||||
|
cmd.Parameters.AddWithValue("rewards", JsonSerializer.Serialize(m.Rewards));
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minigameConfiguration.ScoresHolders != null)
|
||||||
|
{
|
||||||
|
foreach (MinigameScoresHolder s in minigameConfiguration.ScoresHolders)
|
||||||
|
{
|
||||||
|
using var cmd = new NpgsqlCommand(@"INSERT INTO minigame_scores_holders(minigame_type,scores_json)
|
||||||
|
VALUES (@type,@scores::jsonb);", conn, tx);
|
||||||
|
cmd.Parameters.AddWithValue("type", (int)s.Type);
|
||||||
|
cmd.Parameters.AddWithValue("scores", JsonSerializer.Serialize(s.Scores));
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var cmd = new NpgsqlCommand("INSERT INTO global_minigame_config(config_json) VALUES (@cfg::jsonb);", conn, tx))
|
||||||
|
{
|
||||||
|
cmd.Parameters.AddWithValue("cfg", JsonSerializer.Serialize(minigameConfiguration.Configuration));
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit();
|
||||||
|
Log.Info($"[PARSER_DB_SYNC] Synced minigames={minigameConfiguration.Minigames?.Count ?? 0} holders={minigameConfiguration.ScoresHolders?.Count ?? 0}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error("[PARSER_DB_SYNC] Failed to sync minigames", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void SyncRecipes(IReadOnlyList<RecipeDTO> recipes)
|
public static void SyncRecipes(IReadOnlyList<RecipeDTO> recipes)
|
||||||
{
|
{
|
||||||
if (!Enabled) return;
|
if (!Enabled) return;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ using WingsEmu.Plugins.GameEvents.Configuration.InstantBattle;
|
||||||
using WingsEmu.Plugins.GameEvents.Consumers;
|
using WingsEmu.Plugins.GameEvents.Consumers;
|
||||||
using WingsEmu.Plugins.GameEvents.Matchmaking.Matchmaker;
|
using WingsEmu.Plugins.GameEvents.Matchmaking.Matchmaker;
|
||||||
using WingsEmu.Plugins.GameEvents.RecurrentJob;
|
using WingsEmu.Plugins.GameEvents.RecurrentJob;
|
||||||
|
using WingsEmu.Plugins.GameEvents.Services;
|
||||||
|
|
||||||
namespace WingsEmu.Plugins.GameEvents
|
namespace WingsEmu.Plugins.GameEvents
|
||||||
{
|
{
|
||||||
|
|
@ -59,6 +60,8 @@ namespace WingsEmu.Plugins.GameEvents
|
||||||
{
|
{
|
||||||
[GameEventType.InstantBattle] = new InstantBattleMatchmaker(s.GetService<IGlobalInstantBattleConfiguration>())
|
[GameEventType.InstantBattle] = new InstantBattleMatchmaker(s.GetService<IGlobalInstantBattleConfiguration>())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
services.AddHostedService<GameEventConfigPostgresSyncService>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Npgsql;
|
||||||
|
using PhoenixLib.Logging;
|
||||||
|
using WingsEmu.Plugins.GameEvents.Configuration.InstantBattle;
|
||||||
|
|
||||||
|
namespace WingsEmu.Plugins.GameEvents.Services
|
||||||
|
{
|
||||||
|
public class GameEventConfigPostgresSyncService : IHostedService
|
||||||
|
{
|
||||||
|
private readonly IGlobalInstantBattleConfiguration _configuration;
|
||||||
|
|
||||||
|
public GameEventConfigPostgresSyncService(IGlobalInstantBattleConfiguration configuration)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
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 tx = conn.BeginTransaction();
|
||||||
|
|
||||||
|
using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS gameevent_instant_battle_configs (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
map_id INT,
|
||||||
|
game_event_type INT,
|
||||||
|
config_json JSONB
|
||||||
|
);", conn, tx)) cmd.ExecuteNonQuery();
|
||||||
|
|
||||||
|
using (var cmd = new NpgsqlCommand("TRUNCATE TABLE gameevent_instant_battle_configs RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery();
|
||||||
|
|
||||||
|
var configs = (_configuration as GlobalInstantBattleConfiguration)?.Configurations ?? Enumerable.Empty<InstantBattleConfiguration>();
|
||||||
|
|
||||||
|
foreach (InstantBattleConfiguration c in configs)
|
||||||
|
{
|
||||||
|
using var cmd = new NpgsqlCommand("INSERT INTO gameevent_instant_battle_configs(map_id,game_event_type,config_json) VALUES (@map,@type,@cfg::jsonb);", conn, tx);
|
||||||
|
cmd.Parameters.AddWithValue("map", c.MapId);
|
||||||
|
cmd.Parameters.AddWithValue("type", (int)c.GameEventType);
|
||||||
|
cmd.Parameters.AddWithValue("cfg", JsonSerializer.Serialize(c));
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit();
|
||||||
|
Log.Info($"[PARSER_DB_SYNC] Synced instant_battle_configs={configs.Count()}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error("[PARSER_DB_SYNC] Failed to sync gameevents", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||||
|
<PackageReference Include="Npgsql" Version="5.0.18" />
|
||||||
<ProjectReference Include="..\..\PhoenixLib.Configuration\PhoenixLib.Configuration.csproj" />
|
<ProjectReference Include="..\..\PhoenixLib.Configuration\PhoenixLib.Configuration.csproj" />
|
||||||
<ProjectReference Include="..\..\Plugin.RainbowBattle\Plugin.RainbowBattle.csproj" />
|
<ProjectReference Include="..\..\Plugin.RainbowBattle\Plugin.RainbowBattle.csproj" />
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue