diff --git a/Arrowgene.Ddon.Cli/Command/ServerCommand.cs b/Arrowgene.Ddon.Cli/Command/ServerCommand.cs
index 88a07cd3e..a26fb45b7 100644
--- a/Arrowgene.Ddon.Cli/Command/ServerCommand.cs
+++ b/Arrowgene.Ddon.Cli/Command/ServerCommand.cs
@@ -102,6 +102,7 @@ public CommandResultType Run(CommandParameter parameter)
if (_database == null)
{
+ _setting.DatabaseSetting ??= new DatabaseSetting();
_database = DdonDatabaseBuilder.Build(_setting.DatabaseSetting);
}
diff --git a/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj b/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj
index 5dff9fa84..82bb7f672 100644
--- a/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj
+++ b/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj
@@ -14,6 +14,8 @@
+
+
diff --git a/Arrowgene.Ddon.Database/DatabaseSetting.cs b/Arrowgene.Ddon.Database/DatabaseSetting.cs
index 63ced11b4..cf85c5e3c 100644
--- a/Arrowgene.Ddon.Database/DatabaseSetting.cs
+++ b/Arrowgene.Ddon.Database/DatabaseSetting.cs
@@ -3,7 +3,6 @@
using System.Runtime.Serialization;
using Arrowgene.Ddon.Database.Model;
using Arrowgene.Ddon.Shared;
-using Arrowgene.Ddon.Shared.Network;
namespace Arrowgene.Ddon.Database
{
@@ -12,40 +11,61 @@ public class DatabaseSetting
{
public DatabaseSetting()
{
- Type = DatabaseType.SQLite;
- SqLiteFolder = Path.Combine(Util.ExecutingDirectory(), "Files/Database");
+ Type = "sqlite";
+ DatabaseFolder = Path.Combine(Util.ExecutingDirectory(), "Files/Database");
Host = "localhost";
Port = 3306;
Database = "Ddon";
User = string.Empty;
Password = string.Empty;
- WipeOnStartup = true;
+ WipeOnStartup = false;
string envDbType = Environment.GetEnvironmentVariable("DB_TYPE");
- switch (envDbType)
+ if (!string.IsNullOrEmpty(envDbType))
{
- case "sqlite":
- Type = DatabaseType.SQLite;
- break;
+ Type = envDbType;
+ }
+ string envDbFolder = Environment.GetEnvironmentVariable("DB_FOLDER");
+ if (!string.IsNullOrEmpty(envDbFolder))
+ {
+ DatabaseFolder = envDbFolder;
+ }
+ string envDbHost = Environment.GetEnvironmentVariable("DB_HOST");
+ if (!string.IsNullOrEmpty(envDbHost))
+ {
+ Host = envDbHost;
+ }
+ string envDbPort = Environment.GetEnvironmentVariable("DB_PORT");
+ if (!string.IsNullOrEmpty(envDbPort))
+ {
+ Port = Convert.ToInt16(envDbPort);
+ }
+ string envDbDatabase = Environment.GetEnvironmentVariable("DB_DATABASE");
+ if (!string.IsNullOrEmpty(envDbDatabase))
+ {
+ Database = envDbDatabase;
}
-
string envDbUser = Environment.GetEnvironmentVariable("DB_USER");
if (!string.IsNullOrEmpty(envDbUser))
{
User = envDbUser;
}
-
string envDbPass = Environment.GetEnvironmentVariable("DB_PASS");
if (!string.IsNullOrEmpty(envDbPass))
{
Password = envDbPass;
}
+ string envDbWipeOnStartup = Environment.GetEnvironmentVariable("DB_WIPE_ON_STARTUP");
+ if (!string.IsNullOrEmpty(envDbWipeOnStartup))
+ {
+ WipeOnStartup = Convert.ToBoolean(envDbWipeOnStartup);
+ }
}
public DatabaseSetting(DatabaseSetting databaseSettings)
{
Type = databaseSettings.Type;
- SqLiteFolder = databaseSettings.SqLiteFolder;
+ DatabaseFolder = databaseSettings.DatabaseFolder;
Host = databaseSettings.Host;
Port = databaseSettings.Port;
User = databaseSettings.User;
@@ -54,9 +74,9 @@ public DatabaseSetting(DatabaseSetting databaseSettings)
WipeOnStartup = databaseSettings.WipeOnStartup;
}
- [DataMember(Order = 0)] public DatabaseType Type { get; set; }
+ [DataMember(Order = 0)] public string Type { get; set; }
- [DataMember(Order = 1)] public string SqLiteFolder { get; set; }
+ [DataMember(Order = 1)] public string DatabaseFolder { get; set; }
[DataMember(Order = 2)] public string Host { get; set; }
@@ -68,6 +88,6 @@ public DatabaseSetting(DatabaseSetting databaseSettings)
[DataMember(Order = 6)] public string Database { get; set; }
- [DataMember(Order = 6)] public bool WipeOnStartup { get; set; }
+ [DataMember(Order = 7)] public bool WipeOnStartup { get; set; }
}
}
diff --git a/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs b/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs
index 5c8770252..3a045f9aa 100644
--- a/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs
+++ b/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs
@@ -1,5 +1,7 @@
using System;
using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
using Arrowgene.Ddon.Database.Model;
using Arrowgene.Ddon.Database.Sql;
using Arrowgene.Logging;
@@ -9,46 +11,75 @@ namespace Arrowgene.Ddon.Database
public static class DdonDatabaseBuilder
{
private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonDatabaseBuilder));
+ private const string DefaultSchemaFile = "Script/schema_sqlite.sql";
public static IDatabase Build(DatabaseSetting settings)
{
- IDatabase database = null;
- switch (settings.Type)
+ Enum.TryParse(settings.Type, true, out DatabaseType dbType);
+ IDatabase database = dbType switch
{
- case DatabaseType.SQLite:
- string sqLitePath = Path.Combine(settings.SqLiteFolder, $"db.v{DdonSqLiteDb.Version}.sqlite");
- database = BuildSqLite(settings.SqLiteFolder, sqLitePath, settings.WipeOnStartup);
- break;
- }
+ DatabaseType.SQLite => BuildSqLite(settings.DatabaseFolder, settings.WipeOnStartup),
+ DatabaseType.PostgreSQL => BuildPostgres(settings.DatabaseFolder, settings.Host, settings.User, settings.Password, settings.Database, settings.WipeOnStartup),
+ DatabaseType.MariaDb => BuildMariaDB(settings.DatabaseFolder, settings.Host, settings.User, settings.Password, settings.Database, settings.WipeOnStartup),
+ _ => throw new ArgumentOutOfRangeException($"Unknown database type '{settings.Type}' encountered!")
+ };
if (database == null)
{
Logger.Error("Database could not be created, exiting...");
Environment.Exit(1);
}
+ else
+ {
+ Logger.Info($"Database of type '${dbType.ToString()}' has been created.");
+ Logger.Info($"Database path: {settings.DatabaseFolder}");
+ }
return database;
}
- public static DdonSqLiteDb BuildSqLite(string sqLiteFolder, string sqLitePath, bool deleteIfExists)
+ public static DdonSqLiteDb BuildSqLite(string databaseFolder, bool wipeOnStartup)
+ {
+ string sqLitePath = Path.Combine(databaseFolder, $"db.v{DdonSqLiteDb.Version}.sqlite");
+ DdonSqLiteDb db = new DdonSqLiteDb(sqLitePath, wipeOnStartup);
+ if (db.CreateDatabase())
+ {
+ string schemaFilePath = Path.Combine(databaseFolder, DefaultSchemaFile);
+ String schema = File.ReadAllText(schemaFilePath, Encoding.UTF8);
+
+ db.Execute(schema);
+ }
+
+ return db;
+ }
+
+ public static DdonPostgresDb BuildPostgres(string databaseFolder, string host, string user, string password, string database, bool wipeOnStartup)
{
- if (deleteIfExists)
+ DdonPostgresDb db = new DdonPostgresDb(host, user, password, database, wipeOnStartup);
+ if (db.CreateDatabase())
{
- try
- {
- File.Delete(sqLitePath);
- }
- catch (Exception)
- {
- // ignored
- }
+ string schemaFilePath = Path.Combine(databaseFolder, DefaultSchemaFile);
+ String schema = File.ReadAllText(schemaFilePath, Encoding.UTF8);
+ schema = Regex.Replace(schema, "(\\s)DATETIME(\\s|,)", "$1TIMESTAMP WITH TIME ZONE$2");
+ schema = Regex.Replace(schema, "(\\s)INTEGER PRIMARY KEY AUTOINCREMENT(\\s|,)", "$1SERIAL PRIMARY KEY$2");
+ schema = Regex.Replace(schema, "(\\s)BLOB(\\s|,)", "$1BYTEA$2");
+
+ db.Execute(schema);
}
- DdonSqLiteDb db = new DdonSqLiteDb(sqLitePath);
+ return db;
+ }
+
+ public static DdonMariaDb BuildMariaDB(string databaseFolder, string host, string user, string password, string database, bool wipeOnStartup)
+ {
+ DdonMariaDb db = new DdonMariaDb(host, user, password, database, wipeOnStartup);
if (db.CreateDatabase())
{
- ScriptRunner scriptRunner = new ScriptRunner(db);
- scriptRunner.Run(Path.Combine(sqLiteFolder, "Script/schema_sqlite.sql"));
+ string schemaFilePath = Path.Combine(databaseFolder, DefaultSchemaFile);
+ String schema = File.ReadAllText(schemaFilePath, Encoding.UTF8);
+ schema = Regex.Replace(schema, "(\\s)AUTOINCREMENT(\\s|,)", "$1AUTO_INCREMENT$2");
+
+ db.Execute(schema);
}
return db;
diff --git a/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql b/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql
index 83e489fe4..35e69fae4 100644
--- a/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql
+++ b/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql
@@ -1,419 +1,420 @@
-CREATE TABLE IF NOT EXISTS `setting`
+CREATE TABLE IF NOT EXISTS setting
(
- `key` TEXT NOT NULL,
- `value` TEXT NOT NULL,
- PRIMARY KEY (`key`)
+ "key" VARCHAR(32) NOT NULL,
+ "value" TEXT NOT NULL,
+ PRIMARY KEY ("key")
);
-CREATE TABLE IF NOT EXISTS `account`
+CREATE TABLE IF NOT EXISTS account
(
- `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
- `name` TEXT NOT NULL,
- `normal_name` TEXT NOT NULL,
- `hash` TEXT NOT NULL,
- `mail` TEXT NOT NULL,
- `mail_verified` INTEGER NOT NULL,
- `mail_verified_at` DATETIME DEFAULT NULL,
- `mail_token` TEXT DEFAULT NULL,
- `password_token` TEXT DEFAULT NULL,
- `login_token` TEXT DEFAULT NULL,
- `login_token_created` DATETIME DEFAULT NULL,
- `state` INTEGER NOT NULL,
- `last_login` DATETIME DEFAULT NULL,
- `created` DATETIME NOT NULL,
- CONSTRAINT `uq_account_name` UNIQUE (`name`),
- CONSTRAINT `uq_account_normal_name` UNIQUE (`normal_name`),
- CONSTRAINT `uq_account_login_token` UNIQUE (`login_token`),
- CONSTRAINT `uq_account_mail` UNIQUE (`mail`)
+ "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ "name" TEXT NOT NULL,
+ "normal_name" TEXT NOT NULL,
+ "hash" TEXT NOT NULL,
+ "mail" TEXT NOT NULL,
+ "mail_verified" BOOLEAN NOT NULL,
+ "mail_verified_at" DATETIME DEFAULT NULL,
+ "mail_token" TEXT DEFAULT NULL,
+ "password_token" TEXT DEFAULT NULL,
+ "login_token" TEXT DEFAULT NULL,
+ "login_token_created" DATETIME DEFAULT NULL,
+ "state" INTEGER NOT NULL,
+ "last_login" DATETIME DEFAULT NULL,
+ "created" DATETIME NOT NULL,
+ CONSTRAINT uq_account_name UNIQUE ("name"),
+ CONSTRAINT uq_account_normal_name UNIQUE ("normal_name"),
+ CONSTRAINT uq_account_login_token UNIQUE ("login_token"),
+ CONSTRAINT uq_account_mail UNIQUE ("mail")
);
-CREATE TABLE IF NOT EXISTS `ddon_character_common`
+CREATE TABLE IF NOT EXISTS ddon_character_common
(
- `character_common_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
- `job` TINYINT NOT NULL,
- `hide_equip_head` BIT NOT NULL,
- `hide_equip_lantern` BIT NOT NULL,
- `jewelry_slot_num` TINYINT NOT NULL
+ "character_common_id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "hide_equip_head" BOOLEAN NOT NULL,
+ "hide_equip_lantern" BOOLEAN NOT NULL,
+ "jewelry_slot_num" SMALLINT NOT NULL
);
-CREATE TABLE IF NOT EXISTS `ddon_character`
+CREATE TABLE IF NOT EXISTS ddon_character
(
- `character_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
- `character_common_id` INTEGER NOT NULL,
- `account_id` INTEGER NOT NULL,
- `version` INTEGER NOT NULL,
- `first_name` TEXT NOT NULL,
- `last_name` TEXT NOT NULL,
- `created` DATETIME NOT NULL,
- `my_pawn_slot_num` TINYINT NOT NULL,
- `rental_pawn_slot_num` TINYINT NOT NULL,
- `hide_equip_head_pawn` BIT NOT NULL,
- `hide_equip_lantern_pawn` BIT NOT NULL,
- `arisen_profile_share_range` TINYINT NOT NULL,
- CONSTRAINT `fk_character_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE,
- CONSTRAINT `fk_character_account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE
+ "character_id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ "character_common_id" INTEGER NOT NULL,
+ "account_id" INTEGER NOT NULL,
+ "version" INTEGER NOT NULL,
+ "first_name" TEXT NOT NULL,
+ "last_name" TEXT NOT NULL,
+ "created" DATETIME NOT NULL,
+ "my_pawn_slot_num" SMALLINT NOT NULL,
+ "rental_pawn_slot_num" SMALLINT NOT NULL,
+ "hide_equip_head_pawn" BOOLEAN NOT NULL,
+ "hide_equip_lantern_pawn" BOOLEAN NOT NULL,
+ "arisen_profile_share_range" SMALLINT NOT NULL,
+ CONSTRAINT fk_character_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE,
+ CONSTRAINT fk_character_account_id FOREIGN KEY ("account_id") REFERENCES account ("id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_pawn`
+CREATE TABLE IF NOT EXISTS ddon_pawn
(
- `pawn_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
- `character_common_id` INTEGER NOT NULL,
- `character_id` INTEGER NOT NULL,
- `name` TEXT NOT NULL,
- `hm_type` TINYINT NOT NULL,
- `pawn_type` TINYINT NOT NULL,
- CONSTRAINT `fk_character_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE,
- CONSTRAINT `fk_character_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE
+ "pawn_id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ "character_common_id" INTEGER NOT NULL,
+ "character_id" INTEGER NOT NULL,
+ "name" TEXT NOT NULL,
+ "hm_type" SMALLINT NOT NULL,
+ "pawn_type" SMALLINT NOT NULL,
+ CONSTRAINT fk_pawn_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE,
+ CONSTRAINT fk_character_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_edit_info`
+CREATE TABLE IF NOT EXISTS ddon_edit_info
(
- `character_common_id` INTEGER PRIMARY KEY NOT NULL,
- `sex` BIT NOT NULL,
- `voice` TINYINT NOT NULL,
- `voice_pitch` SMALLINT NOT NULL,
- `personality` TINYINT NOT NULL,
- `speech_freq` TINYINT NOT NULL,
- `body_type` TINYINT NOT NULL,
- `hair` TINYINT NOT NULL,
- `beard` TINYINT NOT NULL,
- `makeup` TINYINT NOT NULL,
- `scar` TINYINT NOT NULL,
- `eye_preset_no` TINYINT NOT NULL,
- `nose_preset_no` TINYINT NOT NULL,
- `mouth_preset_no` TINYINT NOT NULL,
- `eyebrow_tex_no` TINYINT NOT NULL,
- `color_skin` TINYINT NOT NULL,
- `color_hair` TINYINT NOT NULL,
- `color_beard` TINYINT NOT NULL,
- `color_eyebrow` TINYINT NOT NULL,
- `color_r_eye` TINYINT NOT NULL,
- `color_l_eye` TINYINT NOT NULL,
- `color_makeup` TINYINT NOT NULL,
- `sokutobu` SMALLINT NOT NULL,
- `hitai` SMALLINT NOT NULL,
- `mimi_jyouge` SMALLINT NOT NULL,
- `kannkaku` SMALLINT NOT NULL,
- `mabisasi_jyouge` SMALLINT NOT NULL,
- `hanakuchi_jyouge` SMALLINT NOT NULL,
- `ago_saki_haba` SMALLINT NOT NULL,
- `ago_zengo` SMALLINT NOT NULL,
- `ago_saki_jyouge` SMALLINT NOT NULL,
- `hitomi_ookisa` SMALLINT NOT NULL,
- `me_ookisa` SMALLINT NOT NULL,
- `me_kaiten` SMALLINT NOT NULL,
- `mayu_kaiten` SMALLINT NOT NULL,
- `mimi_ookisa` SMALLINT NOT NULL,
- `mimi_muki` SMALLINT NOT NULL,
- `elf_mimi` SMALLINT NOT NULL,
- `miken_takasa` SMALLINT NOT NULL,
- `miken_haba` SMALLINT NOT NULL,
- `hohobone_ryou` SMALLINT NOT NULL,
- `hohobone_jyouge` SMALLINT NOT NULL,
- `hohoniku` SMALLINT NOT NULL,
- `erahone_jyouge` SMALLINT NOT NULL,
- `erahone_haba` SMALLINT NOT NULL,
- `hana_jyouge` SMALLINT NOT NULL,
- `hana_haba` SMALLINT NOT NULL,
- `hana_takasa` SMALLINT NOT NULL,
- `hana_kakudo` SMALLINT NOT NULL,
- `kuchi_haba` SMALLINT NOT NULL,
- `kuchi_atsusa` SMALLINT NOT NULL,
- `eyebrow_uv_offset_x` SMALLINT NOT NULL,
- `eyebrow_uv_offset_y` SMALLINT NOT NULL,
- `wrinkle` SMALLINT NOT NULL,
- `wrinkle_albedo_blend_rate` SMALLINT NOT NULL,
- `wrinkle_detail_normal_power` SMALLINT NOT NULL,
- `muscle_albedo_blend_rate` SMALLINT NOT NULL,
- `muscle_detail_normal_power` SMALLINT NOT NULL,
- `height` SMALLINT NOT NULL,
- `head_size` SMALLINT NOT NULL,
- `neck_offset` SMALLINT NOT NULL,
- `neck_scale` SMALLINT NOT NULL,
- `upper_body_scale_x` SMALLINT NOT NULL,
- `belly_size` SMALLINT NOT NULL,
- `teat_scale` SMALLINT NOT NULL,
- `tekubi_size` SMALLINT NOT NULL,
- `koshi_offset` SMALLINT NOT NULL,
- `koshi_size` SMALLINT NOT NULL,
- `ankle_offset` SMALLINT NOT NULL,
- `fat` SMALLINT NOT NULL,
- `muscle` SMALLINT NOT NULL,
- `motion_filter` SMALLINT NOT NULL,
- CONSTRAINT `fk_edit_info_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE
+ "character_common_id" INTEGER PRIMARY KEY NOT NULL,
+ "sex" SMALLINT NOT NULL,
+ "voice" SMALLINT NOT NULL,
+ "voice_pitch" SMALLINT NOT NULL,
+ "personality" SMALLINT NOT NULL,
+ "speech_freq" SMALLINT NOT NULL,
+ "body_type" SMALLINT NOT NULL,
+ "hair" SMALLINT NOT NULL,
+ "beard" SMALLINT NOT NULL,
+ "makeup" SMALLINT NOT NULL,
+ "scar" SMALLINT NOT NULL,
+ "eye_preset_no" SMALLINT NOT NULL,
+ "nose_preset_no" SMALLINT NOT NULL,
+ "mouth_preset_no" SMALLINT NOT NULL,
+ "eyebrow_tex_no" SMALLINT NOT NULL,
+ "color_skin" SMALLINT NOT NULL,
+ "color_hair" SMALLINT NOT NULL,
+ "color_beard" SMALLINT NOT NULL,
+ "color_eyebrow" SMALLINT NOT NULL,
+ "color_r_eye" SMALLINT NOT NULL,
+ "color_l_eye" SMALLINT NOT NULL,
+ "color_makeup" SMALLINT NOT NULL,
+ "sokutobu" SMALLINT NOT NULL,
+ "hitai" SMALLINT NOT NULL,
+ "mimi_jyouge" SMALLINT NOT NULL,
+ "kannkaku" SMALLINT NOT NULL,
+ "mabisasi_jyouge" SMALLINT NOT NULL,
+ "hanakuchi_jyouge" SMALLINT NOT NULL,
+ "ago_saki_haba" SMALLINT NOT NULL,
+ "ago_zengo" SMALLINT NOT NULL,
+ "ago_saki_jyouge" SMALLINT NOT NULL,
+ "hitomi_ookisa" SMALLINT NOT NULL,
+ "me_ookisa" SMALLINT NOT NULL,
+ "me_kaiten" SMALLINT NOT NULL,
+ "mayu_kaiten" SMALLINT NOT NULL,
+ "mimi_ookisa" SMALLINT NOT NULL,
+ "mimi_muki" SMALLINT NOT NULL,
+ "elf_mimi" SMALLINT NOT NULL,
+ "miken_takasa" SMALLINT NOT NULL,
+ "miken_haba" SMALLINT NOT NULL,
+ "hohobone_ryou" SMALLINT NOT NULL,
+ "hohobone_jyouge" SMALLINT NOT NULL,
+ "hohoniku" SMALLINT NOT NULL,
+ "erahone_jyouge" SMALLINT NOT NULL,
+ "erahone_haba" SMALLINT NOT NULL,
+ "hana_jyouge" SMALLINT NOT NULL,
+ "hana_haba" SMALLINT NOT NULL,
+ "hana_takasa" SMALLINT NOT NULL,
+ "hana_kakudo" SMALLINT NOT NULL,
+ "kuchi_haba" SMALLINT NOT NULL,
+ "kuchi_atsusa" SMALLINT NOT NULL,
+ "eyebrow_uv_offset_x" SMALLINT NOT NULL,
+ "eyebrow_uv_offset_y" SMALLINT NOT NULL,
+ "wrinkle" SMALLINT NOT NULL,
+ "wrinkle_albedo_blend_rate" SMALLINT NOT NULL,
+ "wrinkle_detail_normal_power" SMALLINT NOT NULL,
+ "muscle_albedo_blend_rate" SMALLINT NOT NULL,
+ "muscle_detail_normal_power" SMALLINT NOT NULL,
+ "height" SMALLINT NOT NULL,
+ "head_size" SMALLINT NOT NULL,
+ "neck_offset" SMALLINT NOT NULL,
+ "neck_scale" SMALLINT NOT NULL,
+ "upper_body_scale_x" SMALLINT NOT NULL,
+ "belly_size" SMALLINT NOT NULL,
+ "teat_scale" SMALLINT NOT NULL,
+ "tekubi_size" SMALLINT NOT NULL,
+ "koshi_offset" SMALLINT NOT NULL,
+ "koshi_size" SMALLINT NOT NULL,
+ "ankle_offset" SMALLINT NOT NULL,
+ "fat" SMALLINT NOT NULL,
+ "muscle" SMALLINT NOT NULL,
+ "motion_filter" SMALLINT NOT NULL,
+ CONSTRAINT fk_edit_info_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_status_info`
+CREATE TABLE IF NOT EXISTS ddon_status_info
(
- `character_common_id` INTEGER PRIMARY KEY NOT NULL,
- `hp` INT NOT NULL,
- `stamina` INT NOT NULL,
- `revive_point` TINYINT NOT NULL,
- `max_hp` INT NOT NULL,
- `max_stamina` INT NOT NULL,
- `white_hp` INT NOT NULL,
- `gain_hp` INT NOT NULL,
- `gain_stamina` INT NOT NULL,
- `gain_attack` INT NOT NULL,
- `gain_defense` INT NOT NULL,
- `gain_magic_attack` INT NOT NULL,
- `gain_magic_defense` INT NOT NULL,
- CONSTRAINT `fk_status_info_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE
+ "character_common_id" INTEGER PRIMARY KEY NOT NULL,
+ "hp" INTEGER NOT NULL,
+ "stamina" INTEGER NOT NULL,
+ "revive_point" SMALLINT NOT NULL,
+ "max_hp" INTEGER NOT NULL,
+ "max_stamina" INTEGER NOT NULL,
+ "white_hp" INTEGER NOT NULL,
+ "gain_hp" INTEGER NOT NULL,
+ "gain_stamina" INTEGER NOT NULL,
+ "gain_attack" INTEGER NOT NULL,
+ "gain_defense" INTEGER NOT NULL,
+ "gain_magic_attack" INTEGER NOT NULL,
+ "gain_magic_defense" INTEGER NOT NULL,
+ CONSTRAINT fk_status_info_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_character_matching_profile`
+CREATE TABLE IF NOT EXISTS ddon_character_matching_profile
(
- `character_id` INTEGER PRIMARY KEY NOT NULL,
- `entry_job` TINYINT NOT NULL,
- `entry_job_level` INT NOT NULL,
- `current_job` TINYINT NOT NULL,
- `current_job_level` INT NOT NULL,
- `objective_type1` INT NOT NULL,
- `objective_type2` INT NOT NULL,
- `play_style` INT NOT NULL,
- `comment` TEXT NOT NULL,
- `is_join_party` TINYINT NOT NULL,
- CONSTRAINT `fk_matching_profile_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE
+ "character_id" INTEGER PRIMARY KEY NOT NULL,
+ "entry_job" SMALLINT NOT NULL,
+ "entry_job_level" INTEGER NOT NULL,
+ "current_job" SMALLINT NOT NULL,
+ "current_job_level" INTEGER NOT NULL,
+ objective_type1 INTEGER NOT NULL,
+ objective_type2 INTEGER NOT NULL,
+ "play_style" INTEGER NOT NULL,
+ "comment" TEXT NOT NULL,
+ "is_join_party" BOOLEAN NOT NULL,
+ CONSTRAINT fk_matching_profile_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_character_arisen_profile`
+CREATE TABLE IF NOT EXISTS ddon_character_arisen_profile
(
- `character_id` INTEGER PRIMARY KEY NOT NULL,
- `background_id` TINYINT NOT NULL,
- `title_uid` INT NOT NULL,
- `title_index` INT NOT NULL,
- `motion_id` SMALLINT NOT NULL,
- `motion_frame_no` INT NOT NULL,
- CONSTRAINT `fk_arisen_profile_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE
+ "character_id" INTEGER PRIMARY KEY NOT NULL,
+ "background_id" SMALLINT NOT NULL,
+ "title_uid" INTEGER NOT NULL,
+ "title_index" INTEGER NOT NULL,
+ "motion_id" SMALLINT NOT NULL,
+ "motion_frame_no" INTEGER NOT NULL,
+ CONSTRAINT fk_arisen_profile_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_character_job_data`
+CREATE TABLE IF NOT EXISTS ddon_character_job_data
(
- `character_common_id` INTEGER NOT NULL,
- `job` TINYINT NOT NULL,
- `exp` INT NOT NULL,
- `job_point` INT NOT NULL,
- `lv` INT NOT NULL,
- `atk` SMALLINT NOT NULL,
- `def` SMALLINT NOT NULL,
- `m_atk` SMALLINT NOT NULL,
- `m_def` SMALLINT NOT NULL,
- `strength` SMALLINT NOT NULL,
- `down_power` SMALLINT NOT NULL,
- `shake_power` SMALLINT NOT NULL,
- `stun_power` SMALLINT NOT NULL,
- `consitution` SMALLINT NOT NULL,
- `guts` SMALLINT NOT NULL,
- `fire_resist` TINYINT NOT NULL,
- `ice_resist` TINYINT NOT NULL,
- `thunder_resist` TINYINT NOT NULL,
- `holy_resist` TINYINT NOT NULL,
- `dark_resist` TINYINT NOT NULL,
- `spread_resist` TINYINT NOT NULL,
- `freeze_resist` TINYINT NOT NULL,
- `shock_resist` TINYINT NOT NULL,
- `absorb_resist` TINYINT NOT NULL,
- `dark_elm_resist` TINYINT NOT NULL,
- `poison_resist` TINYINT NOT NULL,
- `slow_resist` TINYINT NOT NULL,
- `sleep_resist` TINYINT NOT NULL,
- `stun_resist` TINYINT NOT NULL,
- `wet_resist` TINYINT NOT NULL,
- `oil_resist` TINYINT NOT NULL,
- `seal_resist` TINYINT NOT NULL,
- `curse_resist` TINYINT NOT NULL,
- `soft_resist` TINYINT NOT NULL,
- `stone_resist` TINYINT NOT NULL,
- `gold_resist` TINYINT NOT NULL,
- `fire_reduce_resist` TINYINT NOT NULL,
- `ice_reduce_resist` TINYINT NOT NULL,
- `thunder_reduce_resist` TINYINT NOT NULL,
- `holy_reduce_resist` TINYINT NOT NULL,
- `dark_reduce_resist` TINYINT NOT NULL,
- `atk_down_resist` TINYINT NOT NULL,
- `def_down_resist` TINYINT NOT NULL,
- `m_atk_down_resist` TINYINT NOT NULL,
- `m_def_down_resist` TINYINT NOT NULL,
- PRIMARY KEY (`character_common_id`, `job`),
- CONSTRAINT `fk_character_job_data_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "exp" INTEGER NOT NULL,
+ "job_point" INTEGER NOT NULL,
+ "lv" INTEGER NOT NULL,
+ "atk" SMALLINT NOT NULL,
+ "def" SMALLINT NOT NULL,
+ "m_atk" SMALLINT NOT NULL,
+ "m_def" SMALLINT NOT NULL,
+ "strength" SMALLINT NOT NULL,
+ "down_power" SMALLINT NOT NULL,
+ "shake_power" SMALLINT NOT NULL,
+ "stun_power" SMALLINT NOT NULL,
+ "consitution" SMALLINT NOT NULL,
+ "guts" SMALLINT NOT NULL,
+ "fire_resist" SMALLINT NOT NULL,
+ "ice_resist" SMALLINT NOT NULL,
+ "thunder_resist" SMALLINT NOT NULL,
+ "holy_resist" SMALLINT NOT NULL,
+ "dark_resist" SMALLINT NOT NULL,
+ "spread_resist" SMALLINT NOT NULL,
+ "freeze_resist" SMALLINT NOT NULL,
+ "shock_resist" SMALLINT NOT NULL,
+ "absorb_resist" SMALLINT NOT NULL,
+ "dark_elm_resist" SMALLINT NOT NULL,
+ "poison_resist" SMALLINT NOT NULL,
+ "slow_resist" SMALLINT NOT NULL,
+ "sleep_resist" SMALLINT NOT NULL,
+ "stun_resist" SMALLINT NOT NULL,
+ "wet_resist" SMALLINT NOT NULL,
+ "oil_resist" SMALLINT NOT NULL,
+ "seal_resist" SMALLINT NOT NULL,
+ "curse_resist" SMALLINT NOT NULL,
+ "soft_resist" SMALLINT NOT NULL,
+ "stone_resist" SMALLINT NOT NULL,
+ "gold_resist" SMALLINT NOT NULL,
+ "fire_reduce_resist" SMALLINT NOT NULL,
+ "ice_reduce_resist" SMALLINT NOT NULL,
+ "thunder_reduce_resist" SMALLINT NOT NULL,
+ "holy_reduce_resist" SMALLINT NOT NULL,
+ "dark_reduce_resist" SMALLINT NOT NULL,
+ "atk_down_resist" SMALLINT NOT NULL,
+ "def_down_resist" SMALLINT NOT NULL,
+ "m_atk_down_resist" SMALLINT NOT NULL,
+ "m_def_down_resist" SMALLINT NOT NULL,
+ CONSTRAINT pk_character_job_data PRIMARY KEY (character_common_id, job),
+ CONSTRAINT fk_character_job_data_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_storage`
+CREATE TABLE IF NOT EXISTS ddon_storage
(
- `character_id` INTEGER NOT NULL,
- `storage_type` TINYINT NOT NULL,
- `slot_max` SMALLINT NOT NULL,
- `item_sort` BLOB NOT NULL,
- PRIMARY KEY (`character_id`, `storage_type`),
- CONSTRAINT `fk_storage_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE
+ "character_id" INTEGER NOT NULL,
+ "storage_type" SMALLINT NOT NULL,
+ "slot_max" SMALLINT NOT NULL,
+ "item_sort" BLOB NOT NULL,
+ CONSTRAINT pk_ddon_storage PRIMARY KEY (character_id, storage_type),
+ CONSTRAINT fk_storage_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_wallet_point`
+CREATE TABLE IF NOT EXISTS ddon_wallet_point
(
- `character_id` INTEGER NOT NULL,
- `type` TINYINT NOT NULL,
- `value` INTEGER NOT NULL,
- PRIMARY KEY (`character_id`, `type`),
- CONSTRAINT `fk_wallet_point_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE
+ "character_id" INTEGER NOT NULL,
+ "type" SMALLINT NOT NULL,
+ "value" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_wallet_point PRIMARY KEY (character_id, type),
+ CONSTRAINT fk_wallet_point_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_item`
+CREATE TABLE IF NOT EXISTS ddon_item
(
- `uid` TEXT NOT NULL,
- `item_id` INT NOT NULL,
- `unk3` TINYINT NOT NULL,
- `color` TINYINT NOT NULL,
- `plus_value` TINYINT NOT NULL,
- PRIMARY KEY (`uid`)
+ -- See Item.cs, uid is at most of size 8.
+ "uid" VARCHAR(8) NOT NULL,
+ "item_id" INTEGER NOT NULL,
+ unk3 SMALLINT NOT NULL,
+ "color" SMALLINT NOT NULL,
+ "plus_value" SMALLINT NOT NULL,
+ PRIMARY KEY ("uid")
);
-CREATE TABLE IF NOT EXISTS `ddon_storage_item`
+CREATE TABLE IF NOT EXISTS ddon_storage_item
(
- `item_uid` TEXT NOT NULL,
- `character_id` INTEGER NOT NULL,
- `storage_type` TINYINT NOT NULL,
- `slot_no` SMALLINT NOT NULL,
- `item_num` INT NOT NULL,
- PRIMARY KEY (`character_id`, `storage_type`, `slot_no`),
- CONSTRAINT `fk_storage_item_item_uid` FOREIGN KEY (`item_uid`) REFERENCES `ddon_item` (`uid`) ON DELETE CASCADE,
- CONSTRAINT `fk_storage_item_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE
+ "item_uid" VARCHAR(8) NOT NULL,
+ "character_id" INTEGER NOT NULL,
+ "storage_type" SMALLINT NOT NULL,
+ "slot_no" SMALLINT NOT NULL,
+ "item_num" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_storage_item PRIMARY KEY (character_id, storage_type, slot_no),
+ CONSTRAINT fk_storage_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE,
+ CONSTRAINT fk_storage_item_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_equip_item`
+CREATE TABLE IF NOT EXISTS ddon_equip_item
(
- `item_uid` TEXT NOT NULL,
- `character_common_id` INTEGER NOT NULL,
- `job` TINYINT NOT NULL,
- `equip_type` TINYINT NOT NULL,
- `equip_slot` SMALLINT NOT NULL,
- PRIMARY KEY (`character_common_id`, `job`, `equip_type`, `equip_slot`),
- CONSTRAINT `fk_equip_item_item_uid` FOREIGN KEY (`item_uid`) REFERENCES `ddon_item` (`uid`) ON DELETE CASCADE,
- CONSTRAINT `fk_equip_item_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE
+ "item_uid" VARCHAR(8) NOT NULL,
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "equip_type" SMALLINT NOT NULL,
+ "equip_slot" SMALLINT NOT NULL,
+ CONSTRAINT pk_ddon_equip_item PRIMARY KEY (character_common_id, job, equip_type, equip_slot),
+ CONSTRAINT fk_equip_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE,
+ CONSTRAINT fk_equip_item_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_equip_job_item`
+CREATE TABLE IF NOT EXISTS ddon_equip_job_item
(
- `item_uid` TEXT NOT NULL,
- `character_common_id` INTEGER NOT NULL,
- `job` TINYINT NOT NULL,
- `equip_slot` SMALLINT NOT NULL,
- PRIMARY KEY (`character_common_id`, `job`, `equip_slot`),
- CONSTRAINT `fk_equip_job_item_item_uid` FOREIGN KEY (`item_uid`) REFERENCES `ddon_item` (`uid`) ON DELETE CASCADE,
- CONSTRAINT `fk_equip_job_item_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE
+ "item_uid" VARCHAR(8) NOT NULL,
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "equip_slot" SMALLINT NOT NULL,
+ CONSTRAINT pk_ddon_equip_job_item PRIMARY KEY (character_common_id, job, equip_slot),
+ CONSTRAINT fk_equip_job_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE,
+ CONSTRAINT fk_equip_job_item_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_normal_skill_param`
+CREATE TABLE IF NOT EXISTS ddon_normal_skill_param
(
- `character_common_id` INTEGER NOT NULL,
- `job` TINYINT NOT NULL,
- `skill_no` INT NOT NULL,
- `index` INT NOT NULL,
- `pre_skill_no` INT NOT NULL,
- PRIMARY KEY (`character_common_id`, `job`, `skill_no`),
- CONSTRAINT `fk_normal_skill_param_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "skill_no" INTEGER NOT NULL,
+ "index" INTEGER NOT NULL,
+ "pre_skill_no" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_normal_skill_param PRIMARY KEY (character_common_id, job, skill_no),
+ CONSTRAINT fk_normal_skill_param_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_learned_custom_skill`
+CREATE TABLE IF NOT EXISTS ddon_learned_custom_skill
(
- `character_common_id` INTEGER NOT NULL,
- `job` TINYINT NOT NULL,
- `skill_id` INT NOT NULL,
- `skill_lv` TINYINT NOT NULL,
- PRIMARY KEY (`character_common_id`, `job`, `skill_id`),
- CONSTRAINT `fk_learned_custom_skill_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "skill_id" INTEGER NOT NULL,
+ "skill_lv" SMALLINT NOT NULL,
+ PRIMARY KEY (character_common_id, job, skill_id),
+ CONSTRAINT fk_learned_custom_skill_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_equipped_custom_skill`
+CREATE TABLE IF NOT EXISTS ddon_equipped_custom_skill
(
- `character_common_id` INTEGER NOT NULL,
- `job` TINYINT NOT NULL,
- `slot_no` TINYINT NOT NULL,
- `skill_id` INT NOT NULL,
- PRIMARY KEY (`character_common_id`, `job`, `slot_no`),
- CONSTRAINT `fk_equipped_custom_skill_character_common_id` FOREIGN KEY (`character_common_id`, `job`, `skill_id`) REFERENCES `ddon_learned_custom_skill` (`character_common_id`, `job`, `skill_id`) ON DELETE CASCADE
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "slot_no" SMALLINT NOT NULL,
+ "skill_id" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_equipped_custom_skill PRIMARY KEY (character_common_id, job, slot_no),
+ CONSTRAINT fk_equipped_custom_skill_character_common_id FOREIGN KEY (character_common_id, job, skill_id) REFERENCES ddon_learned_custom_skill (character_common_id, job, skill_id) ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_learned_ability`
+CREATE TABLE IF NOT EXISTS ddon_learned_ability
(
- `character_common_id` INTEGER NOT NULL,
- `job` TINYINT NOT NULL,
- `ability_id` INT NOT NULL,
- `ability_lv` TINYINT NOT NULL,
- PRIMARY KEY (`character_common_id`, `job`, `ability_id`),
- CONSTRAINT `fk_learned_ability_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "ability_id" INTEGER NOT NULL,
+ "ability_lv" SMALLINT NOT NULL,
+ PRIMARY KEY (character_common_id, job, ability_id),
+ CONSTRAINT fk_learned_ability_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_equipped_ability`
+CREATE TABLE IF NOT EXISTS ddon_equipped_ability
(
- `character_common_id` INTEGER NOT NULL,
- `equipped_to_job` TINYINT NOT NULL,
- `job` TINYINT NOT NULL,
- `slot_no` TINYINT NOT NULL,
- `ability_id` INT NOT NULL,
- PRIMARY KEY (`character_common_id`, `equipped_to_job`, `slot_no`),
- CONSTRAINT `fk_equipped_ability_character_common_id` FOREIGN KEY (`character_common_id`, `job`, `ability_id`) REFERENCES `ddon_learned_ability` (`character_common_id`, `job`, `ability_id`) ON DELETE CASCADE
+ "character_common_id" INTEGER NOT NULL,
+ "equipped_to_job" SMALLINT NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "slot_no" SMALLINT NOT NULL,
+ "ability_id" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_equipped_ability PRIMARY KEY (character_common_id, equipped_to_job, slot_no),
+ CONSTRAINT fk_equipped_ability_character_common_id FOREIGN KEY (character_common_id, job, ability_id) REFERENCES ddon_learned_ability (character_common_id, job, ability_id) ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_shortcut`
+CREATE TABLE IF NOT EXISTS ddon_shortcut
(
- `character_id` INTEGER NOT NULL,
- `page_no` INTEGER NOT NULL,
- `button_no` INTEGER NOT NULL,
- `shortcut_id` INTEGER NOT NULL,
- `u32_data` INTEGER NOT NULL,
- `f32_data` INTEGER NOT NULL,
- `exex_type` TINYINT NOT NULL,
- PRIMARY KEY (`character_id`, `page_no`, `button_no`),
- CONSTRAINT `fk_shortcut_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE
+ "character_id" INTEGER NOT NULL,
+ "page_no" INTEGER NOT NULL,
+ "button_no" INTEGER NOT NULL,
+ "shortcut_id" INTEGER NOT NULL,
+ u32_data INTEGER NOT NULL,
+ f32_data INTEGER NOT NULL,
+ "exex_type" SMALLINT NOT NULL,
+ CONSTRAINT pk_ddon_shortcut PRIMARY KEY (character_id, page_no, button_no),
+ CONSTRAINT fk_shortcut_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_communication_shortcut`
+CREATE TABLE IF NOT EXISTS ddon_communication_shortcut
(
- `character_id` INTEGER NOT NULL,
- `page_no` INTEGER NOT NULL,
- `button_no` INTEGER NOT NULL,
- `type` TINYINT NOT NULL,
- `category` TINYINT NOT NULL,
- `id` INTEGER NOT NULL,
- PRIMARY KEY (`character_id`, `page_no`, `button_no`),
- CONSTRAINT `fk_communication_shortcut_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE
+ "character_id" INTEGER NOT NULL,
+ "page_no" INTEGER NOT NULL,
+ "button_no" INTEGER NOT NULL,
+ "type" SMALLINT NOT NULL,
+ "category" SMALLINT NOT NULL,
+ "id" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_communication_shortcut PRIMARY KEY (character_id, page_no, button_no),
+ CONSTRAINT fk_communication_shortcut_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_pawn_reaction`
+CREATE TABLE IF NOT EXISTS ddon_pawn_reaction
(
- `pawn_id` INTEGER NOT NULL,
- `reaction_type` TINYINT NOT NULL,
- `motion_no` INTEGER NOT NULL,
- PRIMARY KEY (`pawn_id`,`reaction_type`),
- CONSTRAINT `fk_pawn_reaction_pawn_id` FOREIGN KEY (`pawn_id`) REFERENCES `ddon_pawn` (`pawn_id`) ON DELETE CASCADE
+ "pawn_id" INTEGER NOT NULL,
+ "reaction_type" SMALLINT NOT NULL,
+ "motion_no" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_pawn_reaction PRIMARY KEY (pawn_id, reaction_type),
+ CONSTRAINT fk_pawn_reaction_pawn_id FOREIGN KEY ("pawn_id") REFERENCES ddon_pawn ("pawn_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_sp_skill`
+CREATE TABLE IF NOT EXISTS ddon_sp_skill
(
- `pawn_id` INTEGER NOT NULL,
- `sp_skill_id` TINYINT NOT NULL,
- `sp_skill_lv` TINYINT NOT NULL,
- PRIMARY KEY (`pawn_id`),
- CONSTRAINT `fk_sp_skill_pawn_id` FOREIGN KEY (`pawn_id`) REFERENCES `ddon_pawn` (`pawn_id`) ON DELETE CASCADE
+ "pawn_id" INTEGER NOT NULL,
+ "sp_skill_id" SMALLINT NOT NULL,
+ "sp_skill_lv" SMALLINT NOT NULL,
+ CONSTRAINT pk_ddon_sp_skill PRIMARY KEY ("pawn_id"),
+ CONSTRAINT fk_sp_skill_pawn_id FOREIGN KEY ("pawn_id") REFERENCES ddon_pawn ("pawn_id") ON DELETE CASCADE
);
-CREATE TABLE IF NOT EXISTS `ddon_game_token`
+CREATE TABLE IF NOT EXISTS ddon_game_token
(
- `account_id` INTEGER PRIMARY KEY NOT NULL,
- `character_id` INTEGER NOT NULL,
- `token` TEXT NOT NULL,
- `created` DATETIME NOT NULL,
- CONSTRAINT `uq_game_token_token` UNIQUE (`token`),
- CONSTRAINT `fk_game_token_account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`),
- CONSTRAINT `fk_game_token_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`)
+ "account_id" INTEGER PRIMARY KEY NOT NULL,
+ "character_id" INTEGER NOT NULL,
+ "token" TEXT NOT NULL,
+ "created" DATETIME NOT NULL,
+ CONSTRAINT uq_game_token_token UNIQUE ("token"),
+ CONSTRAINT fk_game_token_account_id FOREIGN KEY ("account_id") REFERENCES account ("id"),
+ CONSTRAINT fk_game_token_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id")
);
-CREATE TABLE IF NOT EXISTS `ddon_connection`
+CREATE TABLE IF NOT EXISTS ddon_connection
(
- `server_id` INTEGER NOT NULL,
- `account_id` INTEGER NOT NULL,
- `type` INTEGER NOT NULL,
- `created` DATETIME NOT NULL,
- CONSTRAINT `uq_connection_server_id_account_id` UNIQUE (`server_id`, `account_id`),
- CONSTRAINT `fk_game_token_account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`)
-);
\ No newline at end of file
+ "server_id" INTEGER NOT NULL,
+ "account_id" INTEGER NOT NULL,
+ "type" INTEGER NOT NULL,
+ "created" DATETIME NOT NULL,
+ CONSTRAINT uq_connection_server_id_account_id UNIQUE (server_id, account_id),
+ CONSTRAINT fk_connection_token_account_id FOREIGN KEY ("account_id") REFERENCES account ("id")
+);
diff --git a/Arrowgene.Ddon.Database/Model/DatabaseType.cs b/Arrowgene.Ddon.Database/Model/DatabaseType.cs
index 50bafb857..4add44d0a 100644
--- a/Arrowgene.Ddon.Database/Model/DatabaseType.cs
+++ b/Arrowgene.Ddon.Database/Model/DatabaseType.cs
@@ -3,5 +3,7 @@
public enum DatabaseType
{
SQLite,
+ PostgreSQL,
+ MariaDb
}
}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDb.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDb.cs
index 2e6ed9bb6..8920455a4 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDb.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDb.cs
@@ -8,11 +8,12 @@ namespace Arrowgene.Ddon.Database.Sql.Core
///
/// Implementation of Ddon database operations.
///
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonSqlDb));
+ private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonSqlDb));
public DdonSqlDb()
{
@@ -34,13 +35,13 @@ public static string BuildQueryField(string table, params string[][] fieldLists)
string field = fieldList[j];
if(table != null)
{
- sb.Append('`');
+ sb.Append('\"');
sb.Append(table);
- sb.Append("`.");
+ sb.Append("\".");
}
- sb.Append('`');
+ sb.Append('\"');
sb.Append(field);
- sb.Append('`');
+ sb.Append('\"');
if (j < fieldList.Length - 1)
{
sb.Append(", ");
@@ -52,6 +53,11 @@ public static string BuildQueryField(string table, params string[][] fieldLists)
}
public static string BuildQueryUpdate(params string[][] fieldLists)
+ {
+ return BuildQueryUpdateWithPrefix("@", fieldLists);
+ }
+
+ protected static string BuildQueryUpdateWithPrefix(string prefix, params string[][] fieldLists)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < fieldLists.Length; i++)
@@ -60,7 +66,7 @@ public static string BuildQueryUpdate(params string[][] fieldLists)
for (int j = 0; j < fieldList.Length; j++)
{
string field = fieldList[j];
- sb.Append($"`{field}`=@{field}");
+ sb.Append($"\"{field}\"={prefix}{field}");
if (j < fieldList.Length - 1)
{
sb.Append(", ");
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbAccount.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbAccount.cs
index 9ab0776fa..714a502c6 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbAccount.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbAccount.cs
@@ -1,24 +1,26 @@
using System;
using System.Data.Common;
+using System.Text;
using Arrowgene.Ddon.Database.Model;
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
private static readonly string[] AccountFields = new string[]
{
"name", "normal_name", "hash", "mail", "mail_verified", "mail_verified_at", "mail_token", "password_token", "login_token", "login_token_created", "state", "last_login", "created"
};
-
- private static readonly string SqlInsertAccount = $"INSERT INTO `account` ({BuildQueryField(AccountFields)}) VALUES ({BuildQueryInsert(AccountFields)});";
- private static readonly string SqlSelectAccountById = $"SELECT `id`, {BuildQueryField(AccountFields)} FROM `account` WHERE `id`=@id;";
- private static readonly string SqlSelectAccountByName = $"SELECT `id`, {BuildQueryField(AccountFields)} FROM `account` WHERE `normal_name`=@normal_name;";
- private static readonly string SqlSelectAccountByLoginToken = $"SELECT `id`, {BuildQueryField(AccountFields)} FROM `account` WHERE `login_token`=@login_token;";
- private static readonly string SqlUpdateAccount = $"UPDATE `account` SET {BuildQueryUpdate(AccountFields)} WHERE `id`=@id;";
- private const string SqlDeleteAccount = "DELETE FROM `account` WHERE `id`=@id;";
+
+ private static readonly string SqlInsertAccount = $"INSERT INTO \"account\" ({BuildQueryField(AccountFields)}) VALUES ({BuildQueryInsert(AccountFields)});";
+ private static readonly string SqlSelectAccountById = $"SELECT \"id\", {BuildQueryField(AccountFields)} FROM \"account\" WHERE \"id\"=@id;";
+ private static readonly string SqlSelectAccountByName = $"SELECT \"id\", {BuildQueryField(AccountFields)} FROM \"account\" WHERE \"normal_name\"=@normal_name;";
+ private static readonly string SqlSelectAccountByLoginToken = $"SELECT \"id\", {BuildQueryField(AccountFields)} FROM \"account\" WHERE \"login_token\"=@login_token;";
+ private static readonly string SqlUpdateAccount = $"UPDATE \"account\" SET {BuildQueryUpdate(AccountFields)} WHERE \"id\"=@id;";
+ private const string SqlDeleteAccount = "DELETE FROM \"account\" WHERE \"id\"=@id;";
public Account CreateAccount(string name, string mail, string hash)
{
@@ -28,7 +30,14 @@ public Account CreateAccount(string name, string mail, string hash)
account.Mail = mail;
account.Hash = hash;
account.State = AccountStateType.User;
- account.Created = DateTime.Now;
+ account.Created = DateTime.UtcNow;
+ account.MailToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(mail));
+ account.PasswordToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(name));
+ account.LoginToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(name));
+ account.LoginTokenCreated = DateTime.UtcNow;
+ account.MailVerifiedAt = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
+ account.LastAuthentication = DateTime.UtcNow;
+ Logger.Info($"Creating account: {account}");
int rowsAffected = ExecuteNonQuery(SqlInsertAccount, command =>
{
AddParameter(command, "@name", account.Name);
@@ -50,11 +59,11 @@ public Account CreateAccount(string name, string mail, string hash)
return null;
}
- account.Id = (int) autoIncrement;
-
+ account.Id = (int)autoIncrement;
+
return account;
}
-
+
public Account SelectAccountByName(string accountName)
{
accountName = accountName.ToLowerInvariant();
@@ -128,7 +137,7 @@ public bool DeleteAccount(int accountId)
return rowsAffected > NoRowsAffected;
}
- private Account ReadAccount(DbDataReader reader)
+ private Account ReadAccount(TReader reader)
{
Account account = new Account();
account.Id = GetInt32(reader, "id");
@@ -142,7 +151,7 @@ private Account ReadAccount(DbDataReader reader)
account.PasswordToken = GetStringNullable(reader, "password_token");
account.LoginToken = GetStringNullable(reader, "login_token");
account.LoginTokenCreated = GetDateTimeNullable(reader, "login_token_created");
- account.State = (AccountStateType) GetInt32(reader, "state");
+ account.State = (AccountStateType)GetInt32(reader, "state");
account.LastAuthentication = GetDateTimeNullable(reader, "last_login");
account.Created = GetDateTime(reader, "created");
return account;
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs
index 1f1099179..8ca2643ce 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs
@@ -1,14 +1,16 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
+using Arrowgene.Ddon.Database.Model;
using Arrowgene.Ddon.Shared.Entity.Structure;
using Arrowgene.Ddon.Shared.Model;
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
private static readonly string[] CharacterFields = new string[]
{
@@ -26,46 +28,46 @@ public abstract partial class DdonSqlDb : SqlDb
"character_id", "background_id", "title_uid", "title_index", "motion_id", "motion_frame_no"
};
- private readonly string SqlInsertCharacter = $"INSERT INTO `ddon_character` ({BuildQueryField(CharacterFields)}) VALUES ({BuildQueryInsert(CharacterFields)});";
- private static readonly string SqlUpdateCharacter = $"UPDATE `ddon_character` SET {BuildQueryUpdate(CharacterFields)} WHERE `character_id` = @character_id;";
- private static readonly string SqlSelectCharacter = $"SELECT `ddon_character`.`character_id`, {BuildQueryField(CharacterFields)} FROM `ddon_character` WHERE `character_id` = @character_id;";
- private static readonly string SqlSelectCharactersByAccountId = $"SELECT `ddon_character`.`character_id`, {BuildQueryField(CharacterFields)} FROM `ddon_character` WHERE `account_id` = @account_id;";
- private readonly string SqlSelectAllCharacterData = $"SELECT `ddon_character`.`character_id`, {BuildQueryField("ddon_character", CharacterFields)}, `ddon_character_common`.`character_common_id`, {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}, {BuildQueryField("ddon_character_matching_profile", CDataMatchingProfileFields)}, {BuildQueryField("ddon_character_arisen_profile", CDataArisenProfileFields)} "
- + "FROM `ddon_character` "
- + "LEFT JOIN `ddon_character_common` ON `ddon_character_common`.`character_common_id` = `ddon_character`.`character_common_id` "
- + "LEFT JOIN `ddon_edit_info` ON `ddon_edit_info`.`character_common_id` = `ddon_character`.`character_common_id` "
- + "LEFT JOIN `ddon_status_info` ON `ddon_status_info`.`character_common_id` = `ddon_character`.`character_common_id` "
- + "LEFT JOIN `ddon_character_matching_profile` ON `ddon_character_matching_profile`.`character_id` = `ddon_character`.`character_id` "
- + "LEFT JOIN `ddon_character_arisen_profile` ON `ddon_character_arisen_profile`.`character_id` = `ddon_character`.`character_id` "
- + "WHERE `ddon_character`.`character_id` = @character_id";
- private readonly string SqlSelectAllCharactersDataByAccountId = $"SELECT `ddon_character`.`character_id`, {BuildQueryField("ddon_character", CharacterFields)}, `ddon_character_common`.`character_common_id`, {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}, {BuildQueryField("ddon_character_matching_profile", CDataMatchingProfileFields)}, {BuildQueryField("ddon_character_arisen_profile", CDataArisenProfileFields)} "
- + "FROM `ddon_character` "
- + "LEFT JOIN `ddon_character_common` ON `ddon_character_common`.`character_common_id` = `ddon_character`.`character_common_id` "
- + "LEFT JOIN `ddon_edit_info` ON `ddon_edit_info`.`character_common_id` = `ddon_character`.`character_common_id` "
- + "LEFT JOIN `ddon_status_info` ON `ddon_status_info`.`character_common_id` = `ddon_character`.`character_common_id` "
- + "LEFT JOIN `ddon_character_matching_profile` ON `ddon_character_matching_profile`.`character_id` = `ddon_character`.`character_id` "
- + "LEFT JOIN `ddon_character_arisen_profile` ON `ddon_character_arisen_profile`.`character_id` = `ddon_character`.`character_id` "
- + "WHERE `account_id` = @account_id";
- private const string SqlDeleteCharacter = "DELETE FROM `ddon_character_common` WHERE EXISTS (SELECT 1 FROM `ddon_character` WHERE `ddon_character_common`.`character_common_id`=`ddon_character`.`character_common_id` AND `character_id`=@character_id);";
-
-
- private readonly string SqlInsertCharacterMatchingProfile = $"INSERT INTO `ddon_character_matching_profile` ({BuildQueryField(CDataMatchingProfileFields)}) VALUES ({BuildQueryInsert(CDataMatchingProfileFields)});";
- private static readonly string SqlUpdateCharacterMatchingProfile = $"UPDATE `ddon_character_matching_profile` SET {BuildQueryUpdate(CDataMatchingProfileFields)} WHERE `character_id` = @character_id;";
- private static readonly string SqlSelectCharacterMatchingProfile = $"SELECT {BuildQueryField(CDataMatchingProfileFields)} FROM `ddon_character_matching_profile` WHERE `character_id` = @character_id;";
- private const string SqlDeleteCharacterMatchingProfile = "DELETE FROM `ddon_character_matching_profile` WHERE `character_id`=@character_id;";
-
-
- private readonly string SqlInsertCharacterArisenProfile = $"INSERT INTO `ddon_character_arisen_profile` ({BuildQueryField(CDataArisenProfileFields)}) VALUES ({BuildQueryInsert(CDataArisenProfileFields)});";
- private static readonly string SqlUpdateCharacterArisenProfile = $"UPDATE `ddon_character_arisen_profile` SET {BuildQueryUpdate(CDataArisenProfileFields)} WHERE `character_id` = @character_id;";
- private static readonly string SqlSelectCharacterArisenProfile = $"SELECT {BuildQueryField(CDataArisenProfileFields)} FROM `ddon_character_arisen_profile` WHERE `character_id` = @character_id;";
- private const string SqlDeleteCharacterArisenProfile = "DELETE FROM `ddon_character_arisen_profile` WHERE `character_id`=@character_id;";
+ private readonly string SqlInsertCharacter = $"INSERT INTO \"ddon_character\" ({BuildQueryField(CharacterFields)}) VALUES ({BuildQueryInsert(CharacterFields)});";
+ private static readonly string SqlUpdateCharacter = $"UPDATE \"ddon_character\" SET {BuildQueryUpdate(CharacterFields)} WHERE \"character_id\" = @character_id;";
+ private static readonly string SqlSelectCharacter = $"SELECT \"ddon_character\".\"character_id\", {BuildQueryField(CharacterFields)} FROM \"ddon_character\" WHERE \"character_id\" = @character_id;";
+ private static readonly string SqlSelectCharactersByAccountId = $"SELECT \"ddon_character\".\"character_id\", {BuildQueryField(CharacterFields)} FROM \"ddon_character\" WHERE \"account_id\" = @account_id;";
+ private readonly string SqlSelectAllCharacterData = $"SELECT \"ddon_character\".\"character_id\", {BuildQueryField("ddon_character", CharacterFields)}, \"ddon_character_common\".\"character_common_id\", {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}, {BuildQueryField("ddon_character_matching_profile", CDataMatchingProfileFields)}, {BuildQueryField("ddon_character_arisen_profile", CDataArisenProfileFields)} "
+ + "FROM \"ddon_character\" "
+ + "LEFT JOIN \"ddon_character_common\" ON \"ddon_character_common\".\"character_common_id\" = \"ddon_character\".\"character_common_id\" "
+ + "LEFT JOIN \"ddon_edit_info\" ON \"ddon_edit_info\".\"character_common_id\" = \"ddon_character\".\"character_common_id\" "
+ + "LEFT JOIN \"ddon_status_info\" ON \"ddon_status_info\".\"character_common_id\" = \"ddon_character\".\"character_common_id\" "
+ + "LEFT JOIN \"ddon_character_matching_profile\" ON \"ddon_character_matching_profile\".\"character_id\" = \"ddon_character\".\"character_id\" "
+ + "LEFT JOIN \"ddon_character_arisen_profile\" ON \"ddon_character_arisen_profile\".\"character_id\" = \"ddon_character\".\"character_id\" "
+ + "WHERE \"ddon_character\".\"character_id\" = @character_id";
+ private readonly string SqlSelectAllCharactersDataByAccountId = $"SELECT \"ddon_character\".\"character_id\", {BuildQueryField("ddon_character", CharacterFields)}, \"ddon_character_common\".\"character_common_id\", {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}, {BuildQueryField("ddon_character_matching_profile", CDataMatchingProfileFields)}, {BuildQueryField("ddon_character_arisen_profile", CDataArisenProfileFields)} "
+ + "FROM \"ddon_character\" "
+ + "LEFT JOIN \"ddon_character_common\" ON \"ddon_character_common\".\"character_common_id\" = \"ddon_character\".\"character_common_id\" "
+ + "LEFT JOIN \"ddon_edit_info\" ON \"ddon_edit_info\".\"character_common_id\" = \"ddon_character\".\"character_common_id\" "
+ + "LEFT JOIN \"ddon_status_info\" ON \"ddon_status_info\".\"character_common_id\" = \"ddon_character\".\"character_common_id\" "
+ + "LEFT JOIN \"ddon_character_matching_profile\" ON \"ddon_character_matching_profile\".\"character_id\" = \"ddon_character\".\"character_id\" "
+ + "LEFT JOIN \"ddon_character_arisen_profile\" ON \"ddon_character_arisen_profile\".\"character_id\" = \"ddon_character\".\"character_id\" "
+ + "WHERE \"account_id\" = @account_id";
+ private const string SqlDeleteCharacter = "DELETE FROM \"ddon_character_common\" WHERE EXISTS (SELECT 1 FROM \"ddon_character\" WHERE \"ddon_character_common\".\"character_common_id\"=\"ddon_character\".\"character_common_id\" AND \"character_id\"=@character_id);";
+
+
+ private readonly string SqlInsertCharacterMatchingProfile = $"INSERT INTO \"ddon_character_matching_profile\" ({BuildQueryField(CDataMatchingProfileFields)}) VALUES ({BuildQueryInsert(CDataMatchingProfileFields)});";
+ private static readonly string SqlUpdateCharacterMatchingProfile = $"UPDATE \"ddon_character_matching_profile\" SET {BuildQueryUpdate(CDataMatchingProfileFields)} WHERE \"character_id\" = @character_id;";
+ private static readonly string SqlSelectCharacterMatchingProfile = $"SELECT {BuildQueryField(CDataMatchingProfileFields)} FROM \"ddon_character_matching_profile\" WHERE \"character_id\" = @character_id;";
+ private const string SqlDeleteCharacterMatchingProfile = "DELETE FROM \"ddon_character_matching_profile\" WHERE \"character_id\"=@character_id;";
+
+
+ private readonly string SqlInsertCharacterArisenProfile = $"INSERT INTO \"ddon_character_arisen_profile\" ({BuildQueryField(CDataArisenProfileFields)}) VALUES ({BuildQueryInsert(CDataArisenProfileFields)});";
+ private static readonly string SqlUpdateCharacterArisenProfile = $"UPDATE \"ddon_character_arisen_profile\" SET {BuildQueryUpdate(CDataArisenProfileFields)} WHERE \"character_id\" = @character_id;";
+ private static readonly string SqlSelectCharacterArisenProfile = $"SELECT {BuildQueryField(CDataArisenProfileFields)} FROM \"ddon_character_arisen_profile\" WHERE \"character_id\" = @character_id;";
+ private const string SqlDeleteCharacterArisenProfile = "DELETE FROM \"ddon_character_arisen_profile\" WHERE \"character_id\"=@character_id;";
public bool CreateCharacter(Character character)
{
return ExecuteInTransaction(conn =>
{
- character.Created = DateTime.Now;
+ character.Created = DateTime.UtcNow;
ExecuteNonQuery(conn, SqlInsertCharacterCommon, command => { AddParameter(command, character); }, out long commonId);
character.CommonId = (uint) commonId;
@@ -86,14 +88,14 @@ public bool CreateCharacter(Character character)
public bool UpdateCharacterBaseInfo(Character character)
{
- return UpdateCharacterBaseInfo(null, character);
+ using TCon connection = OpenNewConnection();
+ return UpdateCharacterBaseInfo(connection, character);
}
public bool UpdateCharacterBaseInfo(TCon conn, Character character)
{
int characterUpdateRowsAffected = ExecuteNonQuery(conn, SqlUpdateCharacter, command =>
{
- AddParameter(command, "@character_id", character.CharacterId);
AddParameter(command, character);
});
@@ -102,14 +104,14 @@ public bool UpdateCharacterBaseInfo(TCon conn, Character character)
public bool UpdateCharacterMatchingProfile(Character character)
{
- return UpdateCharacterMatchingProfile(null, character);
+ using TCon connection = OpenNewConnection();
+ return UpdateCharacterMatchingProfile(connection, character);
}
public bool UpdateCharacterMatchingProfile(TCon conn, Character character)
{
int characterUpdateRowsAffected = ExecuteNonQuery(conn, SqlUpdateCharacterMatchingProfile, command =>
{
- AddParameter(command, "@character_id", character.CharacterId);
AddParameter(command, character);
});
@@ -118,14 +120,14 @@ public bool UpdateCharacterMatchingProfile(TCon conn, Character character)
public bool UpdateCharacterArisenProfile(Character character)
{
- return UpdateCharacterArisenProfile(null, character);
+ using TCon connection = OpenNewConnection();
+ return UpdateCharacterArisenProfile(connection, character);
}
public bool UpdateCharacterArisenProfile(TCon conn, Character character)
{
int characterUpdateRowsAffected = ExecuteNonQuery(conn, SqlUpdateCharacterArisenProfile, command =>
{
- AddParameter(command, "@character_id", character.CharacterId);
AddParameter(command, character);
});
@@ -153,7 +155,8 @@ public Character SelectCharacter(uint characterId)
public List SelectCharactersByAccountId(int accountId)
{
List characters = new List();
- ExecuteInTransaction(conn => {
+ ExecuteInTransaction(conn =>
+ {
ExecuteReader(conn, SqlSelectAllCharactersDataByAccountId,
command => { AddParameter(command, "@account_id", accountId); }, reader =>
{
@@ -161,10 +164,12 @@ public List SelectCharactersByAccountId(int accountId)
{
Character character = ReadAllCharacterData(reader);
characters.Add(character);
-
- QueryCharacterData(conn, character);
}
});
+ foreach (var character in characters)
+ {
+ QueryCharacterData(conn, character);
+ }
});
return characters;
}
@@ -212,30 +217,30 @@ private void QueryCharacterData(TCon conn, Character character)
Tuple tuple = ReadStorage(reader);
character.Storage.addStorage(tuple.Item1, tuple.Item2);
}
-
- ExecuteReader(conn, SqlSelectStorageItemsByCharacter,
- command2 => { AddParameter(command2, "@character_id", character.CharacterId); },
- reader2 =>
+ });
+ ExecuteReader(conn, SqlSelectStorageItemsByCharacter,
+ command2 => { AddParameter(command2, "@character_id", character.CharacterId); },
+ reader2 =>
+ {
+ while(reader2.Read())
{
- while(reader2.Read())
- {
- string UId = GetString(reader2, "item_uid");
- StorageType storageType = (StorageType) GetByte(reader2, "storage_type");
- ushort slot = GetUInt16(reader2, "slot_no");
- uint itemNum = GetUInt32(reader2, "item_num");
-
- ExecuteReader(conn, SqlSelectItem,
- command3 => { AddParameter(command3, "@uid", UId); },
- reader3 =>
+ string UId = GetString(reader2, "item_uid");
+ StorageType storageType = (StorageType) GetByte(reader2, "storage_type");
+ ushort slot = GetUInt16(reader2, "slot_no");
+ uint itemNum = GetUInt32(reader2, "item_num");
+
+ using TCon connection = OpenNewConnection();
+ ExecuteReader(connection, SqlSelectItem,
+ command3 => { AddParameter(command3, "@uid", UId); },
+ reader3 =>
+ {
+ if(reader3.Read())
{
- if(reader3.Read())
- {
- Item item = ReadItem(reader3);
- character.Storage.setStorageItem(item, itemNum, storageType, slot);
- }
- });
- }
- });
+ Item item = ReadItem(reader3);
+ character.Storage.setStorageItem(item, itemNum, storageType, slot);
+ }
+ });
+ }
});
// Wallet Points
@@ -256,34 +261,22 @@ private void StoreCharacterData(TCon conn, Character character)
foreach(CDataShortCut shortcut in character.ShortCutList)
{
- ExecuteNonQuery(conn, SqlReplaceShortcut, command =>
- {
- AddParameter(command, character.CharacterId, shortcut);
- });
+ ReplaceShortcut(conn, character.CharacterId, shortcut);
}
foreach(CDataCommunicationShortCut communicationShortcut in character.CommunicationShortCutList)
{
- ExecuteNonQuery(conn, SqlReplaceCommunicationShortcut, command =>
- {
- AddParameter(command, character.CharacterId, communicationShortcut);
- });
+ ReplaceCommunicationShortcut(conn, character.CharacterId, communicationShortcut);
}
foreach(StorageType storageType in character.Storage.getAllStorages().Keys)
{
- ExecuteNonQuery(conn, SqlReplaceStorage, command =>
- {
- AddParameter(command, character.CharacterId, storageType, character.Storage.getStorage(storageType));
- });
+ ReplaceStorage(conn, character.CharacterId, storageType, character.Storage.getStorage(storageType));
}
foreach(CDataWalletPoint walletPoint in character.WalletPointList)
{
- ExecuteNonQuery(conn, SqlReplaceWalletPoint, command =>
- {
- AddParameter(command, character.CharacterId, walletPoint);
- });
+ ReplaceWalletPoint(conn, character.CharacterId, walletPoint);
}
}
@@ -327,7 +320,7 @@ private void CreateItems(TCon conn, Character character)
}
}
- private Character ReadAllCharacterData(DbDataReader reader)
+ private Character ReadAllCharacterData(TReader reader)
{
Character character = new Character();
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterCommon.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterCommon.cs
index 411d5f6d8..78a87bc28 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterCommon.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterCommon.cs
@@ -8,9 +8,10 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
private static readonly string[] CharacterCommonFields = new string[]
{
@@ -40,29 +41,29 @@ public abstract partial class DdonSqlDb : SqlDb
};
- private readonly string SqlInsertCharacterCommon = $"INSERT INTO `ddon_character_common` ({BuildQueryField(CharacterCommonFields)}) VALUES ({BuildQueryInsert(CharacterCommonFields)});";
- private readonly string SqlUpdateCharacterCommon = $"UPDATE `ddon_character_common` SET {BuildQueryUpdate(CharacterCommonFields)} WHERE `character_common_id` = @character_common_id;";
+ private readonly string SqlInsertCharacterCommon = $"INSERT INTO \"ddon_character_common\" ({BuildQueryField(CharacterCommonFields)}) VALUES ({BuildQueryInsert(CharacterCommonFields)});";
+ private readonly string SqlUpdateCharacterCommon = $"UPDATE \"ddon_character_common\" SET {BuildQueryUpdate(CharacterCommonFields)} WHERE \"character_common_id\" = @character_common_id;";
- private readonly string SqlInsertEditInfo = $"INSERT INTO `ddon_edit_info` ({BuildQueryField(CDataEditInfoFields)}) VALUES ({BuildQueryInsert(CDataEditInfoFields)});";
- private static readonly string SqlUpdateEditInfo = $"UPDATE `ddon_edit_info` SET {BuildQueryUpdate(CDataEditInfoFields)} WHERE `character_common_id` = @character_common_id;";
- private static readonly string SqlSelectEditInfo = $"SELECT {BuildQueryField(CDataEditInfoFields)} FROM `ddon_edit_info` WHERE `character_common_id` = @character_common_id;";
- private const string SqlDeleteEditInfo = "DELETE FROM `ddon_edit_info` WHERE `character_common_id`=@character_common_id;";
+ private readonly string SqlInsertEditInfo = $"INSERT INTO \"ddon_edit_info\" ({BuildQueryField(CDataEditInfoFields)}) VALUES ({BuildQueryInsert(CDataEditInfoFields)});";
+ private static readonly string SqlUpdateEditInfo = $"UPDATE \"ddon_edit_info\" SET {BuildQueryUpdate(CDataEditInfoFields)} WHERE \"character_common_id\" = @character_common_id;";
+ private static readonly string SqlSelectEditInfo = $"SELECT {BuildQueryField(CDataEditInfoFields)} FROM \"ddon_edit_info\" WHERE \"character_common_id\" = @character_common_id;";
+ private const string SqlDeleteEditInfo = "DELETE FROM \"ddon_edit_info\" WHERE \"character_common_id\"=@character_common_id;";
- private readonly string SqlInsertStatusInfo = $"INSERT INTO `ddon_status_info` ({BuildQueryField(CDataStatusInfoFields)}) VALUES ({BuildQueryInsert(CDataStatusInfoFields)});";
- private static readonly string SqlUpdateStatusInfo = $"UPDATE `ddon_status_info` SET {BuildQueryUpdate(CDataStatusInfoFields)} WHERE `character_common_id` = @character_common_id;";
- private static readonly string SqlSelectStatusInfo = $"SELECT {BuildQueryField(CDataStatusInfoFields)} FROM `ddon_status_info` WHERE `character_common_id` = @character_common_id;";
- private const string SqlDeleteStatusInfo = "DELETE FROM `ddon_status_info` WHERE `character_common_id`=@character_common_id;";
+ private readonly string SqlInsertStatusInfo = $"INSERT INTO \"ddon_status_info\" ({BuildQueryField(CDataStatusInfoFields)}) VALUES ({BuildQueryInsert(CDataStatusInfoFields)});";
+ private static readonly string SqlUpdateStatusInfo = $"UPDATE \"ddon_status_info\" SET {BuildQueryUpdate(CDataStatusInfoFields)} WHERE \"character_common_id\" = @character_common_id;";
+ private static readonly string SqlSelectStatusInfo = $"SELECT {BuildQueryField(CDataStatusInfoFields)} FROM \"ddon_status_info\" WHERE \"character_common_id\" = @character_common_id;";
+ private const string SqlDeleteStatusInfo = "DELETE FROM \"ddon_status_info\" WHERE \"character_common_id\"=@character_common_id;";
public bool UpdateCharacterCommonBaseInfo(CharacterCommon common)
{
- return UpdateCharacterCommonBaseInfo(null, common);
+ using TCon connection = OpenNewConnection();
+ return UpdateCharacterCommonBaseInfo(connection, common);
}
public bool UpdateCharacterCommonBaseInfo(TCon conn, CharacterCommon common)
{
int commonUpdateRowsAffected = ExecuteNonQuery(conn, SqlUpdateCharacterCommon, command =>
{
- AddParameter(command, "@character_common_id", common.CommonId);
AddParameter(command, common);
});
@@ -71,14 +72,14 @@ public bool UpdateCharacterCommonBaseInfo(TCon conn, CharacterCommon common)
public bool UpdateEditInfo(CharacterCommon common)
{
- return UpdateEditInfo(null, common);
+ using TCon connection = OpenNewConnection();
+ return UpdateEditInfo(connection, common);
}
public bool UpdateEditInfo(TCon conn, CharacterCommon common)
{
int commonUpdateRowsAffected = ExecuteNonQuery(conn, SqlUpdateEditInfo, command =>
{
- AddParameter(command, "@character_common_id", common.CommonId);
AddParameter(command, common);
});
@@ -87,14 +88,14 @@ public bool UpdateEditInfo(TCon conn, CharacterCommon common)
public bool UpdateStatusInfo(CharacterCommon common)
{
- return UpdateStatusInfo(null, common);
+ using TCon connection = OpenNewConnection();
+ return UpdateStatusInfo(connection, common);
}
public bool UpdateStatusInfo(TCon conn, CharacterCommon common)
{
int commonUpdateRowsAffected = ExecuteNonQuery(conn, SqlUpdateStatusInfo, command =>
{
- AddParameter(command, "@character_common_id", common.CommonId);
AddParameter(command, common);
});
@@ -125,7 +126,9 @@ private void QueryCharacterCommonData(TCon conn, CharacterCommon common)
JobId job = (JobId) GetByte(reader, "job");
EquipType equipType = (EquipType) GetByte(reader, "equip_type");
byte equipSlot = GetByte(reader, "equip_slot");
- ExecuteReader(conn, SqlSelectItem,
+
+ using TCon connection = OpenNewConnection();
+ ExecuteReader(connection, SqlSelectItem,
command2 => { AddParameter(command2, "@uid", UId); },
reader2 =>
{
@@ -148,7 +151,9 @@ private void QueryCharacterCommonData(TCon conn, CharacterCommon common)
string UId = GetString(reader, "item_uid");
JobId job = (JobId) GetByte(reader, "job");
byte equipSlot = GetByte(reader, "equip_slot");
- ExecuteReader(conn, SqlSelectItem,
+
+ using TCon connection = OpenNewConnection();
+ ExecuteReader(connection, SqlSelectItem,
command2 => { AddParameter(command2, "@uid", UId); },
reader2 =>
{
@@ -228,18 +233,12 @@ private void StoreCharacterCommonData(TCon conn, CharacterCommon common)
{
foreach(CDataCharacterJobData characterJobData in common.CharacterJobDataList)
{
- ExecuteNonQuery(conn, SqlReplaceCharacterJobData, command =>
- {
- AddParameter(command, common.CommonId, characterJobData);
- });
+ ReplaceCharacterJobData(conn, common.CommonId, characterJobData);
}
foreach(CDataNormalSkillParam normalSkillParam in common.LearnedNormalSkills)
{
- ExecuteNonQuery(conn, SqlReplaceNormalSkillParam, command =>
- {
- AddParameter(command, common.CommonId, normalSkillParam);
- });
+ ReplaceNormalSkillParam(conn, common.CommonId, normalSkillParam);
}
foreach(CustomSkill learnedSkills in common.LearnedCustomSkills)
@@ -258,10 +257,7 @@ private void StoreCharacterCommonData(TCon conn, CharacterCommon common)
byte slotNo = (byte)(i+1);
if(skill != null)
{
- ExecuteNonQuery(conn, SqlReplaceEquippedCustomSkill, command =>
- {
- AddParameter(command, common.CommonId, slotNo, skill);
- });
+ ReplaceEquippedCustomSkill(conn, common.CommonId, slotNo, skill);
}
}
}
@@ -283,16 +279,13 @@ private void StoreCharacterCommonData(TCon conn, CharacterCommon common)
byte slotNo = (byte)(i+1);
if(ability != null)
{
- ExecuteNonQuery(conn, SqlReplaceEquippedAbility, command =>
- {
- AddParameter(command, common.CommonId, equippedToJob, slotNo, ability);
- });
+ ReplaceEquippedAbility(conn, common.CommonId, equippedToJob, slotNo, ability);
}
}
}
}
- private void ReadAllCharacterCommonData(DbDataReader reader, CharacterCommon common)
+ private void ReadAllCharacterCommonData(TReader reader, CharacterCommon common)
{
common.CommonId = GetUInt32(reader, "character_common_id");
common.Job = (JobId) GetByte(reader, "job");
@@ -481,4 +474,4 @@ private void AddParameter(TCom command, CharacterCommon common)
AddParameter(command, "@gain_magic_defense", common.StatusInfo.GainMagicDefense);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterJobData.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterJobData.cs
index efe1b9bdd..d5d5a8936 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterJobData.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterJobData.cs
@@ -4,11 +4,12 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private static readonly string[] CDataCharacterJobDataFields = new string[]
+ protected static readonly string[] CDataCharacterJobDataFields = new string[]
{
"character_common_id", "job", "exp", "job_point", "lv", "atk", "def", "m_atk", "m_def", "strength", "down_power", "shake_power",
"stun_power", "consitution", "guts", "fire_resist", "ice_resist", "thunder_resist", "holy_resist", "dark_resist", "spread_resist",
@@ -18,30 +19,74 @@ public abstract partial class DdonSqlDb : SqlDb
"m_atk_down_resist", "m_def_down_resist"
};
- private readonly string SqlInsertCharacterJobData = $"INSERT INTO `ddon_character_job_data` ({BuildQueryField(CDataCharacterJobDataFields)}) VALUES ({BuildQueryInsert(CDataCharacterJobDataFields)});";
- private readonly string SqlReplaceCharacterJobData = $"INSERT OR REPLACE INTO `ddon_character_job_data` ({BuildQueryField(CDataCharacterJobDataFields)}) VALUES ({BuildQueryInsert(CDataCharacterJobDataFields)});";
- private static readonly string SqlUpdateCharacterJobData = $"UPDATE `ddon_character_job_data` SET {BuildQueryUpdate(CDataCharacterJobDataFields)} WHERE `character_common_id` = @character_common_id AND `job` = @job;";
- private static readonly string SqlSelectCharacterJobData = $"SELECT {BuildQueryField(CDataCharacterJobDataFields)} FROM `ddon_character_job_data` WHERE `character_common_id` = @character_common_id AND `job` = @job;";
- private static readonly string SqlSelectCharacterJobDataByCharacter = $"SELECT {BuildQueryField(CDataCharacterJobDataFields)} FROM `ddon_character_job_data` WHERE `character_common_id` = @character_common_id;";
- private const string SqlDeleteCharacterJobData = "DELETE FROM `ddon_character_job_data` WHERE `character_common_id`=@character_common_id AND `job` = @job;";
-
+ private readonly string SqlInsertCharacterJobData =
+ $"INSERT INTO \"ddon_character_job_data\" ({BuildQueryField(CDataCharacterJobDataFields)}) VALUES ({BuildQueryInsert(CDataCharacterJobDataFields)});";
+
+ protected virtual string SqlInsertIfNotExistsCharacterJobData { get; } =
+ $"INSERT INTO \"ddon_character_job_data\" ({BuildQueryField(CDataCharacterJobDataFields)}) SELECT {BuildQueryInsert(CDataCharacterJobDataFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_character_job_data\" WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job);";
+
+ private static readonly string SqlUpdateCharacterJobData =
+ $"UPDATE \"ddon_character_job_data\" SET {BuildQueryUpdate(CDataCharacterJobDataFields)} WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job;";
+
+ private static readonly string SqlSelectCharacterJobData =
+ $"SELECT {BuildQueryField(CDataCharacterJobDataFields)} FROM \"ddon_character_job_data\" WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job;";
+
+ private static readonly string SqlSelectCharacterJobDataByCharacter =
+ $"SELECT {BuildQueryField(CDataCharacterJobDataFields)} FROM \"ddon_character_job_data\" WHERE \"character_common_id\" = @character_common_id;";
+
+ private const string SqlDeleteCharacterJobData = "DELETE FROM \"ddon_character_job_data\" WHERE \"character_common_id\"=@character_common_id AND \"job\" = @job;";
+
public bool ReplaceCharacterJobData(uint commonId, CDataCharacterJobData replacedCharacterJobData)
{
- return ExecuteNonQuery(SqlReplaceCharacterJobData, command =>
+ using TCon connection = OpenNewConnection();
+ return ReplaceCharacterJobData(connection, commonId, replacedCharacterJobData);
+ }
+
+ public bool ReplaceCharacterJobData(TCon connection, uint commonId, CDataCharacterJobData replacedCharacterJobData)
+ {
+ Logger.Debug("Inserting character job data.");
+ if (!InsertIfNotExistsCharacterJobData(connection, commonId, replacedCharacterJobData))
{
- AddParameter(command, commonId, replacedCharacterJobData);
- }) == 1;
+ Logger.Debug("Character job data already exists, replacing.");
+ return UpdateCharacterJobData(connection, commonId, replacedCharacterJobData);
+ }
+ return true;
+ }
+
+ public bool InsertCharacterJobData(uint commonId, CDataCharacterJobData updatedCharacterJobData)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertCharacterJobData(connection, commonId, updatedCharacterJobData);
+ }
+
+ public bool InsertCharacterJobData(TCon connection, uint commonId, CDataCharacterJobData updatedCharacterJobData)
+ {
+ return ExecuteNonQuery(connection, SqlInsertCharacterJobData, command => { AddParameter(command, commonId, updatedCharacterJobData); }) == 1;
}
+ public bool InsertIfNotExistsCharacterJobData(uint commonId, CDataCharacterJobData updatedCharacterJobData)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertIfNotExistsCharacterJobData(connection, commonId, updatedCharacterJobData);
+ }
+
+ public bool InsertIfNotExistsCharacterJobData(TCon connection, uint commonId, CDataCharacterJobData updatedCharacterJobData)
+ {
+ return ExecuteNonQuery(connection, SqlInsertIfNotExistsCharacterJobData, command => { AddParameter(command, commonId, updatedCharacterJobData); }) == 1;
+ }
+
public bool UpdateCharacterJobData(uint commonId, CDataCharacterJobData updatedCharacterJobData)
{
- return ExecuteNonQuery(SqlUpdateCharacterJobData, command =>
- {
- AddParameter(command, commonId, updatedCharacterJobData);
- }) == 1;
+ using TCon connection = OpenNewConnection();
+ return UpdateCharacterJobData(connection, commonId, updatedCharacterJobData);
+ }
+
+ public bool UpdateCharacterJobData(TCon connection, uint commonId, CDataCharacterJobData updatedCharacterJobData)
+ {
+ return ExecuteNonQuery(connection, SqlUpdateCharacterJobData, command => { AddParameter(command, commonId, updatedCharacterJobData); }) == 1;
}
- private CDataCharacterJobData ReadCharacterJobData(DbDataReader reader)
+ private CDataCharacterJobData ReadCharacterJobData(TReader reader)
{
CDataCharacterJobData characterJobData = new CDataCharacterJobData();
characterJobData.Job = (JobId) GetByte(reader, "job");
@@ -140,4 +185,4 @@ private void AddParameter(TCom command, uint commonId, CDataCharacterJobData cha
AddParameter(command, "m_def_down_resist", characterJobData.MDefDownResist);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCommunicationShortcut.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCommunicationShortcut.cs
index 429b86e4b..1f1a741cf 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCommunicationShortcut.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCommunicationShortcut.cs
@@ -3,24 +3,45 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private static readonly string[] CommunicationShortcutFields = new string[]
+ protected static readonly string[] CommunicationShortcutFields = new string[]
{
"character_id", "page_no", "button_no", "type", "category", "id"
};
- private readonly string SqlInsertCommunicationShortcut = $"INSERT INTO `ddon_communication_shortcut` ({BuildQueryField(CommunicationShortcutFields)}) VALUES ({BuildQueryInsert(CommunicationShortcutFields)});";
- private readonly string SqlReplaceCommunicationShortcut = $"INSERT OR REPLACE INTO `ddon_communication_shortcut` ({BuildQueryField(CommunicationShortcutFields)}) VALUES ({BuildQueryInsert(CommunicationShortcutFields)});";
- private static readonly string SqlUpdateCommunicationShortcut = $"UPDATE `ddon_communication_shortcut` SET {BuildQueryUpdate(CommunicationShortcutFields)} WHERE `character_id`=@old_character_id AND `page_no`=@old_page_no AND `button_no`=@old_button_no";
- private static readonly string SqlSelectCommunicationShortcuts = $"SELECT {BuildQueryField(CommunicationShortcutFields)} FROM `ddon_communication_shortcut` WHERE `character_id`=@character_id;";
- private const string SqlDeleteCommunicationShortcut = "DELETE FROM `ddon_communication_shortcut` WHERE `character_id`=@character_id AND `page_no`=@page_no AND `button_no`=@button_no";
+ private readonly string SqlInsertCommunicationShortcut = $"INSERT INTO \"ddon_communication_shortcut\" ({BuildQueryField(CommunicationShortcutFields)}) VALUES ({BuildQueryInsert(CommunicationShortcutFields)});";
+ private readonly string SqlInsertIfNotExistsCommunicationShortcut = $"INSERT INTO \"ddon_communication_shortcut\" ({BuildQueryField(CommunicationShortcutFields)}) SELECT {BuildQueryInsert(CommunicationShortcutFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_communication_shortcut\" WHERE \"character_id\"=@character_id AND \"page_no\"=@page_no AND \"button_no\"=@button_no);";
+ private static readonly string SqlUpdateCommunicationShortcut = $"UPDATE \"ddon_communication_shortcut\" SET {BuildQueryUpdate(CommunicationShortcutFields)} WHERE \"character_id\"=@old_character_id AND \"page_no\"=@old_page_no AND \"button_no\"=@old_button_no";
+ private static readonly string SqlSelectCommunicationShortcuts = $"SELECT {BuildQueryField(CommunicationShortcutFields)} FROM \"ddon_communication_shortcut\" WHERE \"character_id\"=@character_id;";
+ private const string SqlDeleteCommunicationShortcut = "DELETE FROM \"ddon_communication_shortcut\" WHERE \"character_id\"=@character_id AND \"page_no\"=@page_no AND \"button_no\"=@button_no";
+ public bool InsertIfNotExistsCommunicationShortcut(uint characterId, CDataCommunicationShortCut communicationShortcut)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertIfNotExistsCommunicationShortcut(connection, characterId, communicationShortcut);
+ }
+
+ public bool InsertIfNotExistsCommunicationShortcut(TCon connection, uint characterId, CDataCommunicationShortCut communicationShortcut)
+ {
+ return ExecuteNonQuery(connection, SqlInsertIfNotExistsCommunicationShortcut, command =>
+ {
+ AddParameter(command, characterId, communicationShortcut);
+ }) == 1;
+ }
+
public bool InsertCommunicationShortcut(uint characterId, CDataCommunicationShortCut communicationShortcut)
{
- return ExecuteNonQuery(SqlInsertCommunicationShortcut, command =>
+ using TCon connection = OpenNewConnection();
+ return InsertCommunicationShortcut(connection, characterId, communicationShortcut);
+ }
+
+ public bool InsertCommunicationShortcut(TCon connection, uint characterId, CDataCommunicationShortCut communicationShortcut)
+ {
+ return ExecuteNonQuery(connection, SqlInsertCommunicationShortcut, command =>
{
AddParameter(command, characterId, communicationShortcut);
}) == 1;
@@ -28,16 +49,30 @@ public bool InsertCommunicationShortcut(uint characterId, CDataCommunicationShor
public bool ReplaceCommunicationShortcut(uint characterId, CDataCommunicationShortCut communicationShortcut)
{
- ExecuteNonQuery(SqlReplaceCommunicationShortcut, command =>
+ using TCon connection = OpenNewConnection();
+ return ReplaceCommunicationShortcut(connection, characterId, communicationShortcut);
+ }
+
+ public bool ReplaceCommunicationShortcut(TCon connection, uint characterId, CDataCommunicationShortCut communicationShortcut)
+ {
+ Logger.Debug("Inserting communication shortcut.");
+ if (!InsertIfNotExistsCommunicationShortcut(connection, characterId, communicationShortcut))
{
- AddParameter(command, characterId, communicationShortcut);
- });
+ Logger.Debug("Communication shortcut already exists, replacing.");
+ return UpdateCommunicationShortcut(connection, characterId, communicationShortcut.PageNo, communicationShortcut.ButtonNo, communicationShortcut);
+ }
return true;
}
public bool UpdateCommunicationShortcut(uint characterId, uint oldPageNo, uint oldButtonNo, CDataCommunicationShortCut updatedCommunicationShortcut)
{
- return ExecuteNonQuery(SqlUpdateCommunicationShortcut, command =>
+ using TCon connection = OpenNewConnection();
+ return UpdateCommunicationShortcut(connection, characterId, oldPageNo, oldButtonNo, updatedCommunicationShortcut);
+ }
+
+ public bool UpdateCommunicationShortcut(TCon connection, uint characterId, uint oldPageNo, uint oldButtonNo, CDataCommunicationShortCut updatedCommunicationShortcut)
+ {
+ return ExecuteNonQuery(connection, SqlUpdateCommunicationShortcut, command =>
{
AddParameter(command, characterId, updatedCommunicationShortcut);
AddParameter(command, "@old_character_id", characterId);
@@ -56,7 +91,7 @@ public bool DeleteCommunicationShortcut(uint characterId, uint pageNo, uint butt
}) == 1;
}
- private CDataCommunicationShortCut ReadCommunicationShortCut(DbDataReader reader)
+ private CDataCommunicationShortCut ReadCommunicationShortCut(TReader reader)
{
CDataCommunicationShortCut shortcut = new CDataCommunicationShortCut();
shortcut.PageNo = GetUInt32(reader, "page_no");
@@ -77,4 +112,4 @@ private void AddParameter(TCom command, uint characterId, CDataCommunicationShor
AddParameter(command, "id", shortcut.Id);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbConnection.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbConnection.cs
index c9a0e6f77..95d1e7521 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbConnection.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbConnection.cs
@@ -4,24 +4,25 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
private const string SqlInsertConnection =
- "INSERT INTO `ddon_connection` (`server_id`, `account_id`, `type`, `created`) VALUES (@server_id, @account_id, @type, @created);";
+ "INSERT INTO \"ddon_connection\" (\"server_id\", \"account_id\", \"type\", \"created\") VALUES (@server_id, @account_id, @type, @created);";
private const string SqlSelectConnectionsByAccountId =
- "SELECT `server_id`, `account_id`, `type`, `created` FROM `ddon_connection` WHERE `account_id` = @account_id;";
+ "SELECT \"server_id\", \"account_id\", \"type\", \"created\" FROM \"ddon_connection\" WHERE \"account_id\" = @account_id;";
private const string SqlDeleteConnectionsByAccountId =
- "DELETE FROM `ddon_connection` WHERE `account_id`=@account_id;";
+ "DELETE FROM \"ddon_connection\" WHERE \"account_id\"=@account_id;";
private const string SqlDeleteConnectionsByServerId =
- "DELETE FROM `ddon_connection` WHERE `server_id`=@server_id;";
+ "DELETE FROM \"ddon_connection\" WHERE \"server_id\"=@server_id;";
private const string SqlDeleteConnection =
- "DELETE FROM `ddon_connection` WHERE `server_id`=@server_id AND `account_id`=@account_id;";
+ "DELETE FROM \"ddon_connection\" WHERE \"server_id\"=@server_id AND \"account_id\"=@account_id;";
public bool InsertConnection(Connection connection)
{
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipItem.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipItem.cs
index 05c64b910..e6442357c 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipItem.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipItem.cs
@@ -4,50 +4,76 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private static readonly string[] CDataEquipItemFields = new string[]
+ protected static readonly string[] CDataEquipItemFields = new string[]
{
"item_uid", "character_common_id", "job", "equip_type", "equip_slot"
};
- private readonly string SqlInsertEquipItem = $"INSERT INTO `ddon_equip_item` ({BuildQueryField(CDataEquipItemFields)}) VALUES ({BuildQueryInsert(CDataEquipItemFields)});";
- private readonly string SqlReplaceEquipItem = $"INSERT OR REPLACE INTO `ddon_equip_item` ({BuildQueryField(CDataEquipItemFields)}) VALUES ({BuildQueryInsert(CDataEquipItemFields)});";
- private static readonly string SqlUpdateEquipItem = $"UPDATE `ddon_equip_item` SET {BuildQueryUpdate(CDataEquipItemFields)} WHERE `item_uid`=@item_uid AND `character_common_id`=@character_common_id AND `job`=@job AND `equip_type`=@equip_type AND `equip_slot`=@equip_slot;";
- private static readonly string SqlSelectEquipItemByCharacter = $"SELECT {BuildQueryField(CDataEquipItemFields)} FROM `ddon_equip_item` WHERE `character_common_id`=@character_common_id;";
- private static readonly string SqlDeleteEquipItem = "DELETE FROM `ddon_equip_item` WHERE `item_uid`=@item_uid AND `character_common_id`=@character_common_id AND `job`=@job AND `equip_type`=@equip_type AND `equip_slot`=@equip_slot;";
-
- public bool InsertEquipItem(TCon conn, uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId)
+ private readonly string SqlInsertEquipItem = $"INSERT INTO \"ddon_equip_item\" ({BuildQueryField(CDataEquipItemFields)}) VALUES ({BuildQueryInsert(CDataEquipItemFields)});";
+ private readonly string SqlInsertIfNotExistsEquipItem = $"INSERT INTO \"ddon_equip_item\" ({BuildQueryField(CDataEquipItemFields)}) SELECT {BuildQueryInsert(CDataEquipItemFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_equip_item\" WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"equip_type\"=@equip_type AND \"equip_slot\"=@equip_slot);";
+ private static readonly string SqlUpdateEquipItem = $"UPDATE \"ddon_equip_item\" SET {BuildQueryUpdate(CDataEquipItemFields)} WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"equip_type\"=@equip_type AND \"equip_slot\"=@equip_slot;";
+ private static readonly string SqlSelectEquipItemByCharacter = $"SELECT {BuildQueryField(CDataEquipItemFields)} FROM \"ddon_equip_item\" WHERE \"character_common_id\"=@character_common_id;";
+ private static readonly string SqlDeleteEquipItem = "DELETE FROM \"ddon_equip_item\" WHERE \"item_uid\"=@item_uid AND \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"equip_type\"=@equip_type AND \"equip_slot\"=@equip_slot;";
+
+ public bool InsertIfNotExistsEquipItem(uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId)
{
- return ExecuteNonQuery(conn, SqlInsertEquipItem, command =>
+ using TCon connection = OpenNewConnection();
+ return InsertIfNotExistsEquipItem(connection, commonId, job, equipType, equipSlot, itemUId);
+ }
+
+ public bool InsertIfNotExistsEquipItem(TCon conn, uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId)
+ {
+ return ExecuteNonQuery(conn, SqlInsertIfNotExistsEquipItem, command =>
{
AddParameter(command, commonId, job, equipType, equipSlot, itemUId);
}) == 1;
- }
-
+ }
+
public bool InsertEquipItem(uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId)
{
- return this.InsertEquipItem(null, commonId, job, equipType, equipSlot, itemUId);
+ using TCon connection = OpenNewConnection();
+ return InsertEquipItem(connection, commonId, job, equipType, equipSlot, itemUId);
}
-
- public bool ReplaceEquipItem(TCon conn, uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId)
+
+ public bool InsertEquipItem(TCon conn, uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId)
{
- return ExecuteNonQuery(conn, SqlReplaceEquipItem, command =>
+ return ExecuteNonQuery(conn, SqlInsertEquipItem, command =>
{
AddParameter(command, commonId, job, equipType, equipSlot, itemUId);
}) == 1;
}
-
+
public bool ReplaceEquipItem(uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId)
{
- return this.ReplaceEquipItem(null, commonId, job, equipType, equipSlot, itemUId);
+ using TCon connection = OpenNewConnection();
+ return ReplaceEquipItem(connection, commonId, job, equipType, equipSlot, itemUId);
+ }
+
+ public bool ReplaceEquipItem(TCon conn, uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId)
+ {
+ Logger.Debug("Inserting equip item.");
+ if (!InsertIfNotExistsEquipItem(conn, commonId, job, equipType, equipSlot, itemUId))
+ {
+ Logger.Debug("Equip item already exists, replacing.");
+ return UpdateEquipItem(conn, commonId, job, equipType, equipSlot, itemUId);
+ }
+ return true;
}
public bool UpdateEquipItem(uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId)
{
- return ExecuteNonQuery(SqlUpdateEquipItem, command =>
+ using TCon connection = OpenNewConnection();
+ return UpdateEquipItem(connection, commonId, job, equipType, equipSlot, itemUId);
+ }
+
+ public bool UpdateEquipItem(TCon connection, uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId)
+ {
+ return ExecuteNonQuery(connection, SqlUpdateEquipItem, command =>
{
AddParameter(command, commonId, job, equipType, equipSlot, itemUId);
}) == 1;
@@ -70,4 +96,4 @@ private void AddParameter(TCom command, uint commonId, JobId job, EquipType equi
AddParameter(command, "equip_slot", equipSlot);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipJobItem.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipJobItem.cs
index a090cf1a7..9506bdf99 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipJobItem.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipJobItem.cs
@@ -3,41 +3,49 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private static readonly string[] CDataEquipJobItemFields = new string[]
+ protected static readonly string[] CDataEquipJobItemFields = new string[]
{
"item_uid", "character_common_id", "job", "equip_slot"
};
- private readonly string SqlInsertEquipJobItem = $"INSERT INTO `ddon_equip_job_item` ({BuildQueryField(CDataEquipJobItemFields)}) VALUES ({BuildQueryInsert(CDataEquipJobItemFields)});";
- private readonly string SqlReplaceEquipJobItem = $"INSERT OR REPLACE INTO `ddon_equip_job_item` ({BuildQueryField(CDataEquipJobItemFields)}) VALUES ({BuildQueryInsert(CDataEquipJobItemFields)});";
- private static readonly string SqlUpdateEquipJobItem = $"UPDATE `ddon_equip_job_item` SET {BuildQueryUpdate(CDataEquipJobItemFields)} WHERE `character_common_id` = @character_common_id AND `job` = @job AND `equip_slot`=@equip_slot;";
- private static readonly string SqlSelectEquipJobItems = $"SELECT {BuildQueryField(CDataEquipJobItemFields)} FROM `ddon_equip_job_item` WHERE `character_common_id` = @character_common_id AND `job` = @job;";
- private static readonly string SqlSelectEquipJobItemsByCharacter = $"SELECT {BuildQueryField(CDataEquipJobItemFields)} FROM `ddon_equip_job_item` WHERE `character_common_id` = @character_common_id;";
- private const string SqlDeleteEquipJobItem = "DELETE FROM `ddon_equip_job_item` WHERE `character_common_id`=@character_common_id AND `job`=@job AND `equip_slot`=@equip_slot;";
+ private readonly string SqlInsertEquipJobItem = $"INSERT INTO \"ddon_equip_job_item\" ({BuildQueryField(CDataEquipJobItemFields)}) VALUES ({BuildQueryInsert(CDataEquipJobItemFields)});";
+ private readonly string SqlInsertIfNotExistsEquipJobItem = $"INSERT INTO \"ddon_equip_job_item\" ({BuildQueryField(CDataEquipJobItemFields)}) SELECT {BuildQueryInsert(CDataEquipJobItemFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_equip_job_item\" WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job);";
+ private static readonly string SqlUpdateEquipJobItem = $"UPDATE \"ddon_equip_job_item\" SET {BuildQueryUpdate(CDataEquipJobItemFields)} WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job AND \"equip_slot\"=@equip_slot;";
+ private static readonly string SqlSelectEquipJobItems = $"SELECT {BuildQueryField(CDataEquipJobItemFields)} FROM \"ddon_equip_job_item\" WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job;";
+ private static readonly string SqlSelectEquipJobItemsByCharacter = $"SELECT {BuildQueryField(CDataEquipJobItemFields)} FROM \"ddon_equip_job_item\" WHERE \"character_common_id\" = @character_common_id;";
+ private const string SqlDeleteEquipJobItem = "DELETE FROM \"ddon_equip_job_item\" WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"equip_slot\"=@equip_slot;";
+
+ public bool InsertIfNotExistsEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertIfNotExistsEquipJobItem(connection, itemUId, commonId, job, slotNo);
+ }
- public bool InsertEquipJobItem(TCon conn, string itemUId, uint commonId, JobId job, ushort slotNo)
+ public bool InsertIfNotExistsEquipJobItem(TCon conn, string itemUId, uint commonId, JobId job, ushort slotNo)
{
- return ExecuteNonQuery(conn, SqlInsertEquipJobItem, command =>
+ return ExecuteNonQuery(conn, SqlInsertIfNotExistsEquipJobItem, command =>
{
AddParameter(command, "item_uid", itemUId);
AddParameter(command, "character_common_id", commonId);
AddParameter(command, "job", (byte) job);
AddParameter(command, "equip_slot", slotNo);
}) == 1;
- }
-
+ }
+
public bool InsertEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo)
{
- return InsertEquipJobItem(null, itemUId, commonId, job, slotNo);
+ using TCon connection = OpenNewConnection();
+ return InsertEquipJobItem(connection, itemUId, commonId, job, slotNo);
}
- public bool ReplaceEquipJobItem(TCon conn, string itemUId, uint commonId, JobId job, ushort slotNo)
+ public bool InsertEquipJobItem(TCon conn, string itemUId, uint commonId, JobId job, ushort slotNo)
{
- return ExecuteNonQuery(conn, SqlReplaceEquipJobItem, command =>
+ return ExecuteNonQuery(conn, SqlInsertEquipJobItem, command =>
{
AddParameter(command, "item_uid", itemUId);
AddParameter(command, "character_common_id", commonId);
@@ -48,7 +56,19 @@ public bool ReplaceEquipJobItem(TCon conn, string itemUId, uint commonId, JobId
public bool ReplaceEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo)
{
- return ReplaceEquipJobItem(null, itemUId, commonId, job, slotNo);
+ using TCon connection = OpenNewConnection();
+ return ReplaceEquipJobItem(connection, itemUId, commonId, job, slotNo);
+ }
+
+ public bool ReplaceEquipJobItem(TCon conn, string itemUId, uint commonId, JobId job, ushort slotNo)
+ {
+ Logger.Debug("Inserting equp job item.");
+ if (!InsertIfNotExistsEquipJobItem(conn, itemUId, commonId, job, slotNo))
+ {
+ Logger.Debug("Equip job item already exists, replacing.");
+ return UpdateEquipJobItem(conn, itemUId, commonId, job, slotNo);
+ }
+ return true;
}
public bool DeleteEquipJobItem(uint commonId, JobId job, ushort slotNo)
@@ -60,5 +80,22 @@ public bool DeleteEquipJobItem(uint commonId, JobId job, ushort slotNo)
AddParameter(command, "equip_slot", slotNo);
}) == 1;
}
+
+ public bool UpdateEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo)
+ {
+ using TCon connection = OpenNewConnection();
+ return UpdateEquipJobItem(connection, itemUId, commonId, job, slotNo);
+ }
+
+ public bool UpdateEquipJobItem(TCon connection, string itemUId, uint commonId, JobId job, ushort slotNo)
+ {
+ return ExecuteNonQuery(connection, SqlUpdateEquipJobItem, command =>
+ {
+ AddParameter(command, "item_uid", itemUId);
+ AddParameter(command, "character_common_id", commonId);
+ AddParameter(command, "job", (byte) job);
+ AddParameter(command, "equip_slot", slotNo);
+ }) == 1;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedAbility.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedAbility.cs
index 74849e564..cd1deeec4 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedAbility.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedAbility.cs
@@ -4,25 +4,46 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private static readonly string[] EquippedAbilityFields = new string[]
+ protected static readonly string[] EquippedAbilityFields = new string[]
{
"character_common_id", "equipped_to_job", "job", "slot_no", "ability_id"
};
- private readonly string SqlInsertEquippedAbility = $"INSERT INTO `ddon_equipped_ability` ({BuildQueryField(EquippedAbilityFields)}) VALUES ({BuildQueryInsert(EquippedAbilityFields)});";
- private readonly string SqlReplaceEquippedAbility = $"INSERT OR REPLACE INTO `ddon_equipped_ability` ({BuildQueryField(EquippedAbilityFields)}) VALUES ({BuildQueryInsert(EquippedAbilityFields)});";
- private static readonly string SqlUpdateEquippedAbility = $"UPDATE `ddon_equipped_ability` SET {BuildQueryUpdate(EquippedAbilityFields)} WHERE `character_common_id`=@old_character_common_id AND `equipped_to_job`=@old_equipped_to_job AND `slot_no`=@old_slot_no;";
- private static readonly string SqlSelectEquippedAbilities = $"SELECT {BuildQueryField(EquippedAbilityFields)} FROM `ddon_equipped_ability` WHERE `character_common_id`=@character_common_id ORDER BY equipped_to_job, slot_no;";
- private const string SqlDeleteEquippedAbility = "DELETE FROM `ddon_equipped_ability` WHERE `character_common_id`=@character_common_id AND `equipped_to_job`=@equipped_to_job AND `slot_no`=@slot_no;";
- private const string SqlDeleteEquippedAbilities = "DELETE FROM `ddon_equipped_ability` WHERE `character_common_id`=@character_common_id AND `equipped_to_job`=@equipped_to_job;";
+ private readonly string SqlInsertEquippedAbility = $"INSERT INTO \"ddon_equipped_ability\" ({BuildQueryField(EquippedAbilityFields)}) VALUES ({BuildQueryInsert(EquippedAbilityFields)});";
+ private readonly string SqlInsertIfNotExistsEquippedAbility = $"INSERT INTO \"ddon_equipped_ability\" ({BuildQueryField(EquippedAbilityFields)}) SELECT {BuildQueryInsert(EquippedAbilityFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_equipped_ability\" WHERE \"character_common_id\"=@character_common_id AND \"equipped_to_job\"=@equipped_to_job AND \"slot_no\"=@slot_no);";
+ private static readonly string SqlUpdateEquippedAbility = $"UPDATE \"ddon_equipped_ability\" SET {BuildQueryUpdate(EquippedAbilityFields)} WHERE \"character_common_id\"=@old_character_common_id AND \"equipped_to_job\"=@old_equipped_to_job AND \"slot_no\"=@old_slot_no;";
+ private static readonly string SqlSelectEquippedAbilities = $"SELECT {BuildQueryField(EquippedAbilityFields)} FROM \"ddon_equipped_ability\" WHERE \"character_common_id\"=@character_common_id ORDER BY equipped_to_job, slot_no;";
+ private const string SqlDeleteEquippedAbility = "DELETE FROM \"ddon_equipped_ability\" WHERE \"character_common_id\"=@character_common_id AND \"equipped_to_job\"=@equipped_to_job AND \"slot_no\"=@slot_no;";
+ private const string SqlDeleteEquippedAbilities = "DELETE FROM \"ddon_equipped_ability\" WHERE \"character_common_id\"=@character_common_id AND \"equipped_to_job\"=@equipped_to_job;";
+ public bool InsertIfNotExistsEquippedAbility(uint commonId, JobId equippedToJob, byte slotNo, Ability ability)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertIfNotExistsEquippedAbility(connection, commonId, equippedToJob, slotNo, ability);
+ }
+
+ public bool InsertIfNotExistsEquippedAbility(TCon connection, uint commonId, JobId equippedToJob, byte slotNo, Ability ability)
+ {
+ return ExecuteNonQuery(connection, SqlInsertIfNotExistsEquippedAbility, command =>
+ {
+ AddParameter(command, commonId, equippedToJob, slotNo, ability);
+ }) == 1;
+ }
+
public bool InsertEquippedAbility(uint commonId, JobId equippedToJob, byte slotNo, Ability ability)
{
- return ExecuteNonQuery(SqlInsertEquippedAbility, command =>
+ using TCon connection = OpenNewConnection();
+ return InsertEquippedAbility(connection, commonId, equippedToJob, slotNo, ability);
+ }
+
+ public bool InsertEquippedAbility(TCon connection, uint commonId, JobId equippedToJob, byte slotNo, Ability ability)
+ {
+ return ExecuteNonQuery(connection, SqlInsertEquippedAbility, command =>
{
AddParameter(command, commonId, equippedToJob, slotNo, ability);
}) == 1;
@@ -30,42 +51,48 @@ public bool InsertEquippedAbility(uint commonId, JobId equippedToJob, byte slotN
public bool ReplaceEquippedAbility(uint commonId, JobId equippedToJob, byte slotNo, Ability ability)
{
- ExecuteNonQuery(SqlReplaceEquippedAbility, command =>
+ using TCon connection = OpenNewConnection();
+ return ReplaceEquippedAbility(connection, commonId, equippedToJob, slotNo, ability);
+ }
+
+ public bool ReplaceEquippedAbility(TCon connection, uint commonId, JobId equippedToJob, byte slotNo, Ability ability)
+ {
+ Logger.Debug("Inserting equipped ability.");
+ if (!InsertIfNotExistsEquippedAbility(connection, commonId, equippedToJob, slotNo, ability))
{
- AddParameter(command, commonId, equippedToJob, slotNo, ability);
- });
+ Logger.Debug("Equipped ability already exists, replacing.");
+ return UpdateEquippedAbility(connection, commonId, equippedToJob, slotNo, equippedToJob, slotNo, ability);
+ }
return true;
}
-
+
public bool ReplaceEquippedAbilities(uint commonId, JobId equippedToJob, List abilities)
{
return ExecuteInTransaction(connection =>
{
// Remove previously equipped abilities
- ExecuteNonQuery(connection, SqlDeleteEquippedAbilities, command =>
- {
- AddParameter(command, "@character_common_id", commonId);
- AddParameter(command, "@equipped_to_job", (byte) equippedToJob);
- });
-
+ DeleteEquippedAbilities(connection, commonId, equippedToJob);
// Insert new ones
for(byte i = 0; i < abilities.Count; i++)
{
Ability ability = abilities[i];
byte slotNo = (byte)(i+1);
- ExecuteNonQuery(connection, SqlInsertEquippedAbility, command =>
- {
- AddParameter(command, commonId, equippedToJob, slotNo, ability);
- });
+ InsertEquippedAbility(connection, commonId, equippedToJob, slotNo, ability);
}
});
}
- public bool UpdateEquippedAbility(uint commonId, JobId oldEquippedToJob, byte oldSlotNo, JobId equippedToJob, byte slotNo, Ability updatedability)
+ public bool UpdateEquippedAbility(uint commonId, JobId oldEquippedToJob, byte oldSlotNo, JobId equippedToJob, byte slotNo, Ability updatedAbility)
+ {
+ using TCon connection = OpenNewConnection();
+ return UpdateEquippedAbility(connection, commonId, oldEquippedToJob, oldSlotNo, equippedToJob, slotNo, updatedAbility);
+ }
+
+ public bool UpdateEquippedAbility(TCon connection, uint commonId, JobId oldEquippedToJob, byte oldSlotNo, JobId equippedToJob, byte slotNo, Ability updatedAbility)
{
- return ExecuteNonQuery(SqlDeleteEquippedAbility, command =>
+ return ExecuteNonQuery(connection, SqlUpdateEquippedAbility, command =>
{
- AddParameter(command, commonId, equippedToJob, slotNo, updatedability);
+ AddParameter(command, commonId, equippedToJob, slotNo, updatedAbility);
AddParameter(command, "@old_character_common_id", commonId);
AddParameter(command, "@old_equipped_to_job", (byte) oldEquippedToJob);
AddParameter(command, "@old_slot_no", oldSlotNo);
@@ -74,7 +101,13 @@ public bool UpdateEquippedAbility(uint commonId, JobId oldEquippedToJob, byte ol
public bool DeleteEquippedAbility(uint commonId, JobId equippedToJob, byte slotNo)
{
- return ExecuteNonQuery(SqlDeleteEquippedAbility, command =>
+ using TCon connection = OpenNewConnection();
+ return DeleteEquippedAbility(connection, commonId, equippedToJob, slotNo);
+ }
+
+ public bool DeleteEquippedAbility(TCon connection, uint commonId, JobId equippedToJob, byte slotNo)
+ {
+ return ExecuteNonQuery(connection, SqlDeleteEquippedAbility, command =>
{
AddParameter(command, "@character_common_id", commonId);
AddParameter(command, "@equipped_to_job", (byte) equippedToJob);
@@ -84,7 +117,13 @@ public bool DeleteEquippedAbility(uint commonId, JobId equippedToJob, byte slotN
public bool DeleteEquippedAbilities(uint commonId, JobId equippedToJob)
{
- return ExecuteNonQuery(SqlDeleteEquippedAbilities, command =>
+ using TCon connection = OpenNewConnection();
+ return DeleteEquippedAbilities(connection, commonId, equippedToJob);
+ }
+
+ public bool DeleteEquippedAbilities(TCon connection, uint commonId, JobId equippedToJob)
+ {
+ return ExecuteNonQuery(connection, SqlDeleteEquippedAbilities, command =>
{
AddParameter(command, "@character_common_id", commonId);
AddParameter(command, "@equipped_to_job", (byte) equippedToJob);
@@ -101,4 +140,4 @@ private void AddParameter(TCom command, uint commonId, JobId equippedToJob, byte
AddParameter(command, "ability_lv", ability.AbilityLv);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedCustomSkill.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedCustomSkill.cs
index 1fdbe3e00..026534ba8 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedCustomSkill.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedCustomSkill.cs
@@ -3,24 +3,45 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private static readonly string[] EquippedCustomSkillFields = new string[]
+ protected static readonly string[] EquippedCustomSkillFields = new string[]
{
"character_common_id", "job", "slot_no", "skill_id"
};
- private readonly string SqlInsertEquippedCustomSkill = $"INSERT INTO `ddon_equipped_custom_skill` ({BuildQueryField(EquippedCustomSkillFields)}) VALUES ({BuildQueryInsert(EquippedCustomSkillFields)});";
- private readonly string SqlReplaceEquippedCustomSkill = $"INSERT OR REPLACE INTO `ddon_equipped_custom_skill` ({BuildQueryField(EquippedCustomSkillFields)}) VALUES ({BuildQueryInsert(EquippedCustomSkillFields)});";
- private static readonly string SqlUpdateEquippedCustomSkill = $"UPDATE `ddon_equipped_custom_skill` SET {BuildQueryUpdate(EquippedCustomSkillFields)} WHERE `character_common_id`=@old_character_common_id AND `job`=@old_job AND `slot_no`=@old_slot_no;";
- private static readonly string SqlSelectEquippedCustomSkills = $"SELECT {BuildQueryField(EquippedCustomSkillFields)} FROM `ddon_equipped_custom_skill` WHERE `character_common_id`=@character_common_id;";
- private const string SqlDeleteEquippedCustomSkill = "DELETE FROM `ddon_equipped_custom_skill` WHERE `character_common_id`=@character_common_id AND `job`=@job AND `slot_no`=@slot_no;";
+ private readonly string SqlInsertEquippedCustomSkill = $"INSERT INTO \"ddon_equipped_custom_skill\" ({BuildQueryField(EquippedCustomSkillFields)}) VALUES ({BuildQueryInsert(EquippedCustomSkillFields)});";
+ private readonly string SqlInsertIfNotExistsEquippedCustomSkill = $"INSERT INTO \"ddon_equipped_custom_skill\" ({BuildQueryField(EquippedCustomSkillFields)}) SELECT {BuildQueryInsert(EquippedCustomSkillFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_equipped_custom_skill\" WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"slot_no\"=@slot_no);";
+ private static readonly string SqlUpdateEquippedCustomSkill = $"UPDATE \"ddon_equipped_custom_skill\" SET {BuildQueryUpdate(EquippedCustomSkillFields)} WHERE \"character_common_id\"=@old_character_common_id AND \"job\"=@old_job AND \"slot_no\"=@old_slot_no;";
+ private static readonly string SqlSelectEquippedCustomSkills = $"SELECT {BuildQueryField(EquippedCustomSkillFields)} FROM \"ddon_equipped_custom_skill\" WHERE \"character_common_id\"=@character_common_id;";
+ private const string SqlDeleteEquippedCustomSkill = "DELETE FROM \"ddon_equipped_custom_skill\" WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"slot_no\"=@slot_no;";
+ public bool InsertIfNotExistsEquippedCustomSkill(uint commonId, byte slotNo, CustomSkill skill)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertIfNotExistsEquippedCustomSkill(connection, commonId, slotNo, skill);
+ }
+
+ public bool InsertIfNotExistsEquippedCustomSkill(TCon connection, uint commonId, byte slotNo, CustomSkill skill)
+ {
+ return ExecuteNonQuery(connection, SqlInsertIfNotExistsEquippedCustomSkill, command =>
+ {
+ AddParameter(command, commonId, slotNo, skill);
+ }) == 1;
+ }
+
public bool InsertEquippedCustomSkill(uint commonId, byte slotNo, CustomSkill skill)
{
- return ExecuteNonQuery(SqlInsertEquippedCustomSkill, command =>
+ using TCon connection = OpenNewConnection();
+ return InsertEquippedCustomSkill(connection, commonId, slotNo, skill);
+ }
+
+ public bool InsertEquippedCustomSkill(TCon connection, uint commonId, byte slotNo, CustomSkill skill)
+ {
+ return ExecuteNonQuery(connection, SqlInsertEquippedCustomSkill, command =>
{
AddParameter(command, commonId, slotNo, skill);
}) == 1;
@@ -28,18 +49,32 @@ public bool InsertEquippedCustomSkill(uint commonId, byte slotNo, CustomSkill sk
public bool ReplaceEquippedCustomSkill(uint commonId, byte slotNo, CustomSkill skill)
{
- ExecuteNonQuery(SqlReplaceEquippedCustomSkill, command =>
+ using TCon connection = OpenNewConnection();
+ return ReplaceEquippedCustomSkill(connection, commonId, slotNo, skill);
+ }
+
+ public bool ReplaceEquippedCustomSkill(TCon connection, uint commonId, byte slotNo, CustomSkill skill)
+ {
+ Logger.Debug("Inserting equipped custom skill.");
+ if (!InsertIfNotExistsEquippedCustomSkill(connection, commonId, slotNo, skill))
{
- AddParameter(command, commonId, slotNo, skill);
- });
+ Logger.Debug("Equipped custom skill already exists, replacing.");
+ return UpdateEquippedCustomSkill(connection, commonId, skill.Job, slotNo, slotNo, skill);
+ }
return true;
}
public bool UpdateEquippedCustomSkill(uint commonId, JobId oldJob, byte oldSlotNo, byte slotNo, CustomSkill updatedSkill)
{
- return ExecuteNonQuery(SqlDeleteEquippedCustomSkill, command =>
+ using TCon connection = OpenNewConnection();
+ return UpdateEquippedCustomSkill(connection, commonId, oldJob, oldSlotNo, slotNo, updatedSkill);
+ }
+
+ public bool UpdateEquippedCustomSkill(TCon connection, uint commonId, JobId oldJob, byte oldSlotNo, byte slotNo, CustomSkill updatedSkill)
+ {
+ return ExecuteNonQuery(connection, SqlUpdateEquippedCustomSkill, command =>
{
- AddParameter(command, commonId, updatedSkill);
+ AddParameter(command, commonId, slotNo, updatedSkill);
AddParameter(command, "@old_character_common_id", commonId);
AddParameter(command, "@old_job", (byte) oldJob);
AddParameter(command, "@old_slot_no", oldSlotNo);
@@ -64,4 +99,4 @@ private void AddParameter(TCom command, uint commonId, byte slotNo, CustomSkill
AddParameter(command, "skill_id", skill.SkillId);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbGameToken.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbGameToken.cs
index 57510f7dc..c417fafb4 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbGameToken.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbGameToken.cs
@@ -3,16 +3,17 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private const string SqlInsertToken = "INSERT INTO `ddon_game_token` (`account_id`, `character_id`, `token`, `created`) VALUES (@account_id, @character_id, @token, @created);";
- private const string SqlUpdateToken = "UPDATE `ddon_game_token` SET `character_id`=@character_id, `token`=@token, `created`=@created WHERE `account_id` = @account_id;";
- private const string SqlSelectTokenByAccountId = "SELECT `token`, `account_id`, `character_id`, `token`, `created` FROM `ddon_game_token` WHERE `account_id` = @account_id;";
- private const string SqlDeleteTokenByAccountId = "DELETE FROM `ddon_game_token` WHERE `account_id`=@account_id;";
- private const string SqlSelectToken = "SELECT `token`, `account_id`, `character_id`, `token`, `created` FROM `ddon_game_token` WHERE `token` = @token;";
- private const string SqlDeleteToken = "DELETE FROM `ddon_game_token` WHERE `token`=@token;";
+ private const string SqlInsertToken = "INSERT INTO \"ddon_game_token\" (\"account_id\", \"character_id\", \"token\", \"created\") VALUES (@account_id, @character_id, @token, @created);";
+ private const string SqlUpdateToken = "UPDATE \"ddon_game_token\" SET \"character_id\"=@character_id, \"token\"=@token, \"created\"=@created WHERE \"account_id\" = @account_id;";
+ private const string SqlSelectTokenByAccountId = "SELECT \"token\", \"account_id\", \"character_id\", \"token\", \"created\" FROM \"ddon_game_token\" WHERE \"account_id\" = @account_id;";
+ private const string SqlDeleteTokenByAccountId = "DELETE FROM \"ddon_game_token\" WHERE \"account_id\"=@account_id;";
+ private const string SqlSelectToken = "SELECT \"token\", \"account_id\", \"character_id\", \"token\", \"created\" FROM \"ddon_game_token\" WHERE \"token\" = @token;";
+ private const string SqlDeleteToken = "DELETE FROM \"ddon_game_token\" WHERE \"token\"=@token;";
public bool SetToken(GameToken token)
{
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbItem.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbItem.cs
index 63ced654c..38cf20c04 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbItem.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbItem.cs
@@ -1,25 +1,24 @@
-using System;
-using System.Collections.Generic;
using System.Data.Common;
-using System.Linq;
-using System.Threading.Tasks;
using Arrowgene.Ddon.Shared.Model;
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private static readonly string[] ItemFields = new string[]
+ protected static readonly string[] ItemFields = new string[]
{
"uid", "item_id", "unk3", "color", "plus_value"
};
// Items don't get updated or deleted once created as the same row is shared among all players.
// Making a distinction wouldn't make sense, as upgrading/changin crests would generate a new item with a different UID
- private readonly string SqlInsertOrIgnoreItem = $"INSERT OR IGNORE INTO `ddon_item` ({BuildQueryField(ItemFields)}) VALUES ({BuildQueryInsert(ItemFields)});";
- private static readonly string SqlSelectItem = $"SELECT {BuildQueryField(ItemFields)} FROM `ddon_item` WHERE `uid`=@uid;";
+ protected virtual string SqlInsertOrIgnoreItem { get; } =
+ $"INSERT INTO \"ddon_item\" ({BuildQueryField(ItemFields)}) SELECT {BuildQueryInsert(ItemFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_item\" WHERE \"uid\"=@uid);";
+
+ private static readonly string SqlSelectItem = $"SELECT {BuildQueryField(ItemFields)} FROM \"ddon_item\" WHERE \"uid\"=@uid;";
public bool InsertItem(TCon conn, Item item)
{
@@ -31,7 +30,8 @@ public bool InsertItem(TCon conn, Item item)
public bool InsertItem(Item item)
{
- return this.InsertItem(null, item);
+ using TCon connection = OpenNewConnection();
+ return InsertItem(connection, item);
}
public Item SelectItem(string uid)
@@ -52,7 +52,7 @@ public Item SelectItem(string uid)
return item;
}
- private Item ReadItem(DbDataReader reader)
+ private Item ReadItem(TReader reader)
{
Item item = new Item();
item.UId = GetString(reader, "uid");
@@ -72,4 +72,4 @@ private void AddParameter(TCom command, Item item)
AddParameter(command, "plus_value", item.PlusValue);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedAbility.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedAbility.cs
index 2a0c06e11..3fda89559 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedAbility.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedAbility.cs
@@ -4,9 +4,10 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
private static readonly string[] LearnedAbilityFields = new string[]
@@ -14,9 +15,9 @@ public abstract partial class DdonSqlDb : SqlDb
"character_common_id", "job", "ability_id", "ability_lv"
};
- private readonly string SqlInsertLearnedAbility = $"INSERT INTO `ddon_learned_ability` ({BuildQueryField(LearnedAbilityFields)}) VALUES ({BuildQueryInsert(LearnedAbilityFields)});";
- private readonly string SqlUpdateLearnedAbility = $"UPDATE `ddon_learned_ability` SET {BuildQueryUpdate(LearnedAbilityFields)} WHERE `character_common_id`=@character_common_id AND `job`=@job AND `ability_id`=@ability_id;";
- private static readonly string SqlSelectLearnedAbilities = $"SELECT {BuildQueryField(LearnedAbilityFields)} FROM `ddon_learned_ability` WHERE `character_common_id`=@character_common_id;";
+ private readonly string SqlInsertLearnedAbility = $"INSERT INTO \"ddon_learned_ability\" ({BuildQueryField(LearnedAbilityFields)}) VALUES ({BuildQueryInsert(LearnedAbilityFields)});";
+ private readonly string SqlUpdateLearnedAbility = $"UPDATE \"ddon_learned_ability\" SET {BuildQueryUpdate(LearnedAbilityFields)} WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"ability_id\"=@ability_id;";
+ private static readonly string SqlSelectLearnedAbilities = $"SELECT {BuildQueryField(LearnedAbilityFields)} FROM \"ddon_learned_ability\" WHERE \"character_common_id\"=@character_common_id;";
public bool InsertLearnedAbility(uint commonId, Ability ability)
{
@@ -36,7 +37,7 @@ public bool UpdateLearnedAbility(uint commonId, Ability ability)
return true;
}
- private Ability ReadLearnedAbility(DbDataReader reader)
+ private Ability ReadLearnedAbility(TReader reader)
{
Ability ability = new Ability();
ability.Job = (JobId) GetByte(reader, "job");
@@ -53,4 +54,4 @@ private void AddParameter(TCom command, uint commonId, Ability ability)
AddParameter(command, "ability_lv", ability.AbilityLv);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedCustomSkill.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedCustomSkill.cs
index 6b86fed91..3d0f68705 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedCustomSkill.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedCustomSkill.cs
@@ -3,18 +3,19 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
private static readonly string[] LearnedCustomSkillFields = new string[]
{
"character_common_id", "job", "skill_id", "skill_lv"
};
- private readonly string SqlInsertLearnedCustomSkill = $"INSERT INTO `ddon_learned_custom_skill` ({BuildQueryField(LearnedCustomSkillFields)}) VALUES ({BuildQueryInsert(LearnedCustomSkillFields)});";
- private readonly string SqlUpdateLearnedCustomSkill = $"UPDATE `ddon_learned_custom_skill` SET {BuildQueryUpdate(LearnedCustomSkillFields)} WHERE `character_common_id`=@character_common_id AND `job`=@job AND `skill_id`=@skill_id;";
- private static readonly string SqlSelectLearnedCustomSkills = $"SELECT {BuildQueryField(LearnedCustomSkillFields)} FROM `ddon_learned_custom_skill` WHERE `character_common_id`=@character_common_id;";
+ private readonly string SqlInsertLearnedCustomSkill = $"INSERT INTO \"ddon_learned_custom_skill\" ({BuildQueryField(LearnedCustomSkillFields)}) VALUES ({BuildQueryInsert(LearnedCustomSkillFields)});";
+ private readonly string SqlUpdateLearnedCustomSkill = $"UPDATE \"ddon_learned_custom_skill\" SET {BuildQueryUpdate(LearnedCustomSkillFields)} WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"skill_id\"=@skill_id;";
+ private static readonly string SqlSelectLearnedCustomSkills = $"SELECT {BuildQueryField(LearnedCustomSkillFields)} FROM \"ddon_learned_custom_skill\" WHERE \"character_common_id\"=@character_common_id;";
public bool InsertLearnedCustomSkill(uint commonId, CustomSkill skill)
{
@@ -34,7 +35,7 @@ public bool UpdateLearnedCustomSkill(uint commonId, CustomSkill updatedSkill)
return true;
}
- private CustomSkill ReadLearnedCustomSkill(DbDataReader reader)
+ private CustomSkill ReadLearnedCustomSkill(TReader reader)
{
CustomSkill skill = new CustomSkill();
skill.Job = (JobId) GetByte(reader, "job");
@@ -51,4 +52,4 @@ private void AddParameter(TCom command, uint commonId, CustomSkill skill)
AddParameter(command, "skill_lv", skill.SkillLv);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbNormalSkillParam.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbNormalSkillParam.cs
index c52c0cee9..29235cb03 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbNormalSkillParam.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbNormalSkillParam.cs
@@ -4,25 +4,96 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private static readonly string[] CDataNormalSkillParamFields = new string[]
+ protected static readonly string[] CDataNormalSkillParamFields = new string[]
{
"character_common_id", "job", "skill_no", "index", "pre_skill_no"
};
- private readonly string SqlInsertNormalSkillParam = $"INSERT INTO `ddon_normal_skill_param` ({BuildQueryField(CDataNormalSkillParamFields)}) VALUES ({BuildQueryInsert(CDataNormalSkillParamFields)});";
- private readonly string SqlReplaceNormalSkillParam = $"INSERT OR REPLACE INTO `ddon_normal_skill_param` ({BuildQueryField(CDataNormalSkillParamFields)}) VALUES ({BuildQueryInsert(CDataNormalSkillParamFields)});";
- private static readonly string SqlUpdateNormalSkillParam = $"UPDATE `ddon_normal_skill_param` SET {BuildQueryUpdate(CDataNormalSkillParamFields)} WHERE `character_common_id` = @character_common_id AND `job` = @job AND `skill_no`=@skill_no;";
- private static readonly string SqlSelectNormalSkillParam = $"SELECT {BuildQueryField(CDataNormalSkillParamFields)} FROM `ddon_normal_skill_param` WHERE `character_common_id` = @character_common_id;";
- private const string SqlDeleteNormalSkillParam = "DELETE FROM `ddon_normal_skill_param` WHERE `character_common_id`=@character_common_id AND `job`=@job AND `skill_no`=@skill_no;";
+ private readonly string SqlInsertNormalSkillParam = $"INSERT INTO \"ddon_normal_skill_param\" ({BuildQueryField(CDataNormalSkillParamFields)}) VALUES ({BuildQueryInsert(CDataNormalSkillParamFields)});";
+ private readonly string SqlInsertIfNotExistsNormalSkillParam = $"INSERT INTO \"ddon_normal_skill_param\" ({BuildQueryField(CDataNormalSkillParamFields)}) SELECT {BuildQueryInsert(CDataNormalSkillParamFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_normal_skill_param\" WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job AND \"skill_no\"=@skill_no);";
+ private static readonly string SqlUpdateNormalSkillParam = $"UPDATE \"ddon_normal_skill_param\" SET {BuildQueryUpdate(CDataNormalSkillParamFields)} WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job AND \"skill_no\"=@skill_no;";
+ private static readonly string SqlSelectNormalSkillParam = $"SELECT {BuildQueryField(CDataNormalSkillParamFields)} FROM \"ddon_normal_skill_param\" WHERE \"character_common_id\" = @character_common_id;";
+ private const string SqlDeleteNormalSkillParam = "DELETE FROM \"ddon_normal_skill_param\" WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"skill_no\"=@skill_no;";
- private CDataNormalSkillParam ReadNormalSkillParam(DbDataReader reader)
+ public bool InsertIfNotExistsNormalSkillParam(uint commonId, CDataNormalSkillParam normalSkillParam)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertIfNotExistsNormalSkillParam(connection, commonId, normalSkillParam);
+ }
+
+ public bool InsertIfNotExistsNormalSkillParam(TCon conn, uint commonId, CDataNormalSkillParam normalSkillParam)
+ {
+ return ExecuteNonQuery(conn, SqlInsertIfNotExistsNormalSkillParam, command =>
+ {
+ AddParameter(command, commonId, normalSkillParam);
+ }) == 1;
+ }
+
+ public bool InsertNormalSkillParam(uint commonId, CDataNormalSkillParam normalSkillParam)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertNormalSkillParam(connection, commonId, normalSkillParam);
+ }
+
+ public bool InsertNormalSkillParam(TCon conn, uint commonId, CDataNormalSkillParam normalSkillParam)
+ {
+ return ExecuteNonQuery(conn, SqlInsertNormalSkillParam, command =>
+ {
+ AddParameter(command, commonId, normalSkillParam);
+ }) == 1;
+ }
+
+ public bool ReplaceNormalSkillParam(uint commonId, CDataNormalSkillParam normalSkillParam)
+ {
+ using TCon connection = OpenNewConnection();
+ return ReplaceNormalSkillParam(connection, commonId, normalSkillParam);
+ }
+
+ public bool ReplaceNormalSkillParam(TCon conn, uint commonId, CDataNormalSkillParam normalSkillParam)
+ {
+ Logger.Debug("Inserting storage item.");
+ if (!InsertIfNotExistsNormalSkillParam(conn, commonId, normalSkillParam))
+ {
+ Logger.Debug("Storage item already exists, replacing.");
+ return UpdateNormalSkillParam(conn, commonId, normalSkillParam.Job, normalSkillParam.SkillNo, normalSkillParam);
+ }
+
+ return true;
+ }
+
+ public bool UpdateNormalSkillParam(uint commonId, JobId job, uint skillNo, CDataNormalSkillParam normalSkillParam)
+ {
+ using TCon connection = OpenNewConnection();
+ return UpdateNormalSkillParam(connection, commonId, job, skillNo, normalSkillParam);
+ }
+
+ public bool UpdateNormalSkillParam(TCon connection, uint commonId, JobId job, uint skillNo, CDataNormalSkillParam normalSkillParam)
+ {
+ return ExecuteNonQuery(connection, SqlUpdateNormalSkillParam, command =>
+ {
+ AddParameter(command, commonId, normalSkillParam);
+ }) == 1;
+ }
+
+ public bool DeleteNormalSkillParam(uint commonId, JobId job, uint skillNo)
+ {
+ return ExecuteNonQuery(SqlDeleteNormalSkillParam, command =>
+ {
+ AddParameter(command, "@character_common_id", commonId);
+ AddParameter(command, "@job", (byte)job);
+ AddParameter(command, "@skill_no", skillNo);
+ }) == 1;
+ }
+
+ private CDataNormalSkillParam ReadNormalSkillParam(TReader reader)
{
CDataNormalSkillParam normalSkillParam = new CDataNormalSkillParam();
- normalSkillParam.Job = (JobId) GetByte(reader, "job");
+ normalSkillParam.Job = (JobId)GetByte(reader, "job");
normalSkillParam.SkillNo = GetUInt32(reader, "skill_no");
normalSkillParam.Index = GetUInt32(reader, "index");
normalSkillParam.PreSkillNo = GetUInt32(reader, "pre_skill_no");
@@ -32,10 +103,10 @@ private CDataNormalSkillParam ReadNormalSkillParam(DbDataReader reader)
private void AddParameter(TCom command, uint commonId, CDataNormalSkillParam normalSkillParam)
{
AddParameter(command, "character_common_id", commonId);
- AddParameter(command, "job", (byte) normalSkillParam.Job);
+ AddParameter(command, "job", (byte)normalSkillParam.Job);
AddParameter(command, "skill_no", normalSkillParam.SkillNo);
AddParameter(command, "index", normalSkillParam.Index);
AddParameter(command, "pre_skill_no", normalSkillParam.PreSkillNo);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbPawn.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbPawn.cs
index 9f60f434b..c67a02413 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbPawn.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbPawn.cs
@@ -5,54 +5,55 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
private static readonly string[] PawnFields = new string[]
{
"character_common_id", "character_id", "name", "hm_type", "pawn_type"
};
- private static readonly string[] CDataPawnReactionFields = new string[]
+ protected static readonly string[] CDataPawnReactionFields = new string[]
{
"pawn_id", "reaction_type", "motion_no"
};
- private static readonly string[] CDataSpSkillFields = new string[]
+ protected static readonly string[] CDataSpSkillFields = new string[]
{
"pawn_id", "sp_skill_id", "sp_skill_lv"
};
- private readonly string SqlInsertPawn = $"INSERT INTO `ddon_pawn` ({BuildQueryField(PawnFields)}) VALUES ({BuildQueryInsert(PawnFields)});";
- private static readonly string SqlUpdatePawn = $"UPDATE `ddon_pawn` SET {BuildQueryUpdate(PawnFields)} WHERE `pawn_id` = @pawn_id;";
- private static readonly string SqlSelectPawn = $"SELECT `ddon_pawn`.`pawn_id`, {BuildQueryField(PawnFields)} FROM `ddon_pawn` WHERE `pawn_id` = @pawn_id;";
- private static readonly string SqlSelectPawnsByCharacterId = $"SELECT `ddon_pawn`.`pawn_id`, {BuildQueryField(PawnFields)} FROM `ddon_pawn` WHERE `character_id` = @character_id;";
- private readonly string SqlSelectAllPawnData = $"SELECT `ddon_pawn`.`pawn_id`, {BuildQueryField("ddon_pawn", PawnFields)}, `ddon_character_common`.`character_common_id`, {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}"
- + "FROM `ddon_pawn` "
- + "LEFT JOIN `ddon_character_common` ON `ddon_character_common`.`character_common_id` = `ddon_pawn`.`character_common_id` "
- + "LEFT JOIN `ddon_edit_info` ON `ddon_edit_info`.`character_common_id` = `ddon_pawn`.`character_common_id` "
- + "LEFT JOIN `ddon_status_info` ON `ddon_status_info`.`character_common_id` = `ddon_pawn`.`character_common_id` "
- + "WHERE `ddon_pawn`.`pawn_id` = @pawn_id";
- private readonly string SqlSelectAllPawnsDataByCharacterId = $"SELECT `ddon_pawn`.`pawn_id`, {BuildQueryField("ddon_pawn", PawnFields)}, `ddon_character_common`.`character_common_id`, {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}"
- + "FROM `ddon_pawn` "
- + "LEFT JOIN `ddon_character_common` ON `ddon_character_common`.`character_common_id` = `ddon_pawn`.`character_common_id` "
- + "LEFT JOIN `ddon_edit_info` ON `ddon_edit_info`.`character_common_id` = `ddon_pawn`.`character_common_id` "
- + "LEFT JOIN `ddon_status_info` ON `ddon_status_info`.`character_common_id` = `ddon_pawn`.`character_common_id` "
- + "WHERE `character_id` = @character_id";
- private const string SqlDeletePawn = "DELETE FROM `ddon_character_common` WHERE EXISTS (SELECT 1 FROM `ddon_pawn` WHERE `pawn_id`=@pawn_id);";
-
- private readonly string SqlInsertPawnReaction = $"INSERT INTO `ddon_pawn_reaction` ({BuildQueryField(CDataPawnReactionFields)}) VALUES ({BuildQueryInsert(CDataPawnReactionFields)});";
- private readonly string SqlReplacePawnReaction = $"REPLACE INTO `ddon_pawn_reaction` ({BuildQueryField(CDataPawnReactionFields)}) VALUES ({BuildQueryInsert(CDataPawnReactionFields)});";
- private static readonly string SqlUpdatePawnReaction = $"UPDATE `ddon_pawn_reaction` SET {BuildQueryUpdate(CDataPawnReactionFields)} WHERE `pawn_id` = @pawn_id AND `reaction_type`=@reaction_type;";
- private static readonly string SqlSelectPawnReactionByPawnId = $"SELECT {BuildQueryField(CDataPawnReactionFields)} FROM `ddon_pawn_reaction` WHERE `pawn_id` = @pawn_id;";
- private const string SqlDeletePawnReaction = "DELETE FROM `ddon_pawn_reaction` WHERE `pawn_id`=@pawn_id AND `reaction_type`=@reaction_type;";
-
- private readonly string SqlInsertSpSkill = $"INSERT INTO `ddon_sp_skill` ({BuildQueryField(CDataSpSkillFields)}) VALUES ({BuildQueryInsert(CDataSpSkillFields)});";
- private readonly string SqlReplaceSpSkill = $"REPLACE INTO `ddon_sp_skill` ({BuildQueryField(CDataSpSkillFields)}) VALUES ({BuildQueryInsert(CDataSpSkillFields)});";
- private static readonly string SqlUpdateSpSkill = $"UPDATE `ddon_sp_skill` SET {BuildQueryUpdate(CDataSpSkillFields)} WHERE `pawn_id` = @pawn_id AND `sp_skill_id`=@sp_skill_id;";
- private static readonly string SqlSelectSpSkillByPawnId = $"SELECT {BuildQueryField(CDataSpSkillFields)} FROM `ddon_sp_skill` WHERE `pawn_id` = @pawn_id;";
- private const string SqlDeleteSpSkill = "DELETE FROM `ddon_sp_skill` WHERE `pawn_id`=@pawn_id AND `sp_skill_id`=@sp_skill_id;";
+ private readonly string SqlInsertPawn = $"INSERT INTO \"ddon_pawn\" ({BuildQueryField(PawnFields)}) VALUES ({BuildQueryInsert(PawnFields)});";
+ private static readonly string SqlUpdatePawn = $"UPDATE \"ddon_pawn\" SET {BuildQueryUpdate(PawnFields)} WHERE \"pawn_id\" = @pawn_id;";
+ private static readonly string SqlSelectPawn = $"SELECT \"ddon_pawn\".\"pawn_id\", {BuildQueryField(PawnFields)} FROM \"ddon_pawn\" WHERE \"pawn_id\" = @pawn_id;";
+ private static readonly string SqlSelectPawnsByCharacterId = $"SELECT \"ddon_pawn\".\"pawn_id\", {BuildQueryField(PawnFields)} FROM \"ddon_pawn\" WHERE \"character_id\" = @character_id;";
+ private readonly string SqlSelectAllPawnData = $"SELECT \"ddon_pawn\".\"pawn_id\", {BuildQueryField("ddon_pawn", PawnFields)}, \"ddon_character_common\".\"character_common_id\", {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}"
+ + "FROM \"ddon_pawn\" "
+ + "LEFT JOIN \"ddon_character_common\" ON \"ddon_character_common\".\"character_common_id\" = \"ddon_pawn\".\"character_common_id\" "
+ + "LEFT JOIN \"ddon_edit_info\" ON \"ddon_edit_info\".\"character_common_id\" = \"ddon_pawn\".\"character_common_id\" "
+ + "LEFT JOIN \"ddon_status_info\" ON \"ddon_status_info\".\"character_common_id\" = \"ddon_pawn\".\"character_common_id\" "
+ + "WHERE \"ddon_pawn\".\"pawn_id\" = @pawn_id";
+ private readonly string SqlSelectAllPawnsDataByCharacterId = $"SELECT \"ddon_pawn\".\"pawn_id\", {BuildQueryField("ddon_pawn", PawnFields)}, \"ddon_character_common\".\"character_common_id\", {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}"
+ + "FROM \"ddon_pawn\" "
+ + "LEFT JOIN \"ddon_character_common\" ON \"ddon_character_common\".\"character_common_id\" = \"ddon_pawn\".\"character_common_id\" "
+ + "LEFT JOIN \"ddon_edit_info\" ON \"ddon_edit_info\".\"character_common_id\" = \"ddon_pawn\".\"character_common_id\" "
+ + "LEFT JOIN \"ddon_status_info\" ON \"ddon_status_info\".\"character_common_id\" = \"ddon_pawn\".\"character_common_id\" "
+ + "WHERE \"character_id\" = @character_id";
+ private const string SqlDeletePawn = "DELETE FROM \"ddon_character_common\" WHERE EXISTS (SELECT 1 FROM \"ddon_pawn\" WHERE \"pawn_id\"=@pawn_id);";
+
+ private readonly string SqlInsertPawnReaction = $"INSERT INTO \"ddon_pawn_reaction\" ({BuildQueryField(CDataPawnReactionFields)}) VALUES ({BuildQueryInsert(CDataPawnReactionFields)});";
+ private readonly string SqlInsertIfNotExistsPawnReaction = $"INSERT INTO \"ddon_pawn_reaction\" ({BuildQueryField(CDataPawnReactionFields)}) SELECT {BuildQueryInsert(CDataPawnReactionFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_pawn_reaction\" WHERE \"pawn_id\"=@pawn_id AND \"reaction_type\"=@reaction_type);";
+ private static readonly string SqlUpdatePawnReaction = $"UPDATE \"ddon_pawn_reaction\" SET {BuildQueryUpdate(CDataPawnReactionFields)} WHERE \"pawn_id\" = @pawn_id AND \"reaction_type\"=@reaction_type;";
+ private static readonly string SqlSelectPawnReactionByPawnId = $"SELECT {BuildQueryField(CDataPawnReactionFields)} FROM \"ddon_pawn_reaction\" WHERE \"pawn_id\" = @pawn_id;";
+ private const string SqlDeletePawnReaction = "DELETE FROM \"ddon_pawn_reaction\" WHERE \"pawn_id\"=@pawn_id AND \"reaction_type\"=@reaction_type;";
+
+ private readonly string SqlInsertSpSkill = $"INSERT INTO \"ddon_sp_skill\" ({BuildQueryField(CDataSpSkillFields)}) VALUES ({BuildQueryInsert(CDataSpSkillFields)});";
+ private readonly string SqlInsertIfNotExistsSpSkill = $"INSERT INTO \"ddon_sp_skill\" ({BuildQueryField(CDataSpSkillFields)}) SELECT {BuildQueryInsert(CDataSpSkillFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_sp_skill\" WHERE \"pawn_id\" = @pawn_id);";
+ private static readonly string SqlUpdateSpSkill = $"UPDATE \"ddon_sp_skill\" SET {BuildQueryUpdate(CDataSpSkillFields)} WHERE \"pawn_id\" = @pawn_id AND \"sp_skill_id\"=@sp_skill_id;";
+ private static readonly string SqlSelectSpSkillByPawnId = $"SELECT {BuildQueryField(CDataSpSkillFields)} FROM \"ddon_sp_skill\" WHERE \"pawn_id\" = @pawn_id;";
+ private const string SqlDeleteSpSkill = "DELETE FROM \"ddon_sp_skill\" WHERE \"pawn_id\"=@pawn_id AND \"sp_skill_id\"=@sp_skill_id;";
public bool CreatePawn(Pawn pawn)
{
@@ -94,7 +95,8 @@ public Pawn SelectPawn(uint pawnId)
public List SelectPawnsByCharacterId(uint characterId)
{
List pawns = new List();
- ExecuteInTransaction(conn => {
+ ExecuteInTransaction(conn =>
+ {
ExecuteReader(conn, SqlSelectAllPawnsDataByCharacterId,
command => { AddParameter(command, "@character_id", characterId); }, reader =>
{
@@ -102,10 +104,12 @@ public List SelectPawnsByCharacterId(uint characterId)
{
Pawn pawn = ReadAllPawnData(reader);
pawns.Add(pawn);
-
- QueryPawnData(conn, pawn);
}
});
+ foreach (var pawn in pawns)
+ {
+ QueryPawnData(conn, pawn);
+ }
});
return pawns;
}
@@ -113,13 +117,17 @@ public List SelectPawnsByCharacterId(uint characterId)
public bool DeletePawn(uint pawnId)
{
int rowsAffected = ExecuteNonQuery(SqlDeletePawn,
- command => { AddParameter(command, "@pawn_id", pawnId); });
+ command =>
+ {
+ AddParameter(command, "@pawn_id", pawnId);
+ });
return rowsAffected > NoRowsAffected;
}
public bool UpdatePawnBaseInfo(Pawn pawn)
{
- return UpdatePawnBaseInfo(null, pawn);
+ using TCon connection = OpenNewConnection();
+ return UpdatePawnBaseInfo(connection, pawn);
}
public bool UpdatePawnBaseInfo(TCon conn, Pawn pawn)
@@ -163,18 +171,12 @@ private void StorePawnData(TCon conn, Pawn pawn)
foreach (CDataPawnReaction pawnReaction in pawn.PawnReactionList)
{
- ExecuteNonQuery(conn, SqlReplacePawnReaction, command =>
- {
- AddParameter(command, pawn.PawnId, pawnReaction);
- });
+ ReplacePawnReaction(conn, pawn.PawnId, pawnReaction);
}
foreach (CDataSpSkill spSkill in pawn.SpSkillList)
{
- ExecuteNonQuery(conn, SqlReplaceSpSkill, command =>
- {
- AddParameter(command, pawn.PawnId, spSkill);
- });
+ ReplaceSpSkill(conn, pawn.PawnId, spSkill);
}
}
@@ -200,8 +202,144 @@ private void CreateItems(TCon conn, Pawn pawn)
}
}
}
+
+ public bool InsertIfNotExistsSpSkill(uint pawnId, CDataSpSkill spSkill)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertIfNotExistsSpSkill(connection, pawnId, spSkill);
+ }
+
+ public bool InsertIfNotExistsSpSkill(TCon conn, uint pawnId, CDataSpSkill spSkill)
+ {
+ return ExecuteNonQuery(conn, SqlInsertIfNotExistsSpSkill, command =>
+ {
+ AddParameter(command, pawnId, spSkill);
+ }) == 1;
+ }
+
+ public bool InsertSpSkill(uint pawnId, CDataSpSkill spSkill)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertSpSkill(connection, pawnId, spSkill);
+ }
+
+ public bool InsertSpSkill(TCon conn, uint pawnId, CDataSpSkill spSkill)
+ {
+ return ExecuteNonQuery(conn, SqlInsertSpSkill, command =>
+ {
+ AddParameter(command, pawnId, spSkill);
+ }) == 1;
+ }
+
+ public bool ReplaceSpSkill(uint pawnId, CDataSpSkill spSkill)
+ {
+ using TCon connection = OpenNewConnection();
+ return ReplaceSpSkill(connection, pawnId, spSkill);
+ }
+
+ public bool ReplaceSpSkill(TCon conn, uint pawnId, CDataSpSkill spSkill)
+ {
+ Logger.Debug("Inserting SP Skill.");
+ if (!InsertIfNotExistsSpSkill(conn, pawnId, spSkill))
+ {
+ Logger.Debug("SP skill already exists, replacing.");
+ return UpdateSpSkill(conn, pawnId, spSkill);
+ }
+ return true;
+ }
+
+ public bool UpdateSpSkill(uint pawnId, CDataSpSkill spSkill)
+ {
+ using TCon connection = OpenNewConnection();
+ return UpdateSpSkill(connection, pawnId, spSkill);
+ }
+
+ public bool UpdateSpSkill(TCon connection, uint pawnId, CDataSpSkill spSkill)
+ {
+ return ExecuteNonQuery(connection, SqlUpdateSpSkill, command =>
+ {
+ AddParameter(command, pawnId, spSkill);
+ }) == 1;
+ }
+
+ public bool DeleteSpSkill(uint pawnId, byte spSkillId)
+ {
+ return ExecuteNonQuery(SqlDeleteSpSkill, command =>
+ {
+ AddParameter(command, "@pawn_id", pawnId);
+ AddParameter(command, "@sp_skill_id", spSkillId);
+ }) == 1;
+ }
+
+ public bool InsertIfNotExistsPawnReaction(uint pawnId, CDataPawnReaction pawnReaction)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertIfNotExistsPawnReaction(connection, pawnId, pawnReaction);
+ }
+
+ public bool InsertIfNotExistsPawnReaction(TCon conn, uint pawnId, CDataPawnReaction pawnReaction)
+ {
+ return ExecuteNonQuery(conn, SqlInsertIfNotExistsPawnReaction, command =>
+ {
+ AddParameter(command, pawnId, pawnReaction);
+ }) == 1;
+ }
+
+ public bool InsertPawnReaction(uint pawnId, CDataPawnReaction pawnReaction)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertPawnReaction(connection, pawnId, pawnReaction);
+ }
+
+ public bool InsertPawnReaction(TCon conn, uint pawnId, CDataPawnReaction pawnReaction)
+ {
+ return ExecuteNonQuery(conn, SqlInsertPawnReaction, command =>
+ {
+ AddParameter(command, pawnId, pawnReaction);
+ }) == 1;
+ }
+
+ public bool ReplacePawnReaction(uint pawnId, CDataPawnReaction pawnReaction)
+ {
+ using TCon connection = OpenNewConnection();
+ return ReplacePawnReaction(connection, pawnId, pawnReaction);
+ }
+
+ public bool ReplacePawnReaction(TCon conn, uint pawnId, CDataPawnReaction pawnReaction)
+ {
+ Logger.Debug("Inserting pawn reaction.");
+ if (!InsertIfNotExistsPawnReaction(conn, pawnId, pawnReaction))
+ {
+ Logger.Debug("Pawn reaction already exists, replacing.");
+ return UpdatePawnReaction(conn, pawnId, pawnReaction);
+ }
+ return true;
+ }
+
+ public bool UpdatePawnReaction(uint pawnId, CDataPawnReaction pawnReaction)
+ {
+ using TCon connection = OpenNewConnection();
+ return UpdatePawnReaction(connection, pawnId, pawnReaction);
+ }
+
+ public bool UpdatePawnReaction(TCon connection, uint pawnId, CDataPawnReaction pawnReaction)
+ {
+ return ExecuteNonQuery(connection, SqlUpdatePawnReaction, command =>
+ {
+ AddParameter(command, pawnId, pawnReaction);
+ }) == 1;
+ }
+
+ public bool DeleteNormalSkillParam(uint pawnId, byte reactionType)
+ {
+ return ExecuteNonQuery(SqlDeletePawnReaction, command =>
+ {
+ AddParameter(command, "@pawn_id", pawnId);
+ AddParameter(command, "@reaction_type", reactionType);
+ }) == 1;
+ }
- private Pawn ReadAllPawnData(DbDataReader reader)
+ private Pawn ReadAllPawnData(TReader reader)
{
Pawn pawn = new Pawn();
@@ -227,7 +365,7 @@ private void AddParameter(TCom command, Pawn pawn)
AddParameter(command, "@pawn_type", pawn.PawnType);
}
- private CDataPawnReaction ReadPawnReaction(DbDataReader reader)
+ private CDataPawnReaction ReadPawnReaction(TReader reader)
{
CDataPawnReaction pawnReaction = new CDataPawnReaction();
pawnReaction.ReactionType = GetByte(reader, "reaction_type");
@@ -242,7 +380,7 @@ private void AddParameter(TCom command, uint pawnId, CDataPawnReaction pawnReact
AddParameter(command, "motion_no", pawnReaction.MotionNo);
}
- private CDataSpSkill ReadSpSkill(DbDataReader reader)
+ private CDataSpSkill ReadSpSkill(TReader reader)
{
CDataSpSkill spSkill = new CDataSpSkill();
spSkill.SpSkillId = GetByte(reader, "sp_skill_id");
@@ -257,4 +395,4 @@ private void AddParameter(TCom command, uint pawnId, CDataSpSkill spSkill)
AddParameter(command, "sp_skill_lv", spSkill.SpSkillLv);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbSetting.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbSetting.cs
index e6e216ece..f4a2d8acf 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbSetting.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbSetting.cs
@@ -2,14 +2,15 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private const string SqlSelectSetting = "SELECT `value` FROM `setting` WHERE `key` = @key;";
- private const string SqlInsertSetting = "INSERT INTO `setting` (`key`, `value`) VALUES (@key, @value);";
- private const string SqlUpdateSetting = "UPDATE `setting` SET `value`=@value WHERE `key`=@key;";
- private const string SqlDeleteSetting = "DELETE FROM `setting` WHERE `key`=@key;";
+ private const string SqlSelectSetting = "SELECT \"value\" FROM \"setting\" WHERE \"key\" = @key;";
+ private const string SqlInsertSetting = "INSERT INTO \"setting\" (\"key\", \"value\") VALUES (@key, @value);";
+ private const string SqlUpdateSetting = "UPDATE \"setting\" SET \"value\"=@value WHERE \"key\"=@key;";
+ private const string SqlDeleteSetting = "DELETE FROM \"setting\" WHERE \"key\"=@key;";
public bool SetSetting(string key, string value)
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbShortcut.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbShortcut.cs
index b0b6e5f77..2d6e82903 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbShortcut.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbShortcut.cs
@@ -3,24 +3,45 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private static readonly string[] ShortcutFields = new string[]
+ protected static readonly string[] ShortcutFields = new string[]
{
"character_id", "page_no", "button_no", "shortcut_id", "u32_data", "f32_data", "exex_type"
};
- private readonly string SqlInsertShortcut = $"INSERT INTO `ddon_shortcut` ({BuildQueryField(ShortcutFields)}) VALUES ({BuildQueryInsert(ShortcutFields)});";
- private readonly string SqlReplaceShortcut = $"INSERT OR REPLACE INTO `ddon_shortcut` ({BuildQueryField(ShortcutFields)}) VALUES ({BuildQueryInsert(ShortcutFields)});";
- private static readonly string SqlUpdateShortcut = $"UPDATE `ddon_shortcut` SET {BuildQueryUpdate(ShortcutFields)} WHERE `character_id`=@old_character_id AND `page_no`=@old_page_no AND `button_no`=@old_button_no";
- private static readonly string SqlSelectShortcuts = $"SELECT {BuildQueryField(ShortcutFields)} FROM `ddon_shortcut` WHERE `character_id`=@character_id;";
- private const string SqlDeleteShortcut = "DELETE FROM `ddon_shortcut` WHERE `character_id`=@character_id AND `page_no`=@page_no AND `button_no`=@button_no";
+ private readonly string SqlInsertShortcut = $"INSERT INTO \"ddon_shortcut\" ({BuildQueryField(ShortcutFields)}) VALUES ({BuildQueryInsert(ShortcutFields)});";
+ private readonly string SqlInsertIfNotExistsShortcut = $"INSERT INTO \"ddon_shortcut\" ({BuildQueryField(ShortcutFields)}) SELECT {BuildQueryInsert(ShortcutFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_shortcut\" WHERE \"character_id\"=@character_id AND \"page_no\"=@page_no AND \"button_no\"=@button_no);";
+ private static readonly string SqlUpdateShortcut = $"UPDATE \"ddon_shortcut\" SET {BuildQueryUpdate(ShortcutFields)} WHERE \"character_id\"=@old_character_id AND \"page_no\"=@old_page_no AND \"button_no\"=@old_button_no";
+ private static readonly string SqlSelectShortcuts = $"SELECT {BuildQueryField(ShortcutFields)} FROM \"ddon_shortcut\" WHERE \"character_id\"=@character_id;";
+ private const string SqlDeleteShortcut = "DELETE FROM \"ddon_shortcut\" WHERE \"character_id\"=@character_id AND \"page_no\"=@page_no AND \"button_no\"=@button_no";
+ public bool InsertIfNotExistsShortcut(uint characterId, CDataShortCut shortcut)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertShortcut(connection, characterId, shortcut);
+ }
+
+ public bool InsertIfNotExistsShortcut(TCon connection, uint characterId, CDataShortCut shortcut)
+ {
+ return ExecuteNonQuery(connection, SqlInsertIfNotExistsShortcut, command =>
+ {
+ AddParameter(command, characterId, shortcut);
+ }) == 1;
+ }
+
public bool InsertShortcut(uint characterId, CDataShortCut shortcut)
{
- return ExecuteNonQuery(SqlInsertShortcut, command =>
+ using TCon connection = OpenNewConnection();
+ return InsertShortcut(connection, characterId, shortcut);
+ }
+
+ public bool InsertShortcut(TCon connection, uint characterId, CDataShortCut shortcut)
+ {
+ return ExecuteNonQuery(connection, SqlInsertShortcut, command =>
{
AddParameter(command, characterId, shortcut);
}) == 1;
@@ -28,16 +49,29 @@ public bool InsertShortcut(uint characterId, CDataShortCut shortcut)
public bool ReplaceShortcut(uint characterId, CDataShortCut shortcut)
{
- ExecuteNonQuery(SqlReplaceShortcut, command =>
+ using TCon connection = OpenNewConnection();
+ return ReplaceShortcut(connection, characterId, shortcut);
+ }
+
+ public bool ReplaceShortcut(TCon connection, uint characterId, CDataShortCut shortcut)
+ {
+ Logger.Debug("Inserting shortcut.");
+ if (!InsertIfNotExistsShortcut(connection, characterId, shortcut))
{
- AddParameter(command, characterId, shortcut);
- });
+ Logger.Debug("Shortcut already exists, replacing.");
+ return UpdateShortcut(connection, characterId, shortcut.PageNo, shortcut.ButtonNo, shortcut);
+ }
return true;
}
public bool UpdateShortcut(uint characterId, uint oldPageNo, uint oldButtonNo, CDataShortCut updatedShortcut)
{
- return ExecuteNonQuery(SqlDeleteShortcut, command =>
+ using TCon connection = OpenNewConnection();
+ return UpdateShortcut(connection, characterId, oldPageNo, oldButtonNo, updatedShortcut);
+ }
+ public bool UpdateShortcut(TCon connection, uint characterId, uint oldPageNo, uint oldButtonNo, CDataShortCut updatedShortcut)
+ {
+ return ExecuteNonQuery(connection, SqlUpdateShortcut, command =>
{
AddParameter(command, characterId, updatedShortcut);
AddParameter(command, "@old_character_id", characterId);
@@ -56,7 +90,7 @@ public bool DeleteShortcut(uint characterId, uint pageNo, uint buttonNo)
}) == 1;
}
- private CDataShortCut ReadShortCut(DbDataReader reader)
+ private CDataShortCut ReadShortCut(TReader reader)
{
CDataShortCut shortcut = new CDataShortCut();
shortcut.PageNo = GetUInt32(reader, "page_no");
@@ -79,4 +113,4 @@ private void AddParameter(TCom command, uint characterId, CDataShortCut shortcut
AddParameter(command, "exex_type", shortcut.ExexType);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorage.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorage.cs
index 7caea38c9..93cdc2b8a 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorage.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorage.cs
@@ -5,25 +5,63 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private static readonly string[] StorageFields = new string[]
+ protected static readonly string[] StorageFields = new string[]
{
"character_id", "storage_type", "slot_max", "item_sort"
};
- private static readonly string SqlInsertStorage = $"INSERT INTO `ddon_storage` ({BuildQueryField(StorageFields)}) VALUES ({BuildQueryInsert(StorageFields)});";
- private static readonly string SqlReplaceStorage = $"INSERT OR REPLACE INTO `ddon_storage` ({BuildQueryField(StorageFields)}) VALUES ({BuildQueryInsert(StorageFields)});";
- private static readonly string SqlUpdateStorage = $"UPDATE `ddon_storage` SET {BuildQueryUpdate(StorageFields)} WHERE `storage_type` = @storage_type AND `character_id` = @character_id;";
- private static readonly string SqlSelectStorage = $"SELECT {BuildQueryField(StorageFields)} FROM `ddon_storage` WHERE `storage_type` = @storage_type AND `character_id` = @character_id;";
- private static readonly string SqlSelectAllStoragesByCharacter = $"SELECT {BuildQueryField(StorageFields)} FROM `ddon_storage` WHERE `character_id` = @character_id;";
- private static readonly string SqlDeleteStorage = "DELETE FROM `ddon_storage` WHERE `storage_type`=@storage_type AND `character_id` = @character_id;";
+ private static readonly string SqlInsertStorage = $"INSERT INTO \"ddon_storage\" ({BuildQueryField(StorageFields)}) VALUES ({BuildQueryInsert(StorageFields)});";
+ private static readonly string SqlInsertIfNotExistsStorage = $"INSERT INTO \"ddon_storage\" ({BuildQueryField(StorageFields)}) SELECT {BuildQueryInsert(StorageFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_storage\" WHERE \"storage_type\"=@storage_type AND \"character_id\" = @character_id);";
+ private static readonly string SqlUpdateStorage = $"UPDATE \"ddon_storage\" SET {BuildQueryUpdate(StorageFields)} WHERE \"storage_type\" = @storage_type AND \"character_id\" = @character_id;";
+ private static readonly string SqlSelectStorage = $"SELECT {BuildQueryField(StorageFields)} FROM \"ddon_storage\" WHERE \"storage_type\" = @storage_type AND \"character_id\" = @character_id;";
+ private static readonly string SqlSelectAllStoragesByCharacter = $"SELECT {BuildQueryField(StorageFields)} FROM \"ddon_storage\" WHERE \"character_id\" = @character_id;";
+ private static readonly string SqlDeleteStorage = "DELETE FROM \"ddon_storage\" WHERE \"storage_type\"=@storage_type AND \"character_id\" = @character_id;";
+ public bool ReplaceStorage(uint characterId, StorageType storageType, Storage storage)
+ {
+ using TCon connection = OpenNewConnection();
+ return ReplaceStorage(connection, characterId, storageType, storage);
+ }
+
+ public bool ReplaceStorage(TCon connection, uint characterId, StorageType storageType, Storage storage)
+ {
+ Logger.Debug("Inserting storage.");
+ if (!InsertIfNotExistsStorage(connection, characterId, storageType, storage))
+ {
+ Logger.Debug("Storage already exists, replacing.");
+ return UpdateStorage(connection, characterId, storageType, storage);
+ }
+ return true;
+ }
+
+ public bool InsertIfNotExistsStorage(uint characterId, StorageType storageType, Storage storage)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertIfNotExistsStorage(connection, characterId, storageType, storage);
+ }
+
+ public bool InsertIfNotExistsStorage(TCon connection, uint characterId, StorageType storageType, Storage storage)
+ {
+ return ExecuteNonQuery(connection, SqlInsertIfNotExistsStorage, command =>
+ {
+ AddParameter(command, characterId, storageType, storage);
+ }) == 1;
+ }
+
public bool InsertStorage(uint characterId, StorageType storageType, Storage storage)
{
- return ExecuteNonQuery(SqlInsertStorage, command =>
+ using TCon connection = OpenNewConnection();
+ return InsertStorage(connection, characterId, storageType, storage);
+ }
+
+ public bool InsertStorage(TCon connection, uint characterId, StorageType storageType, Storage storage)
+ {
+ return ExecuteNonQuery(connection, SqlInsertStorage, command =>
{
AddParameter(command, characterId, storageType, storage);
}) == 1;
@@ -31,7 +69,13 @@ public bool InsertStorage(uint characterId, StorageType storageType, Storage sto
public bool UpdateStorage(uint characterId, StorageType storageType, Storage storage)
{
- return ExecuteNonQuery(SqlUpdateStorage, command =>
+ using TCon connection = OpenNewConnection();
+ return UpdateStorage(connection, characterId, storageType, storage);
+ }
+
+ public bool UpdateStorage(TCon connection, uint characterId, StorageType storageType, Storage storage)
+ {
+ return ExecuteNonQuery(connection, SqlUpdateStorage, command =>
{
AddParameter(command, characterId, storageType, storage);
}) == 1;
@@ -46,8 +90,7 @@ public bool DeleteStorage(uint characterId, StorageType storageType)
}) == 1;
}
-
- private Tuple ReadStorage(DbDataReader reader)
+ private Tuple ReadStorage(TReader reader)
{
StorageType storageType = (StorageType) GetByte(reader, "storage_type");
ushort slotMax = GetUInt16(reader, "slot_max");
@@ -63,4 +106,4 @@ private void AddParameter(TCom command, uint characterId, StorageType storageTyp
AddParameter(command, "item_sort", storage.SortData);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorageItem.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorageItem.cs
index cfc9183bc..9dddad98d 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorageItem.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorageItem.cs
@@ -4,25 +4,27 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private static readonly string[] StorageItemFields = new string[]
+ protected static readonly string[] StorageItemFields = new string[]
{
"item_uid", "character_id", "storage_type", "slot_no", "item_num"
};
- private static readonly string SqlInsertStorageItem = $"INSERT INTO `ddon_storage_item` ({BuildQueryField(StorageItemFields)}) VALUES ({BuildQueryInsert(StorageItemFields)});";
- private static readonly string SqlReplaceStorageItem = $"INSERT OR REPLACE INTO `ddon_storage_item` ({BuildQueryField(StorageItemFields)}) VALUES ({BuildQueryInsert(StorageItemFields)});";
- private static readonly string SqlSelectStorageItemsByUId = $"SELECT {BuildQueryField(StorageItemFields)} FROM `ddon_storage_item` WHERE `item_uid`=@item_uid;";
- private static readonly string SqlSelectStorageItemsByCharacter = $"SELECT {BuildQueryField(StorageItemFields)} FROM `ddon_storage_item` WHERE `character_id`=@character_id;";
- private static readonly string SqlSelectStorageItemsByCharacterAndStorageType = $"SELECT {BuildQueryField(StorageItemFields)} FROM `ddon_storage_item` WHERE `character_id`=@character_id AND `storage_type`=@storage_type;";
- private static readonly string SqlDeleteStorageItem = "DELETE FROM `ddon_storage_item` WHERE `character_id`=@character_id AND `storage_type`=@storage_type AND `slot_no`=@slot_no;";
-
- public bool InsertStorageItem(TCon conn, uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum)
+ private static readonly string SqlInsertStorageItem = $"INSERT INTO \"ddon_storage_item\" ({BuildQueryField(StorageItemFields)}) VALUES ({BuildQueryInsert(StorageItemFields)});";
+ private static readonly string SqlInsertIfNotExistsStorageItem = $"INSERT INTO \"ddon_storage_item\" ({BuildQueryField(StorageItemFields)}) SELECT {BuildQueryInsert(StorageItemFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_storage_item\" WHERE \"character_id\"=@character_id AND \"storage_type\"=@storage_type AND \"slot_no\"=@slot_no);";
+ private static readonly string SqlSelectStorageItemsByUId = $"SELECT {BuildQueryField(StorageItemFields)} FROM \"ddon_storage_item\" WHERE \"item_uid\"=@item_uid;";
+ private static readonly string SqlSelectStorageItemsByCharacter = $"SELECT {BuildQueryField(StorageItemFields)} FROM \"ddon_storage_item\" WHERE \"character_id\"=@character_id;";
+ private static readonly string SqlSelectStorageItemsByCharacterAndStorageType = $"SELECT {BuildQueryField(StorageItemFields)} FROM \"ddon_storage_item\" WHERE \"character_id\"=@character_id AND \"storage_type\"=@storage_type;";
+ private static readonly string SqlDeleteStorageItem = "DELETE FROM \"ddon_storage_item\" WHERE \"character_id\"=@character_id AND \"storage_type\"=@storage_type AND \"slot_no\"=@slot_no;";
+ private static readonly string SqlUpdateStorageItem = $"UPDATE \"ddon_storage_item\" SET {BuildQueryUpdate(StorageItemFields)} WHERE \"character_id\"=@character_id AND \"storage_type\"=@storage_type AND \"slot_no\"=@slot_no;";
+
+ public bool InsertIfNotExistsStorageItem(TCon conn, uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum)
{
- return ExecuteNonQuery(conn, SqlInsertStorageItem, command =>
+ return ExecuteNonQuery(conn, SqlInsertIfNotExistsStorageItem, command =>
{
AddParameter(command, "character_id", characterId);
AddParameter(command, "storage_type", (byte) storageType);
@@ -32,14 +34,15 @@ public bool InsertStorageItem(TCon conn, uint characterId, StorageType storageTy
}) == 1;
}
- public bool InsertStorageItem(uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum)
+ public bool InsertIfNotExistsStorageItem(uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum)
{
- return this.InsertStorageItem(null, characterId, storageType, slotNo, itemUId, itemNum);
+ using TCon connection = OpenNewConnection();
+ return InsertIfNotExistsStorageItem(connection, characterId, storageType, slotNo, itemUId, itemNum);
}
-
- public bool ReplaceStorageItem(TCon conn, uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum)
+
+ public bool InsertStorageItem(TCon conn, uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum)
{
- return ExecuteNonQuery(conn, SqlReplaceStorageItem, command =>
+ return ExecuteNonQuery(conn, SqlInsertStorageItem, command =>
{
AddParameter(command, "character_id", characterId);
AddParameter(command, "storage_type", (byte) storageType);
@@ -49,9 +52,27 @@ public bool ReplaceStorageItem(TCon conn, uint characterId, StorageType storageT
}) == 1;
}
+ public bool InsertStorageItem(uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertStorageItem(connection, characterId, storageType, slotNo, itemUId, itemNum);
+ }
+
+ public bool ReplaceStorageItem(TCon conn, uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum)
+ {
+ Logger.Debug("Inserting storage item.");
+ if (!InsertIfNotExistsStorageItem(conn, characterId, storageType, slotNo, itemUId, itemNum))
+ {
+ Logger.Debug("Storage item already exists, replacing.");
+ return UpdateStorageItem(conn, characterId, storageType, slotNo, itemUId, itemNum);
+ }
+ return true;
+ }
+
public bool ReplaceStorageItem(uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum)
{
- return this.ReplaceStorageItem(null, characterId, storageType, slotNo, itemUId, itemNum);
+ using TCon connection = OpenNewConnection();
+ return ReplaceStorageItem(connection, characterId, storageType, slotNo, itemUId, itemNum);
}
public bool DeleteStorageItem(uint characterId, StorageType storageType, ushort slotNo)
@@ -63,5 +84,23 @@ public bool DeleteStorageItem(uint characterId, StorageType storageType, ushort
AddParameter(command, "slot_no", slotNo);
}) == 1;
}
+
+ public bool UpdateStorageItem(uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum)
+ {
+ using TCon connection = OpenNewConnection();
+ return UpdateStorageItem(connection, characterId, storageType, slotNo, itemUId, itemNum);
+ }
+
+ public bool UpdateStorageItem(TCon connection, uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum)
+ {
+ return ExecuteNonQuery(connection, SqlUpdateStorageItem, command =>
+ {
+ AddParameter(command, "character_id", characterId);
+ AddParameter(command, "storage_type", (byte) storageType);
+ AddParameter(command, "slot_no", slotNo);
+ AddParameter(command, "item_uid", itemUId);
+ AddParameter(command, "item_num", itemNum);
+ }) == 1;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbWalletPoint.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbWalletPoint.cs
index 04b63fbd0..af7c6b316 100644
--- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbWalletPoint.cs
+++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbWalletPoint.cs
@@ -4,24 +4,45 @@
namespace Arrowgene.Ddon.Database.Sql.Core
{
- public abstract partial class DdonSqlDb : SqlDb
+ public abstract partial class DdonSqlDb : SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- private static readonly string[] WalletPointFields = new string[]
+ protected static readonly string[] WalletPointFields = new string[]
{
"character_id", "type", "value"
};
- private readonly string SqlInsertWalletPoint = $"INSERT INTO `ddon_wallet_point` ({BuildQueryField(WalletPointFields)}) VALUES ({BuildQueryInsert(WalletPointFields)});";
- private readonly string SqlReplaceWalletPoint = $"INSERT OR REPLACE INTO `ddon_wallet_point` ({BuildQueryField(WalletPointFields)}) VALUES ({BuildQueryInsert(WalletPointFields)});";
- private static readonly string SqlUpdateWalletPoint = $"UPDATE `ddon_wallet_point` SET {BuildQueryUpdate(WalletPointFields)} WHERE `character_id`=@character_id AND `type`=@type";
- private static readonly string SqlSelectWalletPoints = $"SELECT {BuildQueryField(WalletPointFields)} FROM `ddon_wallet_point` WHERE `character_id`=@character_id;";
- private const string SqlDeleteWalletPoint = "DELETE FROM `ddon_wallet_point` WHERE `character_id`=@character_id AND `type`=@type";
+ private readonly string SqlInsertWalletPoint = $"INSERT INTO \"ddon_wallet_point\" ({BuildQueryField(WalletPointFields)}) VALUES ({BuildQueryInsert(WalletPointFields)});";
+ private readonly string SqlInsertIfNotExistsWalletPoint = $"INSERT INTO \"ddon_wallet_point\" ({BuildQueryField(WalletPointFields)}) SELECT {BuildQueryInsert(WalletPointFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_wallet_point\" WHERE \"character_id\"=@character_id AND \"type\"=@type);";
+ private static readonly string SqlUpdateWalletPoint = $"UPDATE \"ddon_wallet_point\" SET {BuildQueryUpdate(WalletPointFields)} WHERE \"character_id\"=@character_id AND \"type\"=@type";
+ private static readonly string SqlSelectWalletPoints = $"SELECT {BuildQueryField(WalletPointFields)} FROM \"ddon_wallet_point\" WHERE \"character_id\"=@character_id;";
+ private const string SqlDeleteWalletPoint = "DELETE FROM \"ddon_wallet_point\" WHERE \"character_id\"=@character_id AND \"type\"=@type";
+ public bool InsertIfNotExistsWalletPoint(uint characterId, CDataWalletPoint walletPoint)
+ {
+ using TCon connection = OpenNewConnection();
+ return InsertIfNotExistsWalletPoint(connection, characterId, walletPoint);
+ }
+
+ public bool InsertIfNotExistsWalletPoint(TCon connection, uint characterId, CDataWalletPoint walletPoint)
+ {
+ return ExecuteNonQuery(connection, SqlInsertIfNotExistsWalletPoint, command =>
+ {
+ AddParameter(command, characterId, walletPoint);
+ }) == 1;
+ }
+
public bool InsertWalletPoint(uint characterId, CDataWalletPoint walletPoint)
{
- return ExecuteNonQuery(SqlInsertWalletPoint, command =>
+ using TCon connection = OpenNewConnection();
+ return InsertWalletPoint(connection, characterId, walletPoint);
+ }
+
+ public bool InsertWalletPoint(TCon connection, uint characterId, CDataWalletPoint walletPoint)
+ {
+ return ExecuteNonQuery(connection, SqlInsertWalletPoint, command =>
{
AddParameter(command, characterId, walletPoint);
}) == 1;
@@ -29,16 +50,30 @@ public bool InsertWalletPoint(uint characterId, CDataWalletPoint walletPoint)
public bool ReplaceWalletPoint(uint characterId, CDataWalletPoint walletPoint)
{
- ExecuteNonQuery(SqlReplaceWalletPoint, command =>
+ using TCon connection = OpenNewConnection();
+ return ReplaceWalletPoint(connection, characterId, walletPoint);
+ }
+
+ public bool ReplaceWalletPoint(TCon connection, uint characterId, CDataWalletPoint walletPoint)
+ {
+ Logger.Debug("Inserting wallet point.");
+ if (!InsertIfNotExistsWalletPoint(connection, characterId, walletPoint))
{
- AddParameter(command, characterId, walletPoint);
- });
+ Logger.Debug("Wallet point already exists, replacing.");
+ return UpdateWalletPoint(connection, characterId, walletPoint);
+ }
return true;
}
public bool UpdateWalletPoint(uint characterId, CDataWalletPoint updatedWalletPoint)
{
- return ExecuteNonQuery(SqlUpdateWalletPoint, command =>
+ using TCon connection = OpenNewConnection();
+ return UpdateWalletPoint(connection, characterId, updatedWalletPoint);
+ }
+
+ public bool UpdateWalletPoint(TCon connection, uint characterId, CDataWalletPoint updatedWalletPoint)
+ {
+ return ExecuteNonQuery(connection, SqlUpdateWalletPoint, command =>
{
AddParameter(command, characterId, updatedWalletPoint);
}) == 1;
@@ -53,7 +88,7 @@ public bool DeleteWalletPoint(uint characterId, WalletType type)
}) == 1;
}
- private CDataWalletPoint ReadWalletPoint(DbDataReader reader)
+ private CDataWalletPoint ReadWalletPoint(TReader reader)
{
CDataWalletPoint walletPoint = new CDataWalletPoint();
walletPoint.Type = (WalletType) GetByte(reader, "type");
@@ -68,4 +103,4 @@ private void AddParameter(TCom command, uint characterId, CDataWalletPoint walle
AddParameter(command, "value", walletPoint.Value);
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.Database/Sql/DdonMariaDb.cs b/Arrowgene.Ddon.Database/Sql/DdonMariaDb.cs
new file mode 100644
index 000000000..c2b425d8f
--- /dev/null
+++ b/Arrowgene.Ddon.Database/Sql/DdonMariaDb.cs
@@ -0,0 +1,79 @@
+using System;
+using Arrowgene.Ddon.Database.Sql.Core;
+using Arrowgene.Logging;
+using MySqlConnector;
+
+namespace Arrowgene.Ddon.Database.Sql
+{
+ public class DdonMariaDb : DdonSqlDb, IDatabase
+ {
+ private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonMariaDb));
+
+ private string _connectionString;
+
+ public DdonMariaDb(string host, string user, string password, string database, bool wipeOnStartup)
+ {
+ _connectionString = BuildConnectionString(host, user, password, database);
+ if (wipeOnStartup)
+ {
+ Logger.Info($"WipeOnStartup is currently not supported.");
+ }
+ }
+
+ public bool CreateDatabase()
+ {
+ if (_connectionString == null)
+ {
+ Logger.Error($"Failed to build connection string");
+ return false;
+ }
+
+ ReusableConnection = new MySqlConnection(_connectionString);
+ return true;
+ }
+
+ private string BuildConnectionString(string host, string user, string password, string database)
+ {
+ MySqlConnectionStringBuilder builder = new MySqlConnectionStringBuilder
+ {
+ Server = host,
+ UserID = user,
+ Password = password,
+ Database = database,
+ IgnoreCommandTransaction = true,
+ Pooling = true
+ };
+ string connectionString = builder.ToString();
+ Logger.Info($"Connection String: {connectionString}");
+ return connectionString;
+ }
+
+ protected override MySqlConnection OpenNewConnection()
+ {
+ MySqlConnection connection = new MySqlConnection(_connectionString);
+ connection.Open();
+ return connection;
+ }
+
+ protected override MySqlCommand Command(string query, MySqlConnection connection)
+ {
+ return new MySqlCommand(query, connection);
+ }
+
+ ///
+ /// Always returns the first generated ID in a multi-statement environment. Ideally should be used on a per-connection basis.
+ /// https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_last-insert-id
+ ///
+ protected override long AutoIncrement(MySqlConnection connection, MySqlCommand command)
+ {
+ return command.LastInsertedId;
+ }
+
+ public override int Upsert(string table, string[] columns, object[] values, string whereColumn,
+ object whereValue,
+ out long autoIncrement)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Arrowgene.Ddon.Database/Sql/DdonPostgresDb.cs b/Arrowgene.Ddon.Database/Sql/DdonPostgresDb.cs
new file mode 100644
index 000000000..56f44af4c
--- /dev/null
+++ b/Arrowgene.Ddon.Database/Sql/DdonPostgresDb.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Data;
+using Arrowgene.Ddon.Database.Sql.Core;
+using Arrowgene.Logging;
+using Npgsql;
+
+namespace Arrowgene.Ddon.Database.Sql
+{
+ public class DdonPostgresDb : DdonSqlDb, IDatabase
+ {
+ private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonPostgresDb));
+
+ private string _connectionString;
+ private NpgsqlDataSource _dataSource;
+
+ public DdonPostgresDb(string host, string user, string password, string database, bool wipeOnStartup)
+ {
+ _connectionString = BuildConnectionString(host, user, password, database);
+ if (wipeOnStartup)
+ {
+ Logger.Info($"WipeOnStartup is currently not supported.");
+ }
+ }
+
+ public bool CreateDatabase()
+ {
+ if (_connectionString == null)
+ {
+ Logger.Error($"Failed to build connection string");
+ return false;
+ }
+
+ if (_dataSource == null)
+ {
+ var dataSourceBuilder = new NpgsqlDataSourceBuilder(_connectionString);
+ dataSourceBuilder.EnableParameterLogging();
+ _dataSource = dataSourceBuilder.Build();
+ }
+
+ ReusableConnection = _dataSource.OpenConnection();
+ return true;
+ }
+
+ private string BuildConnectionString(string host, string user, string password, string database)
+ {
+ NpgsqlConnectionStringBuilder builder = new NpgsqlConnectionStringBuilder
+ {
+ Host = host,
+ Username = user,
+ Password = password,
+ Database = database,
+ Pooling = true
+ };
+ string connectionString = builder.ToString();
+ Logger.Info($"Connection String: {connectionString}");
+ return connectionString;
+ }
+
+ protected override NpgsqlConnection OpenNewConnection()
+ {
+ return _dataSource.OpenConnection();
+ }
+
+ protected override NpgsqlCommand Command(string query, NpgsqlConnection connection)
+ {
+ return new NpgsqlCommand(query, connection);
+ }
+
+ ///
+ /// Safe within the same connection session (transaction?), but unsafe if triggers are involved.
+ /// https://stackoverflow.com/questions/2944297/postgresql-function-for-last-inserted-id
+ ///
+ protected override long AutoIncrement(NpgsqlConnection connection, NpgsqlCommand command)
+ {
+ using NpgsqlCommand lastIdCommand = new NpgsqlCommand("SELECT LASTVAL();", connection);
+ return (long)lastIdCommand.ExecuteScalar();
+ }
+
+ public override int Upsert(string table, string[] columns, object[] values, string whereColumn,
+ object whereValue,
+ out long autoIncrement)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override void AddParameter(NpgsqlCommand command, string name, DateTime? value)
+ {
+ if (value.HasValue)
+ {
+ AddParameter(command, name, DateTime.SpecifyKind(value.Value, DateTimeKind.Utc), DbType.DateTime);
+ }
+ else
+ {
+ AddParameter(command, name, value, DbType.DateTime);
+ }
+ }
+
+ protected override void AddParameter(NpgsqlCommand command, string name, DateTime value)
+ {
+ AddParameter(command, name, DateTime.SpecifyKind(value, DateTimeKind.Utc), DbType.DateTime);
+ }
+
+ protected override DateTime GetDateTime(NpgsqlDataReader reader, string column)
+ {
+ return DateTime.SpecifyKind(reader.GetDateTime(reader.GetOrdinal(column)), DateTimeKind.Utc);
+ }
+
+ protected override DateTime? GetDateTimeNullable(NpgsqlDataReader reader, int ordinal)
+ {
+ if (reader.IsDBNull(ordinal))
+ {
+ return null;
+ }
+
+ return DateTime.SpecifyKind(reader.GetDateTime(ordinal), DateTimeKind.Utc);
+ }
+ }
+}
diff --git a/Arrowgene.Ddon.Database/Sql/DdonSqLiteDb.cs b/Arrowgene.Ddon.Database/Sql/DdonSqLiteDb.cs
index a40aa0ecf..fac928bce 100644
--- a/Arrowgene.Ddon.Database/Sql/DdonSqLiteDb.cs
+++ b/Arrowgene.Ddon.Database/Sql/DdonSqLiteDb.cs
@@ -6,10 +6,7 @@
namespace Arrowgene.Ddon.Database.Sql
{
- ///
- /// SQLite Ddon database.
- ///
- public class DdonSqLiteDb : DdonSqlDb, IDatabase
+ public class DdonSqLiteDb : DdonSqlDb, IDatabase
{
private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonSqLiteDb));
@@ -21,11 +18,22 @@ public class DdonSqLiteDb : DdonSqlDb, IDatabas
private string _connectionString;
private SQLiteConnection _memoryConnection;
- public DdonSqLiteDb(string databasePath)
+ public DdonSqLiteDb(string databasePath, bool wipeOnStartup)
{
_memoryConnection = null;
_databasePath = databasePath;
- Logger.Info($"Database Path: {_databasePath}");
+ if (wipeOnStartup)
+ {
+ try
+ {
+ File.Delete(_databasePath);
+ Logger.Info($"Database has been wiped.");
+ }
+ catch (Exception)
+ {
+ Logger.Error($"Failed to wipe database.");
+ }
+ }
}
public bool CreateDatabase()
@@ -37,6 +45,8 @@ public bool CreateDatabase()
return false;
}
+ ReusableConnection = new SQLiteConnection(_connectionString);
+
if (_databasePath == MemoryDatabasePath)
{
throw new NotSupportedException("Connections are utilized via `using`, disposing the connection. In Memory DB only available for lifetime of connection");
@@ -59,22 +69,24 @@ public bool CreateDatabase()
private string BuildConnectionString(string source)
{
- SQLiteConnectionStringBuilder builder = new SQLiteConnectionStringBuilder();
- builder.DataSource = source;
- builder.Version = 3;
- builder.ForeignKeys = true;
- // Set ADO.NET conformance flag https://system.data.sqlite.org/index.html/info/e36e05e299
- builder.Flags = builder.Flags & SQLiteConnectionFlags.StrictConformance;
+ SQLiteConnectionStringBuilder builder = new SQLiteConnectionStringBuilder
+ {
+ DataSource = source,
+ Version = 3,
+ ForeignKeys = true,
+ Pooling = true,
+ // Set ADO.NET conformance flag https://system.data.sqlite.org/index.html/info/e36e05e299
+ Flags = SQLiteConnectionFlags.Default | SQLiteConnectionFlags.StrictConformance
+ };
string connectionString = builder.ToString();
Logger.Info($"Connection String: {connectionString}");
return connectionString;
}
- protected override SQLiteConnection Connection()
+ protected override SQLiteConnection OpenNewConnection()
{
- SQLiteConnection connection = new SQLiteConnection(_connectionString);
- return connection.OpenAndReturn();
+ return new SQLiteConnection(_connectionString).OpenAndReturn();
}
protected override SQLiteCommand Command(string query, SQLiteConnection connection)
diff --git a/Arrowgene.Ddon.Database/Sql/SqlDb.cs b/Arrowgene.Ddon.Database/Sql/SqlDb.cs
index 3922f4b0b..134e0e957 100644
--- a/Arrowgene.Ddon.Database/Sql/SqlDb.cs
+++ b/Arrowgene.Ddon.Database/Sql/SqlDb.cs
@@ -9,18 +9,41 @@ namespace Arrowgene.Ddon.Database.Sql
///
/// Operations for SQL type databases.
///
- public abstract class SqlDb
+ public abstract class SqlDb
where TCon : DbConnection
where TCom : DbCommand
+ where TReader : DbDataReader
{
- public const int NoRowsAffected = 0;
- public const long NoAutoIncrement = 0;
+ protected const int NoRowsAffected = 0;
+ protected const long NoAutoIncrement = 0;
- public SqlDb()
+ protected abstract TCon OpenNewConnection();
+
+ protected virtual TCon ReusableConnection { get; set; }
+
+ ///
+ /// Reusing connections can usually only be done in special cases, depending on the database engine:
+ /// - One operation at a time.
+ /// - Not thread-safe.
+ /// If unsure, check DB engine connector documentation or prefer to use .
+ ///
+ /// An opened, prior-existing connection
+ protected virtual TCon OpenExistingConnection()
{
+ switch (ReusableConnection.State)
+ {
+ case ConnectionState.Closed:
+ ReusableConnection.Open();
+ break;
+ case ConnectionState.Broken:
+ ReusableConnection.Close();
+ ReusableConnection.Open();
+ break;
+ }
+
+ return ReusableConnection;
}
- protected abstract TCon Connection();
protected abstract TCom Command(string query, TCon connection);
protected abstract long AutoIncrement(TCon connection, TCom command);
@@ -29,54 +52,47 @@ public abstract int Upsert(string table, string[] columns, object[] values, stri
public bool ExecuteInTransaction(Action action)
{
- using (TCon connection = Connection())
+ using TCon connection = OpenNewConnection();
+ using DbTransaction transaction = connection.BeginTransaction();
+ try
{
- try
- {
- Execute(connection, "BEGIN TRANSACTION");
- action(connection);
- Execute(connection, "COMMIT");
- return true;
- }
- catch (Exception ex)
- {
- Execute(connection, "ROLLBACK");
- Exception(ex);
- return false;
- }
+ action(connection);
+ transaction.Commit();
+ return true;
+ }
+ catch (Exception ex)
+ {
+ transaction.Rollback();
+ connection.Close();
+ Exception(ex);
+ return false;
+ }
+ finally
+ {
+ connection.Close();
}
}
public int ExecuteNonQuery(string query, Action nonQueryAction)
{
- return ExecuteNonQuery(null, query, nonQueryAction);
+ using TCon connection = OpenNewConnection();
+ try
+ {
+ return ExecuteNonQuery(connection, query, nonQueryAction);
+ }
+ finally
+ {
+ connection.Close();
+ }
}
- public int ExecuteNonQuery(TCon? conn, string query, Action nonQueryAction)
+ public int ExecuteNonQuery(TCon conn, string query, Action nonQueryAction)
{
try
{
- bool autoCloseConnection = false;
- TCon? connection = conn;
- if (connection == null)
- {
- autoCloseConnection = true;
- connection = Connection();
- }
-
- int rowsAffected = 0;
- using (TCom command = Command(query, connection))
- {
- nonQueryAction(command);
- rowsAffected = command.ExecuteNonQuery();
- }
-
- if (autoCloseConnection)
- {
- connection.Close();
- }
-
- return rowsAffected;
+ using TCom command = Command(query, conn);
+ nonQueryAction(command);
+ return command.ExecuteNonQuery();
}
catch (Exception ex)
{
@@ -87,34 +103,25 @@ public int ExecuteNonQuery(TCon? conn, string query, Action nonQueryAction
public int ExecuteNonQuery(string query, Action nonQueryAction, out long autoIncrement)
{
- return ExecuteNonQuery(null, query, nonQueryAction, out autoIncrement);
+ using TCon connection = OpenNewConnection();
+ try
+ {
+ return ExecuteNonQuery(connection, query, nonQueryAction, out autoIncrement);
+ }
+ finally
+ {
+ connection.Close();
+ }
}
- public int ExecuteNonQuery(TCon? conn, string query, Action nonQueryAction, out long autoIncrement)
+ public int ExecuteNonQuery(TCon conn, string query, Action nonQueryAction, out long autoIncrement)
{
try
{
- bool autoCloseConnection = false;
- TCon? connection = conn;
- if (connection == null)
- {
- autoCloseConnection = true;
- connection = Connection();
- }
-
- int rowsAffected = 0;
- using (TCom command = Command(query, connection))
- {
- nonQueryAction(command);
- rowsAffected = command.ExecuteNonQuery();
- autoIncrement = AutoIncrement(connection, command);
- }
-
- if (autoCloseConnection)
- {
- connection.Close();
- }
-
+ using TCom command = Command(query, conn);
+ nonQueryAction(command);
+ var rowsAffected = command.ExecuteNonQuery();
+ autoIncrement = AutoIncrement(conn, command);
return rowsAffected;
}
catch (Exception ex)
@@ -125,36 +132,27 @@ public int ExecuteNonQuery(TCon? conn, string query, Action nonQueryAction
}
}
- public void ExecuteReader(string query, Action nonQueryAction, Action readAction)
+ public void ExecuteReader(string query, Action nonQueryAction, Action readAction)
{
- ExecuteReader(null, query, nonQueryAction, readAction);
+ using TCon connection = OpenNewConnection();
+ try
+ {
+ ExecuteReader(connection, query, nonQueryAction, readAction);
+ }
+ finally
+ {
+ connection.Close();
+ }
}
- public void ExecuteReader(TCon? conn, string query, Action nonQueryAction, Action readAction)
+ public void ExecuteReader(TCon conn, string query, Action nonQueryAction, Action readAction)
{
try
{
- bool autoCloseConnection = false;
- TCon? connection = conn;
- if (connection == null)
- {
- autoCloseConnection = true;
- connection = Connection();
- }
-
- using (TCom command = Command(query, connection))
- {
- nonQueryAction(command);
- using (DbDataReader reader = command.ExecuteReader())
- {
- readAction(reader);
- }
- }
-
- if (autoCloseConnection)
- {
- connection.Close();
- }
+ using TCom command = Command(query, conn);
+ nonQueryAction(command);
+ using TReader reader = (TReader)command.ExecuteReader();
+ readAction(reader);
}
catch (Exception ex)
{
@@ -162,35 +160,26 @@ public void ExecuteReader(TCon? conn, string query, Action nonQueryAction,
}
}
- public void ExecuteReader(string query, Action readAction)
+ public void ExecuteReader(string query, Action readAction)
{
- ExecuteReader(null, query, readAction);
+ using TCon connection = OpenNewConnection();
+ try
+ {
+ ExecuteReader(connection, query, readAction);
+ }
+ finally
+ {
+ connection.Close();
+ }
}
- public void ExecuteReader(TCon? conn, string query, Action readAction)
+ public void ExecuteReader(TCon conn, string query, Action readAction)
{
try
{
- bool autoCloseConnection = false;
- TCon? connection = conn;
- if (connection == null)
- {
- autoCloseConnection = true;
- connection = Connection();
- }
-
- using (TCom command = Command(query, connection))
- {
- using (DbDataReader reader = command.ExecuteReader())
- {
- readAction(reader);
- }
- }
-
- if (autoCloseConnection)
- {
- connection.Close();
- }
+ using TCom command = Command(query, conn);
+ using TReader reader = (TReader)command.ExecuteReader();
+ readAction(reader);
}
catch (Exception ex)
{
@@ -200,30 +189,23 @@ public void ExecuteReader(TCon? conn, string query, Action readAct
public void Execute(string query)
{
- Execute(null, query);
+ using TCon connection = OpenNewConnection();
+ try
+ {
+ Execute(connection, query);
+ }
+ finally
+ {
+ connection.Close();
+ }
}
public void Execute(TCon? conn, string query)
{
try
{
- bool autoCloseConnection = false;
- TCon? connection = conn;
- if (connection == null)
- {
- autoCloseConnection = true;
- connection = Connection();
- }
-
- using (TCom command = Command(query, connection))
- {
- command.ExecuteNonQuery();
- }
-
- if (autoCloseConnection)
- {
- connection.Close();
- }
+ using TCom command = Command(query, conn);
+ command.ExecuteNonQuery();
}
catch (Exception ex)
{
@@ -233,29 +215,22 @@ public void Execute(TCon? conn, string query)
public string ServerVersion()
{
- return ServerVersion(null);
+ using TCon connection = OpenNewConnection();
+ try
+ {
+ return ServerVersion(connection);
+ }
+ finally
+ {
+ connection.Close();
+ }
}
- public string ServerVersion(TCon? conn)
+ public string ServerVersion(TCon conn)
{
try
{
- bool autoCloseConnection = false;
- TCon? connection = conn;
- if (connection == null)
- {
- autoCloseConnection = true;
- connection = Connection();
- }
-
- string serverVersion = connection.ServerVersion;
-
- if (autoCloseConnection)
- {
- connection.Close();
- }
-
- return serverVersion;
+ return conn.ServerVersion;
}
catch (Exception ex)
{
@@ -309,9 +284,14 @@ protected void AddParameter(TCom command, string name, byte value)
AddParameter(command, name, value, DbType.Byte);
}
+ protected void AddParameter(TCom command, string name, UInt16 value)
+ {
+ AddParameter(command, name, (short)value, DbType.Int16);
+ }
+
protected void AddParameter(TCom command, string name, UInt32 value)
{
- AddParameter(command, name, value, DbType.UInt32);
+ AddParameter(command, name, (int)value, DbType.Int32);
}
protected void AddParameterEnumInt32(TCom command, string name, T value) where T : Enum
@@ -319,12 +299,12 @@ protected void AddParameterEnumInt32(TCom command, string name, T value) wher
AddParameter(command, name, (Int32)(object)value, DbType.Int32);
}
- protected void AddParameter(TCom command, string name, DateTime? value)
+ protected virtual void AddParameter(TCom command, string name, DateTime? value)
{
AddParameter(command, name, value, DbType.DateTime);
}
- protected void AddParameter(TCom command, string name, DateTime value)
+ protected virtual void AddParameter(TCom command, string name, DateTime value)
{
AddParameter(command, name, value, DbType.DateTime);
}
@@ -339,7 +319,7 @@ protected void AddParameter(TCom command, string name, bool value)
AddParameter(command, name, value, DbType.Boolean);
}
- protected DateTime? GetDateTimeNullable(DbDataReader reader, int ordinal)
+ protected virtual DateTime? GetDateTimeNullable(TReader reader, int ordinal)
{
if (reader.IsDBNull(ordinal))
{
@@ -349,7 +329,7 @@ protected void AddParameter(TCom command, string name, bool value)
return reader.GetDateTime(ordinal);
}
- protected string? GetStringNullable(DbDataReader reader, int ordinal)
+ protected string? GetStringNullable(TReader reader, int ordinal)
{
if (reader.IsDBNull(ordinal))
{
@@ -359,9 +339,9 @@ protected void AddParameter(TCom command, string name, bool value)
return reader.GetString(ordinal);
}
- protected byte[]? GetBytesNullable(DbDataReader reader, int ordinal, int size)
+ protected byte[]? GetBytesNullable(TReader reader, int ordinal, int size)
{
- if(reader.IsDBNull(ordinal))
+ if (reader.IsDBNull(ordinal))
{
return null;
}
@@ -371,76 +351,76 @@ protected void AddParameter(TCom command, string name, bool value)
return buffer;
}
- protected int GetInt32(DbDataReader reader, string column)
+ protected int GetInt32(TReader reader, string column)
{
return reader.GetInt32(reader.GetOrdinal(column));
}
- protected uint GetUInt32(DbDataReader reader, string column)
+ protected uint GetUInt32(TReader reader, string column)
{
return (uint)reader.GetInt32(reader.GetOrdinal(column));
}
- protected byte GetByte(DbDataReader reader, string column)
+ protected byte GetByte(TReader reader, string column)
{
return reader.GetByte(reader.GetOrdinal(column));
}
- protected short GetInt16(DbDataReader reader, string column)
+ protected short GetInt16(TReader reader, string column)
{
return reader.GetInt16(reader.GetOrdinal(column));
}
- protected ushort GetUInt16(DbDataReader reader, string column)
+ protected ushort GetUInt16(TReader reader, string column)
{
return (ushort)reader.GetInt16(reader.GetOrdinal(column));
}
- protected float GetFloat(DbDataReader reader, string column)
+ protected float GetFloat(TReader reader, string column)
{
return reader.GetFloat(reader.GetOrdinal(column));
}
- protected string GetString(DbDataReader reader, string column)
+ protected string GetString(TReader reader, string column)
{
return reader.GetString(reader.GetOrdinal(column));
}
- protected bool GetBoolean(DbDataReader reader, string column)
+ protected bool GetBoolean(TReader reader, string column)
{
return reader.GetBoolean(reader.GetOrdinal(column));
}
- protected T GetEnumInt32(DbDataReader reader, string column) where T : Enum
+ protected T GetEnumInt32(TReader reader, string column) where T : Enum
{
return (T)(object)reader.GetInt32(reader.GetOrdinal(column));
}
- protected DateTime GetDateTime(DbDataReader reader, string column)
+ protected virtual DateTime GetDateTime(TReader reader, string column)
{
return reader.GetDateTime(reader.GetOrdinal(column));
}
- protected byte[] GetBytes(DbDataReader reader, string column, int size)
+ protected byte[] GetBytes(TReader reader, string column, int size)
{
byte[] buffer = new byte[size];
reader.GetBytes(reader.GetOrdinal(column), 0, buffer, 0, size);
return buffer;
}
- protected DateTime? GetDateTimeNullable(DbDataReader reader, string column)
+ protected DateTime? GetDateTimeNullable(TReader reader, string column)
{
int ordinal = reader.GetOrdinal(column);
return GetDateTimeNullable(reader, ordinal);
}
- protected string? GetStringNullable(DbDataReader reader, string column)
+ protected string? GetStringNullable(TReader reader, string column)
{
int ordinal = reader.GetOrdinal(column);
return GetStringNullable(reader, ordinal);
}
- protected byte[]? GetBytesNullable(DbDataReader reader, string column, int size)
+ protected byte[]? GetBytesNullable(TReader reader, string column, int size)
{
int ordinal = reader.GetOrdinal(column);
return GetBytesNullable(reader, ordinal, size);
diff --git a/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs b/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs
index e01636542..5a63d41f9 100644
--- a/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs
+++ b/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs
@@ -208,9 +208,8 @@ private void EquipItem(DdonGameServer server, GameClient client, CharacterCommon
uint itemToEquipNum = tuple.item.Item2;
ushort storageSlotNo = tuple.slot;
- Item? previouslyEquippedItem = characterToEquipTo.Equipment.GetEquipItem(characterToEquipTo.Job, equipType, equipSlot);
+ Item? previouslyEquippedItem = characterToEquipTo.Equipment.SetEquipItem(itemToEquip, characterToEquipTo.Job, equipType, equipSlot);
- characterToEquipTo.Equipment.SetEquipItem(itemToEquip, characterToEquipTo.Job, equipType, equipSlot);
server.Database.ReplaceEquipItem(characterToEquipTo.CommonId, characterToEquipTo.Job, equipType, equipSlot, itemToEquip.UId);
if(previouslyEquippedItem != null)
@@ -307,4 +306,4 @@ private void EquipItem(DdonGameServer server, GameClient client, CharacterCommon
});
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.GameServer/Chat/Log/ChatMessageLogEntry.cs b/Arrowgene.Ddon.GameServer/Chat/Log/ChatMessageLogEntry.cs
index 08fea911d..146d876ec 100644
--- a/Arrowgene.Ddon.GameServer/Chat/Log/ChatMessageLogEntry.cs
+++ b/Arrowgene.Ddon.GameServer/Chat/Log/ChatMessageLogEntry.cs
@@ -12,7 +12,7 @@ public ChatMessageLogEntry()
public ChatMessageLogEntry(Character character, ChatMessage chatMessage)
{
- DateTime = DateTime.Now;
+ DateTime = DateTime.UtcNow;
FirstName = character.FirstName;
LastName = character.LastName;
CharacterId = character.CharacterId;
diff --git a/Arrowgene.Ddon.GameServer/Handler/CharacterChargeRevivePointHandler.cs b/Arrowgene.Ddon.GameServer/Handler/CharacterChargeRevivePointHandler.cs
index c427a2e5c..4d1fa6a1c 100644
--- a/Arrowgene.Ddon.GameServer/Handler/CharacterChargeRevivePointHandler.cs
+++ b/Arrowgene.Ddon.GameServer/Handler/CharacterChargeRevivePointHandler.cs
@@ -19,7 +19,7 @@ public override void Handle(GameClient client, StructurePacket connections = Database.SelectConnectionsByAccountId(account.Id);
if (connections.Count > 0)
diff --git a/Arrowgene.Ddon.GameServer/Handler/WarpAreaWarpHandler.cs b/Arrowgene.Ddon.GameServer/Handler/WarpAreaWarpHandler.cs
index 54ebc034c..cecb249a2 100644
--- a/Arrowgene.Ddon.GameServer/Handler/WarpAreaWarpHandler.cs
+++ b/Arrowgene.Ddon.GameServer/Handler/WarpAreaWarpHandler.cs
@@ -42,7 +42,7 @@ public override void Handle(GameClient client, StructurePacket TimeSpan.FromSeconds(60)) {
res.Result = 1; // I guess
}
@@ -30,4 +30,4 @@ public override void Handle(GameClient client, StructurePacket p
}
client.LastWarpPointId = packet.Structure.DestPointId;
- client.LastWarpDateTime = DateTime.Now;
+ client.LastWarpDateTime = DateTime.UtcNow;
}
}
-}
\ No newline at end of file
+}
diff --git a/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs b/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs
index 6b3cd4953..92ebee144 100644
--- a/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs
+++ b/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs
@@ -195,7 +195,7 @@ public ErrorRes Accept(GameClient client)
return ErrorRes.Fail;
}
- TimeSpan invitationAge = DateTime.Now - invitation.Date;
+ TimeSpan invitationAge = DateTime.UtcNow - invitation.Date;
if (invitationAge > TimeSpan.FromSeconds(PartyManager.InvitationTimeoutSec))
{
Logger.Error(client, $"[PartyId:{Id}][Accept] invitation expired");
diff --git a/Arrowgene.Ddon.GameServer/Party/PartyManager.cs b/Arrowgene.Ddon.GameServer/Party/PartyManager.cs
index 9879325ea..58b9d47f0 100644
--- a/Arrowgene.Ddon.GameServer/Party/PartyManager.cs
+++ b/Arrowgene.Ddon.GameServer/Party/PartyManager.cs
@@ -37,7 +37,7 @@ public bool InviteParty(GameClient invitee, GameClient host, PartyGroup party)
invitation.Invitee = invitee;
invitation.Host = host;
invitation.Party = party;
- invitation.Date = DateTime.Now;
+ invitation.Date = DateTime.UtcNow;
if (!_invites.TryAdd(invitee, invitation))
{
Logger.Error(invitee, $"Already has pending invite)");
diff --git a/Arrowgene.Ddon.LoginServer/Handler/ClientLoginHandler.cs b/Arrowgene.Ddon.LoginServer/Handler/ClientLoginHandler.cs
index d322d1ac7..e1a8875a9 100644
--- a/Arrowgene.Ddon.LoginServer/Handler/ClientLoginHandler.cs
+++ b/Arrowgene.Ddon.LoginServer/Handler/ClientLoginHandler.cs
@@ -25,7 +25,7 @@ public ClientLoginHandler(DdonLoginServer server) : base(server)
public override void Handle(LoginClient client, StructurePacket packet)
{
- DateTime now = DateTime.Now;
+ DateTime now = DateTime.UtcNow;
client.SetChallengeCompleted(true);
string oneTimeToken = packet.Structure.OneTimeToken;
diff --git a/Arrowgene.Ddon.LoginServer/Handler/ClientPingHandler.cs b/Arrowgene.Ddon.LoginServer/Handler/ClientPingHandler.cs
index aa80978d4..5879012cb 100644
--- a/Arrowgene.Ddon.LoginServer/Handler/ClientPingHandler.cs
+++ b/Arrowgene.Ddon.LoginServer/Handler/ClientPingHandler.cs
@@ -20,7 +20,7 @@ public ClientPingHandler(DdonLoginServer server) : base(server)
public override void Handle(LoginClient client, IPacket packet)
{
- client.PingTime = DateTime.Now;
+ client.PingTime = DateTime.UtcNow;
ServerRes res = new ServerRes(PacketId.L2C_PING_RES);
client.Send(res);
}
diff --git a/Arrowgene.Ddon.Rpc/Command/ChatCommand.cs b/Arrowgene.Ddon.Rpc/Command/ChatCommand.cs
index b43649b35..40ef4b754 100644
--- a/Arrowgene.Ddon.Rpc/Command/ChatCommand.cs
+++ b/Arrowgene.Ddon.Rpc/Command/ChatCommand.cs
@@ -12,7 +12,7 @@ public class ChatCommand : IRpcCommand
public ChatCommand()
{
- _since = DateTime.MinValue;
+ _since = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
}
public ChatCommand(DateTime since)
diff --git a/Arrowgene.Ddon.Shared/Model/Character.cs b/Arrowgene.Ddon.Shared/Model/Character.cs
index 6735de11b..fa3ef9e24 100644
--- a/Arrowgene.Ddon.Shared/Model/Character.cs
+++ b/Arrowgene.Ddon.Shared/Model/Character.cs
@@ -11,7 +11,7 @@ public Character():base()
{
FirstName = string.Empty;
LastName = string.Empty;
- Created = DateTime.MinValue;
+ Created = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
PlayPointList = Enum.GetValues(typeof(JobId)).Cast().Select(job => new CDataJobPlayPoint()
{
Job = job,
diff --git a/Arrowgene.Ddon.Shared/Model/GameToken.cs b/Arrowgene.Ddon.Shared/Model/GameToken.cs
index ddc106cc1..4ce730bad 100644
--- a/Arrowgene.Ddon.Shared/Model/GameToken.cs
+++ b/Arrowgene.Ddon.Shared/Model/GameToken.cs
@@ -32,7 +32,7 @@ public static GameToken GenerateGameToken(int accountId, uint characterId)
GameToken token = new GameToken();
token.Token = sb.ToString();
- token.Created = DateTime.Now;
+ token.Created = DateTime.UtcNow;
token.AccountId = accountId;
token.CharacterId = characterId;
return token;
diff --git a/Arrowgene.Ddon.Test/Database/CharacterDatabaseTest.cs b/Arrowgene.Ddon.Test/Database/CharacterDatabaseTest.cs
index d2c8241b6..645bb583c 100644
--- a/Arrowgene.Ddon.Test/Database/CharacterDatabaseTest.cs
+++ b/Arrowgene.Ddon.Test/Database/CharacterDatabaseTest.cs
@@ -18,7 +18,7 @@ public void TestCharacterOperations()
{
DatabaseSetting setting = new DatabaseSetting();
setting.WipeOnStartup = true;
- IDatabase database = DdonDatabaseBuilder.BuildSqLite(setting.SqLiteFolder, "character.test", true);
+ IDatabase database = DdonDatabaseBuilder.BuildSqLite(setting.DatabaseFolder, true);
Account account = database.CreateAccount("test", "test", "test");
Assert.NotNull(account);
diff --git a/Arrowgene.Ddon.WebServer/AccountRoute.cs b/Arrowgene.Ddon.WebServer/AccountRoute.cs
index 00238b0eb..a7fb60753 100644
--- a/Arrowgene.Ddon.WebServer/AccountRoute.cs
+++ b/Arrowgene.Ddon.WebServer/AccountRoute.cs
@@ -107,7 +107,7 @@ private string CreateToken(string name, string password)
}
account.LoginToken = GameToken.GenerateLoginToken();
- account.LoginTokenCreated = DateTime.Now;
+ account.LoginTokenCreated = DateTime.UtcNow;
_database.UpdateAccount(account);
return account.LoginToken;
}
diff --git a/Arrowgene.Ddon.config.local_dev.json b/Arrowgene.Ddon.config.local_dev.json
index 631525115..995d6be20 100644
--- a/Arrowgene.Ddon.config.local_dev.json
+++ b/Arrowgene.Ddon.config.local_dev.json
@@ -147,15 +147,5 @@
"UrlShopStoneLimit": "http:\/\/localhost:52099\/shop\/ingame\/stone\/limit",
"UrlSupportIndex": "http:\/\/localhost:52099\/sp_ingame\/support\/index.html"
},
- "DatabaseSetting": {
- "Type": 0,
- "SqLiteFolder": "\/var\/ddon\/server\/Files\/Database",
- "Host": "localhost",
- "Port": 3306,
- "User": "",
- "Password": "",
- "Database": "Ddon",
- "WipeOnStartup": false
- },
"AssetPath": "\/var\/ddon\/server\/Files\/Assets"
}
\ No newline at end of file
diff --git a/Arrowgene.Ddon.config.mariadb.local_dev.json b/Arrowgene.Ddon.config.mariadb.local_dev.json
new file mode 100644
index 000000000..995d6be20
--- /dev/null
+++ b/Arrowgene.Ddon.config.mariadb.local_dev.json
@@ -0,0 +1,151 @@
+{
+ "WebServerSetting": {
+ "PublicWebEndPoint": {
+ "IpAddress": "0.0.0.0",
+ "Port": 52099,
+ "IsHttps": false,
+ "HttpsCertPath": "",
+ "HttpsCertPw": "",
+ "SslProtocols": 0,
+ "DomainName": ""
+ },
+ "WebSetting": {
+ "ServerHeader": "",
+ "WebEndpoints": [
+ {
+ "IpAddress": "0.0.0.0",
+ "Port": 52099,
+ "IsHttps": false,
+ "HttpsCertPath": "",
+ "HttpsCertPw": "",
+ "SslProtocols": 0,
+ "DomainName": ""
+ }
+ ],
+ "WebFolder": "\/var\/ddon\/server\/Files\/www"
+ }
+ },
+ "GameServerSetting": {
+ "ServerSetting": {
+ "Id": 10,
+ "Name": "Game",
+ "ListenIpAddress": "0.0.0.0",
+ "ServerPort": 52000,
+ "LogLevel": 0,
+ "LogUnknownPackets": true,
+ "LogOutgoingPackets": true,
+ "LogOutgoingPacketPayload": false,
+ "LogIncomingPackets": true,
+ "LogIncomingPacketPayload": false,
+ "LogIncomingPacketStructure": false,
+ "LogOutgoingPacketStructure": false,
+ "ServerSocketSettings": {
+ "Identity": "Game",
+ "MaxConnections": 100,
+ "NumSimultaneouslyWriteOperations": 100,
+ "BufferSize": 2000,
+ "Retries": 10,
+ "MaxUnitOfOrder": 1,
+ "MaxSimultaneousSendsPerClient": 1,
+ "SocketTimeoutSeconds": -1,
+ "SocketSettings": {
+ "Backlog": 5,
+ "DontFragment": true,
+ "DualMode": false,
+ "ExclusiveAddressUse": false,
+ "NoDelay": false,
+ "UseOnlyOverlappedIo": false,
+ "ReceiveBufferSize": 8192,
+ "ReceiveTimeout": 0,
+ "SendBufferSize": 8192,
+ "SendTimeout": 0,
+ "Ttl": 32,
+ "LingerEnabled": false,
+ "LingerTime": 30,
+ "SocketOptions": [
+ {
+ "Level": "Socket",
+ "Name": 4,
+ "Value": false
+ }
+ ]
+ }
+ }
+ }
+ },
+ "LoginServerSetting": {
+ "ServerSetting": {
+ "Id": 1,
+ "Name": "Login",
+ "ListenIpAddress": "0.0.0.0",
+ "ServerPort": 52100,
+ "LogLevel": 0,
+ "LogUnknownPackets": true,
+ "LogOutgoingPackets": true,
+ "LogOutgoingPacketPayload": false,
+ "LogIncomingPackets": true,
+ "LogIncomingPacketPayload": false,
+ "LogIncomingPacketStructure": false,
+ "LogOutgoingPacketStructure": false,
+ "ServerSocketSettings": {
+ "Identity": "Login",
+ "MaxConnections": 100,
+ "NumSimultaneouslyWriteOperations": 100,
+ "BufferSize": 2000,
+ "Retries": 10,
+ "MaxUnitOfOrder": 1,
+ "MaxSimultaneousSendsPerClient": 1,
+ "SocketTimeoutSeconds": -1,
+ "SocketSettings": {
+ "Backlog": 5,
+ "DontFragment": true,
+ "DualMode": false,
+ "ExclusiveAddressUse": false,
+ "NoDelay": false,
+ "UseOnlyOverlappedIo": false,
+ "ReceiveBufferSize": 8192,
+ "ReceiveTimeout": 0,
+ "SendBufferSize": 8192,
+ "SendTimeout": 0,
+ "Ttl": 32,
+ "LingerEnabled": false,
+ "LingerTime": 30,
+ "SocketOptions": [
+ {
+ "Level": "Socket",
+ "Name": 4,
+ "Value": false
+ }
+ ]
+ }
+ }
+ },
+ "AccountRequired": false,
+ "JobLevelMax": 65,
+ "ClanMemberMax": 100,
+ "CharacterNumMax": 4,
+ "EnableVisualEquip": true,
+ "FriendListMax": 200,
+ "NoOperationTimeOutTime": 14400,
+ "UrlApiA": "http:\/\/localhost:52099\/link\/api",
+ "UrlApiB": "http:\/\/localhost:52099\/link\/api",
+ "UrlCampaign": "http:\/\/localhost:52099\/sp_ingame\/campaign\/bnr\/slide.html",
+ "UrlCampaignBanner": "http:\/\/localhost:52099\/sp_ingame\/campaign\/bnr\/bnr01.html?",
+ "UrlChargeA": "http:\/\/localhost:52099\/sp_ingame\/charge\/",
+ "UrlChargeB": "http:\/\/localhost:52099\/sp_ingame\/charge\/",
+ "UrlChargeCallback": "http:\/\/localhost:52099\/opening\/entry\/ddo\/cog_callback\/charge",
+ "UrlCompanionImage": "http:\/\/localhost:52099\/",
+ "UrlIndex": "http:\/\/localhost:52099\/sp_ingame\/link\/index.html",
+ "UrlManual": "http:\/\/localhost:52099\/manual_nfb\/",
+ "UrlPhotoupAuthorize": "http:\/\/localhost:52099\/api\/photoup\/authorize",
+ "UrlSample10": "http:\/\/sample10.html",
+ "UrlSample9": "http:\/\/sample09.html",
+ "UrlShopAttention": "http:\/\/localhost:52099\/shop\/ingame\/attention?",
+ "UrlShopCounterA": "http:\/\/localhost:52099\/shop\/ingame\/counter?",
+ "UrlShopCounterB": "http:\/\/localhost:52099\/shop\/ingame\/counter?",
+ "UrlShopDetail": "http:\/\/localhost:52099\/shop\/ingame\/stone\/detail",
+ "UrlShopStoneLimit": "http:\/\/localhost:52099\/shop\/ingame\/stone\/limit",
+ "UrlSupportIndex": "http:\/\/localhost:52099\/sp_ingame\/support\/index.html"
+ },
+ "AssetPath": "\/var\/ddon\/server\/Files\/Assets"
+}
\ No newline at end of file
diff --git a/Arrowgene.Ddon.config.psql.local_dev.json b/Arrowgene.Ddon.config.psql.local_dev.json
new file mode 100644
index 000000000..995d6be20
--- /dev/null
+++ b/Arrowgene.Ddon.config.psql.local_dev.json
@@ -0,0 +1,151 @@
+{
+ "WebServerSetting": {
+ "PublicWebEndPoint": {
+ "IpAddress": "0.0.0.0",
+ "Port": 52099,
+ "IsHttps": false,
+ "HttpsCertPath": "",
+ "HttpsCertPw": "",
+ "SslProtocols": 0,
+ "DomainName": ""
+ },
+ "WebSetting": {
+ "ServerHeader": "",
+ "WebEndpoints": [
+ {
+ "IpAddress": "0.0.0.0",
+ "Port": 52099,
+ "IsHttps": false,
+ "HttpsCertPath": "",
+ "HttpsCertPw": "",
+ "SslProtocols": 0,
+ "DomainName": ""
+ }
+ ],
+ "WebFolder": "\/var\/ddon\/server\/Files\/www"
+ }
+ },
+ "GameServerSetting": {
+ "ServerSetting": {
+ "Id": 10,
+ "Name": "Game",
+ "ListenIpAddress": "0.0.0.0",
+ "ServerPort": 52000,
+ "LogLevel": 0,
+ "LogUnknownPackets": true,
+ "LogOutgoingPackets": true,
+ "LogOutgoingPacketPayload": false,
+ "LogIncomingPackets": true,
+ "LogIncomingPacketPayload": false,
+ "LogIncomingPacketStructure": false,
+ "LogOutgoingPacketStructure": false,
+ "ServerSocketSettings": {
+ "Identity": "Game",
+ "MaxConnections": 100,
+ "NumSimultaneouslyWriteOperations": 100,
+ "BufferSize": 2000,
+ "Retries": 10,
+ "MaxUnitOfOrder": 1,
+ "MaxSimultaneousSendsPerClient": 1,
+ "SocketTimeoutSeconds": -1,
+ "SocketSettings": {
+ "Backlog": 5,
+ "DontFragment": true,
+ "DualMode": false,
+ "ExclusiveAddressUse": false,
+ "NoDelay": false,
+ "UseOnlyOverlappedIo": false,
+ "ReceiveBufferSize": 8192,
+ "ReceiveTimeout": 0,
+ "SendBufferSize": 8192,
+ "SendTimeout": 0,
+ "Ttl": 32,
+ "LingerEnabled": false,
+ "LingerTime": 30,
+ "SocketOptions": [
+ {
+ "Level": "Socket",
+ "Name": 4,
+ "Value": false
+ }
+ ]
+ }
+ }
+ }
+ },
+ "LoginServerSetting": {
+ "ServerSetting": {
+ "Id": 1,
+ "Name": "Login",
+ "ListenIpAddress": "0.0.0.0",
+ "ServerPort": 52100,
+ "LogLevel": 0,
+ "LogUnknownPackets": true,
+ "LogOutgoingPackets": true,
+ "LogOutgoingPacketPayload": false,
+ "LogIncomingPackets": true,
+ "LogIncomingPacketPayload": false,
+ "LogIncomingPacketStructure": false,
+ "LogOutgoingPacketStructure": false,
+ "ServerSocketSettings": {
+ "Identity": "Login",
+ "MaxConnections": 100,
+ "NumSimultaneouslyWriteOperations": 100,
+ "BufferSize": 2000,
+ "Retries": 10,
+ "MaxUnitOfOrder": 1,
+ "MaxSimultaneousSendsPerClient": 1,
+ "SocketTimeoutSeconds": -1,
+ "SocketSettings": {
+ "Backlog": 5,
+ "DontFragment": true,
+ "DualMode": false,
+ "ExclusiveAddressUse": false,
+ "NoDelay": false,
+ "UseOnlyOverlappedIo": false,
+ "ReceiveBufferSize": 8192,
+ "ReceiveTimeout": 0,
+ "SendBufferSize": 8192,
+ "SendTimeout": 0,
+ "Ttl": 32,
+ "LingerEnabled": false,
+ "LingerTime": 30,
+ "SocketOptions": [
+ {
+ "Level": "Socket",
+ "Name": 4,
+ "Value": false
+ }
+ ]
+ }
+ }
+ },
+ "AccountRequired": false,
+ "JobLevelMax": 65,
+ "ClanMemberMax": 100,
+ "CharacterNumMax": 4,
+ "EnableVisualEquip": true,
+ "FriendListMax": 200,
+ "NoOperationTimeOutTime": 14400,
+ "UrlApiA": "http:\/\/localhost:52099\/link\/api",
+ "UrlApiB": "http:\/\/localhost:52099\/link\/api",
+ "UrlCampaign": "http:\/\/localhost:52099\/sp_ingame\/campaign\/bnr\/slide.html",
+ "UrlCampaignBanner": "http:\/\/localhost:52099\/sp_ingame\/campaign\/bnr\/bnr01.html?",
+ "UrlChargeA": "http:\/\/localhost:52099\/sp_ingame\/charge\/",
+ "UrlChargeB": "http:\/\/localhost:52099\/sp_ingame\/charge\/",
+ "UrlChargeCallback": "http:\/\/localhost:52099\/opening\/entry\/ddo\/cog_callback\/charge",
+ "UrlCompanionImage": "http:\/\/localhost:52099\/",
+ "UrlIndex": "http:\/\/localhost:52099\/sp_ingame\/link\/index.html",
+ "UrlManual": "http:\/\/localhost:52099\/manual_nfb\/",
+ "UrlPhotoupAuthorize": "http:\/\/localhost:52099\/api\/photoup\/authorize",
+ "UrlSample10": "http:\/\/sample10.html",
+ "UrlSample9": "http:\/\/sample09.html",
+ "UrlShopAttention": "http:\/\/localhost:52099\/shop\/ingame\/attention?",
+ "UrlShopCounterA": "http:\/\/localhost:52099\/shop\/ingame\/counter?",
+ "UrlShopCounterB": "http:\/\/localhost:52099\/shop\/ingame\/counter?",
+ "UrlShopDetail": "http:\/\/localhost:52099\/shop\/ingame\/stone\/detail",
+ "UrlShopStoneLimit": "http:\/\/localhost:52099\/shop\/ingame\/stone\/limit",
+ "UrlSupportIndex": "http:\/\/localhost:52099\/sp_ingame\/support\/index.html"
+ },
+ "AssetPath": "\/var\/ddon\/server\/Files\/Assets"
+}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 2adf609b8..68b6c5118 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,7 +2,9 @@ FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
WORKDIR /App
COPY . ./
-RUN dotnet publish Arrowgene.Ddon.Cli/Arrowgene.Ddon.Cli.csproj /p:Version=1.0.0.0 -p:PublishReadyToRun=true /p:DebugType=None /p:DebugSymbols=false -r linux-x64 --self-contained false -c Release -o out
+# Runtime can be e.g. linux-x64 or linux-arm64, see officially supported Runtime Identifiers
+ARG RUNTIME
+RUN dotnet publish Arrowgene.Ddon.Cli/Arrowgene.Ddon.Cli.csproj /p:Version=1.0.0.0 -p:PublishReadyToRun=true /p:DebugType=None /p:DebugSymbols=false -r ${RUNTIME:-linux-x64} --self-contained false -c Release -o out
FROM mcr.microsoft.com/dotnet/aspnet:6.0
#RUN apt-get update && apt-get install -y apt-transport-https && rm -rf /var/lib/apt/lists/*
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 000000000..408cf0cab
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,188 @@
+:toc:
+:toclevels: 1
+:toc-placement!:
+
+= Dragons Dogma Online - Server
+
+image::https://github.com/sebastian-heinz/Arrowgene.DragonsDogmaOnline/actions/workflows/build.yaml/badge.svg[]
+
+Server Emulator for the Game Dragons Dogma Online.
+
+'''
+
+toc::[]
+
+'''
+
+== Disclaimer
+The project is intended for educational purpose only.
+
+== Developer Setup
+
+. Clone the repository
+.. `git clone https://github.com/sebastian-heinz/Arrowgene.DragonsDogmaOnline.git`
+. Install .NET 6.0 SDK or later https://dotnet.microsoft.com/download
+. Use your IDE of choice
+.. *Visual Studio*
+... Open the `DragonsDogmaOnline.sln`-file
+... Note: Minimum version of "Visual Studio 2022" or later.
+.. *VS Code*
+... Download IDE: https://code.visualstudio.com/download
+... C# Plugin: https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp
+... Open the Project Folder: `\Arrowgene.DragonsDogmaOnline`
+.. *IntelliJ Rider*
+... https://www.jetbrains.com/rider/
+... Note: Minimum version of "IntelliJ Rider 2021.3" or later.
+... Open the `DragonsDogmaOnline.sln`-file
+. Debug the Project
+.. Run the `Ddon.Cli`-Project with arguments `server start`
+
+== Deployment
+
+The application (server) requires the (ASP).NET runtime 6 to function. Alternatively the SDK also works and is required when building from source.
+
+On a high level, a total of four components are required to successfully connect a client:
+
+. Web server
+. Login server
+. Game server
+. Database
+
+All of these components are provided & automatically started up in the default developer setup outlined above.
+
+As the developer setup relies on an embedded https://www.sqlite.org/index.html[SQLite] database, this might not suite all production needs (e.g. the embedded SQLite setup currently lacks ARM binaries). Thus, two more relational databases are supported:
+
+. https://mariadb.org/[MariaDB]
+. https://www.postgresql.org/[PostgreSQL]
+
+=== Container setup
+
+It is also possible to run a containerized setup.
+A xref:./Dockerfile[Dockerfile] is provided which encapsulates everything required to build & publish the server from source. Three separate docker-compose files are provided depending on the desired database. None of these actually reference any image and instead rely on ad-hoc source code builds as the image is currently not publicly available.
+
+. xref:./docker-compose.yml[SQLite-enabled docker-compose]
+.. `docker-compose up`
+. xref:./docker-compose.mariadb.yml[MariaDB-enabled docker-compose]
+.. `docker-compose -f docker-compose.mariadb.yml up`
+. xref:./docker-compose.psql.yml[PostgreSQL-enabled docker-compose]
+.. `docker-compose -f docker-compose.psql.yml up`
+
+==== Useful run commands
+
+* Force rebuild `docker-compose up --build`
+* Clean up & delete persistent volumes (data) as well `docker-compose down -v`
+* Only build the image `docker-compose build`
+* Build for other runtimes, e.g. arm64 `RUNTIME=linux-arm64 docker-compose build`
+* All-in-one clean & rebuild `docker-compose down -v && docker-compose up --build`
+* All-in-one clean & rebuild & arm64 `RUNTIME=linux-arm64 docker-compose down -v && docker-compose up --build`
+
+== Server
+With default configuration the server will listen on following ports:
+
+[source]
+----
+52099 - http/download
+52000 - tcp/gameserver
+52100 - tcp/loginserver
+----
+
+ensure that no other local services listen on these ports.
+
+== Client
+Launch the client with the following args:
+
+`"DDO.exe" "addr=localhost port=52100 token=00000000000000000000 DL=http://127.0.0.1:52099/win/ LVer=03.04.003.20181115.0 RVer=3040008"`
+
+== Progress
+
+=== Login Server
+* [x] Account
+* [x] Character Creation
+
+=== Game Server
+==== Party Management (Party List)
+* [ ] Party Members
+** [x] View Arisen Profile
+** [ ] Send Tell
+** [ ] Send Friend Request
+** [ ] View Status and Equipment
+** [x] Promote to Party Leader
+** [x] Kick from Party
+** [ ] Invite to Group Chat
+** [x] Disband Party
+** [ ] Invite to Entryboard
+** [ ] Follow with Autorun
+** [ ] Cancel Party Invite
+** [ ] Decline Party Invite
+** [ ] View Party List
+** [x] Leave
+** [ ] Invite Directly to Clan
+* [ ] Main Pawns
+** [ ] View Pawn Profile
+** [x] Invite to Party
+** [x] Kick from Party
+** [ ] View Status and Equipment
+* [ ] Support Pawns
+* [ ] Party Search
+** [ ] Search
+** [ ] Simple Request
+* [ ] Player Search
+** [ ] View Arisen Profile
+** [x] Invite to Party
+** [ ] Send Tell
+** [ ] Send Friend Request
+** [ ] Invite to Group Chat
+** [ ] Invite to Entryboard
+** [x] Search
+
+== Guidelines
+=== Git
+==== Workflow
+The work on this project should happen via `feature-branches`
+
+Feature branches (or sometimes called topic branches) are used to develop new features for the upcoming or a distant future release.
+When starting development of a feature, the target release in which this feature will be incorporated may well be unknown at that point.
+The essence of a feature branch is that it exists as long as the feature is in development,
+but will eventually be merged back into develop (to definitely add the new feature to the upcoming release) or discarded (in case of a disappointing experiment).
+
+1. Create a new `feature/feature-name` or `fix/bug-fix-name` branch from master
+2. Push all your changes to that branch
+3. Create a Pull Request to merge that branch into `master`
+
+=== Best Practise
+- Do not use Console.WriteLine etc., use the specially designed logger.
+- Own the Code: extract solutions, discard libraries.
+- Annotate functions with documentation comments (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments).
+
+=== C# Coding Standards and Naming Conventions
+
+[options="header"]
+|=========================================================
+| Object Name | Notation | Char Mask | Underscores
+| Class name | PascalCase | [A-z][0-9] | No
+| Constructor name | PascalCase | [A-z][0-9] | No
+| Method name | PascalCase | [A-z][0-9] | No
+| Method arguments | camelCase | [A-z][0-9] | No
+| Local variables | camelCase | [A-z][0-9] | No
+| Constants name | PascalCase | [A-z][0-9] | No
+| Field name | _camelCase | [A-z][0-9] | Yes
+| Properties name | PascalCase | [A-z][0-9] | No
+| Delegate name | PascalCase | [A-z] | No
+| Enum type name | PascalCase | [A-z] | No
+|=========================================================
+
+== Attribution
+=== Contributors / Making It Happening
+Let me preface with that this work could not exist without the excellent work of various individuals
+- Ando - Reverse Engineering & Tooling (Session Splitter, Camellia Key Cracker)
+- David - Reverse Engineering (unpacking PC Executable, defeating Anti Debug and CRC checks)
+- The White Dragon Temple
+- Nothilvien [@sebastian-heinz](https://github.com/sebastian-heinz) - Reverse Engineering & Server Code
+
+(if you have been forgotten please reach out)
+
+=== 3rd Parties and Libraries
+- System.Data.SQLite (https://system.data.sqlite.org/)
+- KaitaiStruct.Runtime.Csharp (https://kaitai.io/)
+- Arrowgene.Networking (https://github.com/sebastian-heinz/Arrowgene.Networking)
+- .NET Standard (https://github.com/dotnet/standard)
diff --git a/README.md b/README.md
deleted file mode 100644
index e552198ce..000000000
--- a/README.md
+++ /dev/null
@@ -1,159 +0,0 @@
-Dragons Dogma Online - Server
-===
-Server Emulator for the Game Dragons Dogma Online.
-
-## Table of contents
-- [Disclaimer](#disclaimer)
-- [Setup](#setup)
- - [Visual Studio](#visual-studio)
- - [VS Code](#vs-code)
- - [IntelliJ Rider](#intellij-rider)
-- [Server](#server)
-- [Client](#client)
-- [Progress](#progress)
-- [Guidelines](#guidelines)
-- [Attribution](#attribution)
- - [Contributers](#contributers)
- - [3rd Parties and Libraries](#3rd-parties-and-libraries)
-
-# Disclaimer
-The project is intended for educational purpose only.
-
-# Setup
-## 1) Clone the repository
-`git clone https://github.com/sebastian-heinz/ddo-server.git`
-
-## 2) Install .Net 6.0 SDK or later
-https://dotnet.microsoft.com/download
-
-## 3) Use your IDE of choice:
-
-## 3.1) Visual Studio
-### Notice:
-Minimum version of "Visual Studio 2022" or later.
-
-### Open Project:
-Open the `DragonsDogmaOnline.sln`-file
-
-## 3.2) VS Code
-Download IDE: https://code.visualstudio.com/download
-C# Plugin: https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp
-
-### Open Project:
-Open the Project Folder:
-`\Arrowgene.DragonsDogmaOnline`
-
-## 3.3) IntelliJ Rider
-https://www.jetbrains.com/rider/
-### Notice:
-Minimum version of "IntelliJ Rider 2021.3" or later.
-
-### Open Project:
-Open the `DragonsDogmaOnline.sln`-file
-
-## 4) Debug the Project
-Run the `Ddon.Cli`-Project
-
-# Server
-With default configuration the server will listen on following ports:
-```
-52099 - http/download
-52000 - tcp/gameserver
-52100 - tcp/loginserver
-```
-ensure that no other local services listen on these ports.
-
-# Client
-Launch the client with the following args:
-`"DDO.exe" "addr=localhost port=52100 token=00000000000000000000 DL=http://127.0.0.1:52099/win/ LVer=03.04.003.20181115.0 RVer=3040008"`
-
-# Progress
-
-## Login Server
-- [x] Account
-- [x] Character Creation
-
-## Game Server
-### Party Management (Party List)
-- [ ] Party Members
- - [x] View Arisen Profile
- - [ ] Send Tell
- - [ ] Send Friend Request
- - [ ] View Status and Equipment
- - [x] Promote to Party Leader
- - [x] Kick from Party
- - [ ] Invite to Group Chat
- - [x] Disband Party
- - [ ] Invite to Entryboard
- - [ ] Follow with Autorun
- - [ ] Cancel Party Invite
- - [ ] Decline Party Invite
- - [ ] View Party List
- - [x] Leave
- - [ ] Invite Directly to Clan
-- [ ] Main Pawns
- - [ ] View Pawn Profile
- - [x] Invite to Party
- - [x] Kick from Party
- - [ ] View Status and Equipment
-- [ ] Support Pawns
-- [ ] Party Search
- - [ ] Search
- - [ ] Simple Request
-- [ ] Player Search
- - [ ] View Arisen Profile
- - [x] Invite to Party
- - [ ] Send Tell
- - [ ] Send Friend Request
- - [ ] Invite to Group Chat
- - [ ] Invite to Entryboard
- - [x] Search
-
-# Guidelines
-## Git
-### Workflow
-The work on this project should happen via `feature-branches`
-
-Feature branches (or sometimes called topic branches) are used to develop new features for the upcoming or a distant future release.
-When starting development of a feature, the target release in which this feature will be incorporated may well be unknown at that point.
-The essence of a feature branch is that it exists as long as the feature is in development,
-but will eventually be merged back into develop (to definitely add the new feature to the upcoming release) or discarded (in case of a disappointing experiment).
-
-1) Create a new `feature/feature-name` or `fix/bug-fix-name` branch from master
-2) Push all your changes to that branch
-3) Create a Pull Request to merge that branch into `master`
-
-## Best Practise
-- Do not use Console.WriteLine etc, use the specially designed logger.
-- Own the Code: extract solutions, discard libraries.
-- Annotate functions with documentation comments (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments).
-
-## C# Coding Standards and Naming Conventions
-| Object Name | Notation | Char Mask | Underscores |
-|:--------------------------|:------------|:-------------------|:------------|
-| Class name | PascalCase | [A-z][0-9] | No |
-| Constructor name | PascalCase | [A-z][0-9] | No |
-| Method name | PascalCase | [A-z][0-9] | No |
-| Method arguments | camelCase | [A-z][0-9] | No |
-| Local variables | camelCase | [A-z][0-9] | No |
-| Constants name | PascalCase | [A-z][0-9] | No |
-| Field name | _camelCase | [A-z][0-9] | Yes |
-| Properties name | PascalCase | [A-z][0-9] | No |
-| Delegate name | PascalCase | [A-z] | No |
-| Enum type name | PascalCase | [A-z] | No |
-
-# Attribution
-## Contributors / Making It Happening
-Let me preface with that this work could not exist without the excellent work of various individuals
-- Ando - Reverse Engineering & Tooling (Session Splitter, Camellia Key Cracker)
-- David - Reverse Engineering (unpacking PC Executable, defeating Anti Debug and CRC checks)
-- The White Dragon Temple
-- Nothilvien [@sebastian-heinz](https://github.com/sebastian-heinz) - Reverse Engineering & Server Code
-
-(if you have been forgotten please reach out)
-
-## 3rd Parties and Libraries
-- System.Data.SQLite (https://system.data.sqlite.org/)
-- KaitaiStruct.Runtime.Csharp (https://kaitai.io/)
-- Arrowgene.Networking (https://github.com/sebastian-heinz/Arrowgene.Networking)
-- .NET Standard (https://github.com/dotnet/standard)
diff --git a/deploy/Arrowgene.Ddon.config.json b/deploy/Arrowgene.Ddon.config.json
index 354d9b4c5..fc7012078 100644
--- a/deploy/Arrowgene.Ddon.config.json
+++ b/deploy/Arrowgene.Ddon.config.json
@@ -133,8 +133,8 @@
"UrlSupportIndex": "http:\/\/localhost:52099\/sp_ingame\/support\/index.html"
},
"DatabaseSetting": {
- "Type": 0,
- "SqLiteFolder": "\/var\/ddon\/server\/Files\/Database",
+ "Type": "sqlite",
+ "DatabaseFolder": "\/var\/ddon\/server\/Files\/Database",
"Host": "localhost",
"Port": 3306,
"User": "",
diff --git a/deploy/mariadb/docker-compose.yml b/deploy/mariadb/docker-compose.yml
new file mode 100644
index 000000000..a2648be4a
--- /dev/null
+++ b/deploy/mariadb/docker-compose.yml
@@ -0,0 +1,28 @@
+version: '3.8'
+
+volumes:
+ ddon-mariadb-volume:
+
+services:
+ mariadb:
+ container_name: ddon-mariadb
+ image: mariadb:11
+ ports:
+ - "3306:3306"
+ restart: no
+ volumes:
+ - ddon-mariadb-volume:/var/lib/mysql
+# - $PWD/schema_mariadb.sql:/docker-entrypoint-initdb.d/schema_mariadb.sql:ro
+ - $PWD/mariadb.cnf:/etc/mysql/mariadb.conf.d/mariadb.cnf:ro
+ environment:
+ - MARIADB_USER=admin
+ - MARIADB_ROOT_PASSWORD=admin
+ - MARIADB_PASSWORD=admin
+ - MARIADB_DATABASE=mariadb
+ - LANG=C.UTF_8
+ command: ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"]
+ healthcheck:
+ test: ["CMD-SHELL", "mariadb --defaults-file=/var/lib/mysql/.my-healthcheck.cnf --skip-column-names -h localhost -e \"select 1 from information_schema.ENGINES WHERE ENGINE='InnoDB' AND support in ('YES', 'DEFAULT', 'ENABLED')\""]
+ interval: 10s
+ timeout: 1s
+ retries: 3
diff --git a/deploy/mariadb/mariadb.cnf b/deploy/mariadb/mariadb.cnf
new file mode 100644
index 000000000..aefa24bc5
--- /dev/null
+++ b/deploy/mariadb/mariadb.cnf
@@ -0,0 +1,7 @@
+[mysqld]
+sql_mode="STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,ANSI_QUOTES"
+
+[mariadb]
+log_warnings=3
+general_log
+general_log_file=/tmp/queries.log
diff --git a/deploy/mariadb/schema_mariadb.sql b/deploy/mariadb/schema_mariadb.sql
new file mode 100644
index 000000000..578efda72
--- /dev/null
+++ b/deploy/mariadb/schema_mariadb.sql
@@ -0,0 +1,420 @@
+CREATE TABLE IF NOT EXISTS setting
+(
+ "key" VARCHAR(32) NOT NULL,
+ "value" TEXT NOT NULL,
+ PRIMARY KEY ("key")
+);
+
+CREATE TABLE IF NOT EXISTS account
+(
+ "id" INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL,
+ "name" TEXT NOT NULL,
+ "normal_name" TEXT NOT NULL,
+ "hash" TEXT NOT NULL,
+ "mail" TEXT NOT NULL,
+ "mail_verified" BOOLEAN NOT NULL,
+ "mail_verified_at" DATETIME DEFAULT NULL,
+ "mail_token" TEXT DEFAULT NULL,
+ "password_token" TEXT DEFAULT NULL,
+ "login_token" TEXT DEFAULT NULL,
+ "login_token_created" DATETIME DEFAULT NULL,
+ "state" INTEGER NOT NULL,
+ "last_login" DATETIME DEFAULT NULL,
+ "created" DATETIME NOT NULL,
+ CONSTRAINT uq_account_name UNIQUE ("name"),
+ CONSTRAINT uq_account_normal_name UNIQUE ("normal_name"),
+ CONSTRAINT uq_account_login_token UNIQUE ("login_token"),
+ CONSTRAINT uq_account_mail UNIQUE ("mail")
+);
+
+CREATE TABLE IF NOT EXISTS ddon_character_common
+(
+ "character_common_id" INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "hide_equip_head" BOOLEAN NOT NULL,
+ "hide_equip_lantern" BOOLEAN NOT NULL,
+ "jewelry_slot_num" SMALLINT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS ddon_character
+(
+ "character_id" INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL,
+ "character_common_id" INTEGER NOT NULL,
+ "account_id" INTEGER NOT NULL,
+ "version" INTEGER NOT NULL,
+ "first_name" TEXT NOT NULL,
+ "last_name" TEXT NOT NULL,
+ "created" DATETIME NOT NULL,
+ "my_pawn_slot_num" SMALLINT NOT NULL,
+ "rental_pawn_slot_num" SMALLINT NOT NULL,
+ "hide_equip_head_pawn" BOOLEAN NOT NULL,
+ "hide_equip_lantern_pawn" BOOLEAN NOT NULL,
+ "arisen_profile_share_range" SMALLINT NOT NULL,
+ CONSTRAINT fk_character_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE,
+ CONSTRAINT fk_character_account_id FOREIGN KEY ("account_id") REFERENCES account ("id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_pawn
+(
+ "pawn_id" INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL,
+ "character_common_id" INTEGER NOT NULL,
+ "character_id" INTEGER NOT NULL,
+ "name" TEXT NOT NULL,
+ "hm_type" SMALLINT NOT NULL,
+ "pawn_type" SMALLINT NOT NULL,
+ CONSTRAINT fk_pawn_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE,
+ CONSTRAINT fk_character_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_edit_info
+(
+ "character_common_id" INTEGER PRIMARY KEY NOT NULL,
+ "sex" SMALLINT NOT NULL,
+ "voice" SMALLINT NOT NULL,
+ "voice_pitch" SMALLINT NOT NULL,
+ "personality" SMALLINT NOT NULL,
+ "speech_freq" SMALLINT NOT NULL,
+ "body_type" SMALLINT NOT NULL,
+ "hair" SMALLINT NOT NULL,
+ "beard" SMALLINT NOT NULL,
+ "makeup" SMALLINT NOT NULL,
+ "scar" SMALLINT NOT NULL,
+ "eye_preset_no" SMALLINT NOT NULL,
+ "nose_preset_no" SMALLINT NOT NULL,
+ "mouth_preset_no" SMALLINT NOT NULL,
+ "eyebrow_tex_no" SMALLINT NOT NULL,
+ "color_skin" SMALLINT NOT NULL,
+ "color_hair" SMALLINT NOT NULL,
+ "color_beard" SMALLINT NOT NULL,
+ "color_eyebrow" SMALLINT NOT NULL,
+ "color_r_eye" SMALLINT NOT NULL,
+ "color_l_eye" SMALLINT NOT NULL,
+ "color_makeup" SMALLINT NOT NULL,
+ "sokutobu" SMALLINT NOT NULL,
+ "hitai" SMALLINT NOT NULL,
+ "mimi_jyouge" SMALLINT NOT NULL,
+ "kannkaku" SMALLINT NOT NULL,
+ "mabisasi_jyouge" SMALLINT NOT NULL,
+ "hanakuchi_jyouge" SMALLINT NOT NULL,
+ "ago_saki_haba" SMALLINT NOT NULL,
+ "ago_zengo" SMALLINT NOT NULL,
+ "ago_saki_jyouge" SMALLINT NOT NULL,
+ "hitomi_ookisa" SMALLINT NOT NULL,
+ "me_ookisa" SMALLINT NOT NULL,
+ "me_kaiten" SMALLINT NOT NULL,
+ "mayu_kaiten" SMALLINT NOT NULL,
+ "mimi_ookisa" SMALLINT NOT NULL,
+ "mimi_muki" SMALLINT NOT NULL,
+ "elf_mimi" SMALLINT NOT NULL,
+ "miken_takasa" SMALLINT NOT NULL,
+ "miken_haba" SMALLINT NOT NULL,
+ "hohobone_ryou" SMALLINT NOT NULL,
+ "hohobone_jyouge" SMALLINT NOT NULL,
+ "hohoniku" SMALLINT NOT NULL,
+ "erahone_jyouge" SMALLINT NOT NULL,
+ "erahone_haba" SMALLINT NOT NULL,
+ "hana_jyouge" SMALLINT NOT NULL,
+ "hana_haba" SMALLINT NOT NULL,
+ "hana_takasa" SMALLINT NOT NULL,
+ "hana_kakudo" SMALLINT NOT NULL,
+ "kuchi_haba" SMALLINT NOT NULL,
+ "kuchi_atsusa" SMALLINT NOT NULL,
+ "eyebrow_uv_offset_x" SMALLINT NOT NULL,
+ "eyebrow_uv_offset_y" SMALLINT NOT NULL,
+ "wrinkle" SMALLINT NOT NULL,
+ "wrinkle_albedo_blend_rate" SMALLINT NOT NULL,
+ "wrinkle_detail_normal_power" SMALLINT NOT NULL,
+ "muscle_albedo_blend_rate" SMALLINT NOT NULL,
+ "muscle_detail_normal_power" SMALLINT NOT NULL,
+ "height" SMALLINT NOT NULL,
+ "head_size" SMALLINT NOT NULL,
+ "neck_offset" SMALLINT NOT NULL,
+ "neck_scale" SMALLINT NOT NULL,
+ "upper_body_scale_x" SMALLINT NOT NULL,
+ "belly_size" SMALLINT NOT NULL,
+ "teat_scale" SMALLINT NOT NULL,
+ "tekubi_size" SMALLINT NOT NULL,
+ "koshi_offset" SMALLINT NOT NULL,
+ "koshi_size" SMALLINT NOT NULL,
+ "ankle_offset" SMALLINT NOT NULL,
+ "fat" SMALLINT NOT NULL,
+ "muscle" SMALLINT NOT NULL,
+ "motion_filter" SMALLINT NOT NULL,
+ CONSTRAINT fk_edit_info_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_status_info
+(
+ "character_common_id" INTEGER PRIMARY KEY NOT NULL,
+ "hp" INTEGER NOT NULL,
+ "stamina" INTEGER NOT NULL,
+ "revive_point" SMALLINT NOT NULL,
+ "max_hp" INTEGER NOT NULL,
+ "max_stamina" INTEGER NOT NULL,
+ "white_hp" INTEGER NOT NULL,
+ "gain_hp" INTEGER NOT NULL,
+ "gain_stamina" INTEGER NOT NULL,
+ "gain_attack" INTEGER NOT NULL,
+ "gain_defense" INTEGER NOT NULL,
+ "gain_magic_attack" INTEGER NOT NULL,
+ "gain_magic_defense" INTEGER NOT NULL,
+ CONSTRAINT fk_status_info_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_character_matching_profile
+(
+ "character_id" INTEGER PRIMARY KEY NOT NULL,
+ "entry_job" SMALLINT NOT NULL,
+ "entry_job_level" INTEGER NOT NULL,
+ "current_job" SMALLINT NOT NULL,
+ "current_job_level" INTEGER NOT NULL,
+ objective_type1 INTEGER NOT NULL,
+ objective_type2 INTEGER NOT NULL,
+ "play_style" INTEGER NOT NULL,
+ "comment" TEXT NOT NULL,
+ "is_join_party" BOOLEAN NOT NULL,
+ CONSTRAINT fk_matching_profile_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_character_arisen_profile
+(
+ "character_id" INTEGER PRIMARY KEY NOT NULL,
+ "background_id" SMALLINT NOT NULL,
+ "title_uid" INTEGER NOT NULL,
+ "title_index" INTEGER NOT NULL,
+ "motion_id" SMALLINT NOT NULL,
+ "motion_frame_no" INTEGER NOT NULL,
+ CONSTRAINT fk_arisen_profile_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_character_job_data
+(
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "exp" INTEGER NOT NULL,
+ "job_point" INTEGER NOT NULL,
+ "lv" INTEGER NOT NULL,
+ "atk" SMALLINT NOT NULL,
+ "def" SMALLINT NOT NULL,
+ "m_atk" SMALLINT NOT NULL,
+ "m_def" SMALLINT NOT NULL,
+ "strength" SMALLINT NOT NULL,
+ "down_power" SMALLINT NOT NULL,
+ "shake_power" SMALLINT NOT NULL,
+ "stun_power" SMALLINT NOT NULL,
+ "consitution" SMALLINT NOT NULL,
+ "guts" SMALLINT NOT NULL,
+ "fire_resist" SMALLINT NOT NULL,
+ "ice_resist" SMALLINT NOT NULL,
+ "thunder_resist" SMALLINT NOT NULL,
+ "holy_resist" SMALLINT NOT NULL,
+ "dark_resist" SMALLINT NOT NULL,
+ "spread_resist" SMALLINT NOT NULL,
+ "freeze_resist" SMALLINT NOT NULL,
+ "shock_resist" SMALLINT NOT NULL,
+ "absorb_resist" SMALLINT NOT NULL,
+ "dark_elm_resist" SMALLINT NOT NULL,
+ "poison_resist" SMALLINT NOT NULL,
+ "slow_resist" SMALLINT NOT NULL,
+ "sleep_resist" SMALLINT NOT NULL,
+ "stun_resist" SMALLINT NOT NULL,
+ "wet_resist" SMALLINT NOT NULL,
+ "oil_resist" SMALLINT NOT NULL,
+ "seal_resist" SMALLINT NOT NULL,
+ "curse_resist" SMALLINT NOT NULL,
+ "soft_resist" SMALLINT NOT NULL,
+ "stone_resist" SMALLINT NOT NULL,
+ "gold_resist" SMALLINT NOT NULL,
+ "fire_reduce_resist" SMALLINT NOT NULL,
+ "ice_reduce_resist" SMALLINT NOT NULL,
+ "thunder_reduce_resist" SMALLINT NOT NULL,
+ "holy_reduce_resist" SMALLINT NOT NULL,
+ "dark_reduce_resist" SMALLINT NOT NULL,
+ "atk_down_resist" SMALLINT NOT NULL,
+ "def_down_resist" SMALLINT NOT NULL,
+ "m_atk_down_resist" SMALLINT NOT NULL,
+ "m_def_down_resist" SMALLINT NOT NULL,
+ CONSTRAINT pk_character_job_data PRIMARY KEY (character_common_id, job),
+ CONSTRAINT fk_character_job_data_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_storage
+(
+ "character_id" INTEGER NOT NULL,
+ "storage_type" SMALLINT NOT NULL,
+ "slot_max" SMALLINT NOT NULL,
+ "item_sort" BLOB NOT NULL,
+ CONSTRAINT pk_ddon_storage PRIMARY KEY (character_id, storage_type),
+ CONSTRAINT fk_storage_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_wallet_point
+(
+ "character_id" INTEGER NOT NULL,
+ "type" SMALLINT NOT NULL,
+ "value" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_wallet_point PRIMARY KEY (character_id, type),
+ CONSTRAINT fk_wallet_point_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_item
+(
+ -- See Item.cs, uid is at most of size 8.
+ "uid" VARCHAR(8) NOT NULL,
+ "item_id" INTEGER NOT NULL,
+ unk3 SMALLINT NOT NULL,
+ "color" SMALLINT NOT NULL,
+ "plus_value" SMALLINT NOT NULL,
+ PRIMARY KEY ("uid")
+);
+
+CREATE TABLE IF NOT EXISTS ddon_storage_item
+(
+ "item_uid" VARCHAR(8) NOT NULL,
+ "character_id" INTEGER NOT NULL,
+ "storage_type" SMALLINT NOT NULL,
+ "slot_no" SMALLINT NOT NULL,
+ "item_num" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_storage_item PRIMARY KEY (character_id, storage_type, slot_no),
+ CONSTRAINT fk_storage_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE,
+ CONSTRAINT fk_storage_item_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_equip_item
+(
+ "item_uid" VARCHAR(8) NOT NULL,
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "equip_type" SMALLINT NOT NULL,
+ "equip_slot" SMALLINT NOT NULL,
+ CONSTRAINT pk_ddon_equip_item PRIMARY KEY (character_common_id, job, equip_type, equip_slot),
+ CONSTRAINT fk_equip_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE,
+ CONSTRAINT fk_equip_item_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_equip_job_item
+(
+ "item_uid" VARCHAR(8) NOT NULL,
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "equip_slot" SMALLINT NOT NULL,
+ CONSTRAINT pk_ddon_equip_job_item PRIMARY KEY (character_common_id, job, equip_slot),
+ CONSTRAINT fk_equip_job_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE,
+ CONSTRAINT fk_equip_job_item_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_normal_skill_param
+(
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "skill_no" INTEGER NOT NULL,
+ "index" INTEGER NOT NULL,
+ "pre_skill_no" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_normal_skill_param PRIMARY KEY (character_common_id, job, skill_no),
+ CONSTRAINT fk_normal_skill_param_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_learned_custom_skill
+(
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "skill_id" INTEGER NOT NULL,
+ "skill_lv" SMALLINT NOT NULL,
+ PRIMARY KEY (character_common_id, job, skill_id),
+ CONSTRAINT fk_learned_custom_skill_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_equipped_custom_skill
+(
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "slot_no" SMALLINT NOT NULL,
+ "skill_id" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_equipped_custom_skill PRIMARY KEY (character_common_id, job, slot_no),
+ CONSTRAINT fk_equipped_custom_skill_character_common_id FOREIGN KEY (character_common_id, job, skill_id) REFERENCES ddon_learned_custom_skill (character_common_id, job, skill_id) ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_learned_ability
+(
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "ability_id" INTEGER NOT NULL,
+ "ability_lv" SMALLINT NOT NULL,
+ PRIMARY KEY (character_common_id, job, ability_id),
+ CONSTRAINT fk_learned_ability_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_equipped_ability
+(
+ "character_common_id" INTEGER NOT NULL,
+ "equipped_to_job" SMALLINT NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "slot_no" SMALLINT NOT NULL,
+ "ability_id" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_equipped_ability PRIMARY KEY (character_common_id, equipped_to_job, slot_no),
+ CONSTRAINT fk_equipped_ability_character_common_id FOREIGN KEY (character_common_id, job, ability_id) REFERENCES ddon_learned_ability (character_common_id, job, ability_id) ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_shortcut
+(
+ "character_id" INTEGER NOT NULL,
+ "page_no" INTEGER NOT NULL,
+ "button_no" INTEGER NOT NULL,
+ "shortcut_id" INTEGER NOT NULL,
+ u32_data INTEGER NOT NULL,
+ f32_data INTEGER NOT NULL,
+ "exex_type" SMALLINT NOT NULL,
+ CONSTRAINT pk_ddon_shortcut PRIMARY KEY (character_id, page_no, button_no),
+ CONSTRAINT fk_shortcut_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_communication_shortcut
+(
+ "character_id" INTEGER NOT NULL,
+ "page_no" INTEGER NOT NULL,
+ "button_no" INTEGER NOT NULL,
+ "type" SMALLINT NOT NULL,
+ "category" SMALLINT NOT NULL,
+ "id" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_communication_shortcut PRIMARY KEY (character_id, page_no, button_no),
+ CONSTRAINT fk_communication_shortcut_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_pawn_reaction
+(
+ "pawn_id" INTEGER NOT NULL,
+ "reaction_type" SMALLINT NOT NULL,
+ "motion_no" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_pawn_reaction PRIMARY KEY (pawn_id, reaction_type),
+ CONSTRAINT fk_pawn_reaction_pawn_id FOREIGN KEY ("pawn_id") REFERENCES ddon_pawn ("pawn_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_sp_skill
+(
+ "pawn_id" INTEGER NOT NULL,
+ "sp_skill_id" SMALLINT NOT NULL,
+ "sp_skill_lv" SMALLINT NOT NULL,
+ CONSTRAINT pk_ddon_sp_skill PRIMARY KEY ("pawn_id"),
+ CONSTRAINT fk_sp_skill_pawn_id FOREIGN KEY ("pawn_id") REFERENCES ddon_pawn ("pawn_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_game_token
+(
+ "account_id" INTEGER PRIMARY KEY NOT NULL,
+ "character_id" INTEGER NOT NULL,
+ "token" TEXT NOT NULL,
+ "created" DATETIME NOT NULL,
+ CONSTRAINT uq_game_token_token UNIQUE ("token"),
+ CONSTRAINT fk_game_token_account_id FOREIGN KEY ("account_id") REFERENCES account ("id"),
+ CONSTRAINT fk_game_token_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id")
+);
+
+CREATE TABLE IF NOT EXISTS ddon_connection
+(
+ "server_id" INTEGER NOT NULL,
+ "account_id" INTEGER NOT NULL,
+ "type" INTEGER NOT NULL,
+ "created" DATETIME NOT NULL,
+ CONSTRAINT uq_connection_server_id_account_id UNIQUE (server_id, account_id),
+ CONSTRAINT fk_connection_token_account_id FOREIGN KEY ("account_id") REFERENCES account ("id")
+);
diff --git a/deploy/postgresql/docker-compose.yml b/deploy/postgresql/docker-compose.yml
new file mode 100644
index 000000000..c0ef0635d
--- /dev/null
+++ b/deploy/postgresql/docker-compose.yml
@@ -0,0 +1,27 @@
+version: '3.8'
+
+volumes:
+ ddon-psql-data:
+
+services:
+ psql:
+ container_name: ddon-psql
+ image: postgres:16-alpine
+ ports:
+ - "5432:5432"
+ restart: no
+ volumes:
+ - ddon-psql-data:/var/lib/postgresql/data
+# - $PWD/schema_postgres.sql:/docker-entrypoint-initdb.d/schema_postgres.sql:ro
+ - $PWD/postgresql.conf:/etc/postgresql/postgresql.conf:ro
+ environment:
+ - POSTGRES_USER=root
+ - POSTGRES_PASSWORD=root
+ - POSTGRES_DB=postgres
+ - POSTGRES_INITDB_ARGS=--auth=scram-sha-256 --lc-numeric=en_US.UTF-8
+ command: postgres -c config_file=/etc/postgresql/postgresql.conf
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready", "-d", "postgres"]
+ interval: 10s
+ timeout: 1s
+ retries: 3
diff --git a/deploy/postgresql/postgresql.conf b/deploy/postgresql/postgresql.conf
new file mode 100644
index 000000000..4a8f88f8a
--- /dev/null
+++ b/deploy/postgresql/postgresql.conf
@@ -0,0 +1,841 @@
+# -----------------------------
+# PostgreSQL configuration file
+# -----------------------------
+#
+# This file consists of lines of the form:
+#
+# name = value
+#
+# (The "=" is optional.) Whitespace may be used. Comments are introduced with
+# "#" anywhere on a line. The complete list of parameter names and allowed
+# values can be found in the PostgreSQL documentation.
+#
+# The commented-out settings shown in this file represent the default values.
+# Re-commenting a setting is NOT sufficient to revert it to the default value;
+# you need to reload the server.
+#
+# This file is read on server startup and when the server receives a SIGHUP
+# signal. If you edit the file on a running system, you have to SIGHUP the
+# server for the changes to take effect, run "pg_ctl reload", or execute
+# "SELECT pg_reload_conf()". Some parameters, which are marked below,
+# require a server shutdown and restart to take effect.
+#
+# Any parameter can also be given as a command-line option to the server, e.g.,
+# "postgres -c log_connections=on". Some parameters can be changed at run time
+# with the "SET" SQL command.
+#
+# Memory units: B = bytes Time units: us = microseconds
+# kB = kilobytes ms = milliseconds
+# MB = megabytes s = seconds
+# GB = gigabytes min = minutes
+# TB = terabytes h = hours
+# d = days
+
+
+#------------------------------------------------------------------------------
+# FILE LOCATIONS
+#------------------------------------------------------------------------------
+
+# The default values of these variables are driven from the -D command-line
+# option or PGDATA environment variable, represented here as ConfigDir.
+
+#data_directory = 'ConfigDir' # use data in another directory
+ # (change requires restart)
+#hba_file = 'ConfigDir/pg_hba.conf' # host-based authentication file
+ # (change requires restart)
+#ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file
+ # (change requires restart)
+
+# If external_pid_file is not explicitly set, no extra PID file is written.
+#external_pid_file = '' # write an extra PID file
+ # (change requires restart)
+
+
+#------------------------------------------------------------------------------
+# CONNECTIONS AND AUTHENTICATION
+#------------------------------------------------------------------------------
+
+# - Connection Settings -
+
+listen_addresses = '*' # what IP address(es) to listen on;
+ # comma-separated list of addresses;
+ # defaults to 'localhost'; use '*' for all
+ # (change requires restart)
+#port = 5432 # (change requires restart)
+#max_connections = 100 # (change requires restart)
+#reserved_connections = 0 # (change requires restart)
+#superuser_reserved_connections = 3 # (change requires restart)
+#unix_socket_directories = '/tmp' # comma-separated list of directories
+ # (change requires restart)
+#unix_socket_group = '' # (change requires restart)
+#unix_socket_permissions = 0777 # begin with 0 to use octal notation
+ # (change requires restart)
+#bonjour = off # advertise server via Bonjour
+ # (change requires restart)
+#bonjour_name = '' # defaults to the computer name
+ # (change requires restart)
+
+# - TCP settings -
+# see "man tcp" for details
+
+#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds;
+ # 0 selects the system default
+#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds;
+ # 0 selects the system default
+#tcp_keepalives_count = 0 # TCP_KEEPCNT;
+ # 0 selects the system default
+#tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds;
+ # 0 selects the system default
+
+#client_connection_check_interval = 0 # time between checks for client
+ # disconnection while running queries;
+ # 0 for never
+
+# - Authentication -
+
+#authentication_timeout = 1min # 1s-600s
+#password_encryption = scram-sha-256 # scram-sha-256 or md5
+#scram_iterations = 4096
+#db_user_namespace = off
+
+# GSSAPI using Kerberos
+#krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab'
+#krb_caseins_users = off
+#gss_accept_delegation = off
+
+# - SSL -
+
+#ssl = off
+#ssl_ca_file = ''
+#ssl_cert_file = 'server.crt'
+#ssl_crl_file = ''
+#ssl_crl_dir = ''
+#ssl_key_file = 'server.key'
+#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
+#ssl_prefer_server_ciphers = on
+#ssl_ecdh_curve = 'prime256v1'
+#ssl_min_protocol_version = 'TLSv1.2'
+#ssl_max_protocol_version = ''
+#ssl_dh_params_file = ''
+#ssl_passphrase_command = ''
+#ssl_passphrase_command_supports_reload = off
+
+
+#------------------------------------------------------------------------------
+# RESOURCE USAGE (except WAL)
+#------------------------------------------------------------------------------
+
+# - Memory -
+
+#shared_buffers = 128MB # min 128kB
+ # (change requires restart)
+#huge_pages = try # on, off, or try
+ # (change requires restart)
+#huge_page_size = 0 # zero for system default
+ # (change requires restart)
+#temp_buffers = 8MB # min 800kB
+#max_prepared_transactions = 0 # zero disables the feature
+ # (change requires restart)
+# Caution: it is not advisable to set max_prepared_transactions nonzero unless
+# you actively intend to use prepared transactions.
+#work_mem = 4MB # min 64kB
+#hash_mem_multiplier = 2.0 # 1-1000.0 multiplier on hash table work_mem
+#maintenance_work_mem = 64MB # min 1MB
+#autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem
+#logical_decoding_work_mem = 64MB # min 64kB
+#max_stack_depth = 2MB # min 100kB
+#shared_memory_type = mmap # the default is the first option
+ # supported by the operating system:
+ # mmap
+ # sysv
+ # windows
+ # (change requires restart)
+#dynamic_shared_memory_type = posix # the default is usually the first option
+ # supported by the operating system:
+ # posix
+ # sysv
+ # windows
+ # mmap
+ # (change requires restart)
+#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = 256kB # size of vacuum and analyze buffer access strategy ring;
+ # 0 to disable vacuum buffer access strategy;
+ # range 128kB to 16GB
+
+# - Disk -
+
+#temp_file_limit = -1 # limits per-process temp file space
+ # in kilobytes, or -1 for no limit
+
+# - Kernel Resources -
+
+#max_files_per_process = 1000 # min 64
+ # (change requires restart)
+
+# - Cost-Based Vacuum Delay -
+
+#vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables)
+#vacuum_cost_page_hit = 1 # 0-10000 credits
+#vacuum_cost_page_miss = 2 # 0-10000 credits
+#vacuum_cost_page_dirty = 20 # 0-10000 credits
+#vacuum_cost_limit = 200 # 1-10000 credits
+
+# - Background Writer -
+
+#bgwriter_delay = 200ms # 10-10000ms between rounds
+#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables
+#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round
+#bgwriter_flush_after = 0 # measured in pages, 0 disables
+
+# - Asynchronous Behavior -
+
+#backend_flush_after = 0 # measured in pages, 0 disables
+#effective_io_concurrency = 1 # 1-1000; 0 disables prefetching
+#maintenance_io_concurrency = 10 # 1-1000; 0 disables prefetching
+#max_worker_processes = 8 # (change requires restart)
+#max_parallel_workers_per_gather = 2 # taken from max_parallel_workers
+#max_parallel_maintenance_workers = 2 # taken from max_parallel_workers
+#max_parallel_workers = 8 # maximum number of max_worker_processes that
+ # can be used in parallel operations
+#parallel_leader_participation = on
+#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate
+ # (change requires restart)
+
+
+#------------------------------------------------------------------------------
+# WRITE-AHEAD LOG
+#------------------------------------------------------------------------------
+
+# - Settings -
+
+#wal_level = replica # minimal, replica, or logical
+ # (change requires restart)
+#fsync = on # flush data to disk for crash safety
+ # (turning this off can cause
+ # unrecoverable data corruption)
+#synchronous_commit = on # synchronization level;
+ # off, local, remote_write, remote_apply, or on
+#wal_sync_method = fsync # the default is the first option
+ # supported by the operating system:
+ # open_datasync
+ # fdatasync (default on Linux and FreeBSD)
+ # fsync
+ # fsync_writethrough
+ # open_sync
+#full_page_writes = on # recover from partial page writes
+#wal_log_hints = off # also do full page writes of non-critical updates
+ # (change requires restart)
+#wal_compression = off # enables compression of full-page writes;
+ # off, pglz, lz4, zstd, or on
+#wal_init_zero = on # zero-fill new WAL files
+#wal_recycle = on # recycle WAL files
+#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
+ # (change requires restart)
+#wal_writer_delay = 200ms # 1-10000 milliseconds
+#wal_writer_flush_after = 1MB # measured in pages, 0 disables
+#wal_skip_threshold = 2MB
+
+#commit_delay = 0 # range 0-100000, in microseconds
+#commit_siblings = 5 # range 1-1000
+
+# - Checkpoints -
+
+#checkpoint_timeout = 5min # range 30s-1d
+#checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0
+#checkpoint_flush_after = 0 # measured in pages, 0 disables
+#checkpoint_warning = 30s # 0 disables
+#max_wal_size = 1GB
+#min_wal_size = 80MB
+
+# - Prefetching during recovery -
+
+#recovery_prefetch = try # prefetch pages referenced in the WAL?
+#wal_decode_buffer_size = 512kB # lookahead window used for prefetching
+ # (change requires restart)
+
+# - Archiving -
+
+#archive_mode = off # enables archiving; off, on, or always
+ # (change requires restart)
+#archive_library = '' # library to use to archive a WAL file
+ # (empty string indicates archive_command should
+ # be used)
+#archive_command = '' # command to use to archive a WAL file
+ # placeholders: %p = path of file to archive
+ # %f = file name only
+ # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'
+#archive_timeout = 0 # force a WAL file switch after this
+ # number of seconds; 0 disables
+
+# - Archive Recovery -
+
+# These are only used in recovery mode.
+
+#restore_command = '' # command to use to restore an archived WAL file
+ # placeholders: %p = path of file to restore
+ # %f = file name only
+ # e.g. 'cp /mnt/server/archivedir/%f %p'
+#archive_cleanup_command = '' # command to execute at every restartpoint
+#recovery_end_command = '' # command to execute at completion of recovery
+
+# - Recovery Target -
+
+# Set these only when performing a targeted recovery.
+
+#recovery_target = '' # 'immediate' to end recovery as soon as a
+ # consistent state is reached
+ # (change requires restart)
+#recovery_target_name = '' # the named restore point to which recovery will proceed
+ # (change requires restart)
+#recovery_target_time = '' # the time stamp up to which recovery will proceed
+ # (change requires restart)
+#recovery_target_xid = '' # the transaction ID up to which recovery will proceed
+ # (change requires restart)
+#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed
+ # (change requires restart)
+#recovery_target_inclusive = on # Specifies whether to stop:
+ # just after the specified recovery target (on)
+ # just before the recovery target (off)
+ # (change requires restart)
+#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID
+ # (change requires restart)
+#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown'
+ # (change requires restart)
+
+
+#------------------------------------------------------------------------------
+# REPLICATION
+#------------------------------------------------------------------------------
+
+# - Sending Servers -
+
+# Set these on the primary and on any standby that will send replication data.
+
+#max_wal_senders = 10 # max number of walsender processes
+ # (change requires restart)
+#max_replication_slots = 10 # max number of replication slots
+ # (change requires restart)
+#wal_keep_size = 0 # in megabytes; 0 disables
+#max_slot_wal_keep_size = -1 # in megabytes; -1 disables
+#wal_sender_timeout = 60s # in milliseconds; 0 disables
+#track_commit_timestamp = off # collect timestamp of transaction commit
+ # (change requires restart)
+
+# - Primary Server -
+
+# These settings are ignored on a standby server.
+
+#synchronous_standby_names = '' # standby servers that provide sync rep
+ # method to choose sync standbys, number of sync standbys,
+ # and comma-separated list of application_name
+ # from standby(s); '*' = all
+
+# - Standby Servers -
+
+# These settings are ignored on a primary server.
+
+#primary_conninfo = '' # connection string to sending server
+#primary_slot_name = '' # replication slot on sending server
+#hot_standby = on # "off" disallows queries during recovery
+ # (change requires restart)
+#max_standby_archive_delay = 30s # max delay before canceling queries
+ # when reading WAL from archive;
+ # -1 allows indefinite delay
+#max_standby_streaming_delay = 30s # max delay before canceling queries
+ # when reading streaming WAL;
+ # -1 allows indefinite delay
+#wal_receiver_create_temp_slot = off # create temp slot if primary_slot_name
+ # is not set
+#wal_receiver_status_interval = 10s # send replies at least this often
+ # 0 disables
+#hot_standby_feedback = off # send info from standby to prevent
+ # query conflicts
+#wal_receiver_timeout = 60s # time that receiver waits for
+ # communication from primary
+ # in milliseconds; 0 disables
+#wal_retrieve_retry_interval = 5s # time to wait before retrying to
+ # retrieve WAL after a failed attempt
+#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery
+
+# - Subscribers -
+
+# These settings are ignored on a publisher.
+
+#max_logical_replication_workers = 4 # taken from max_worker_processes
+ # (change requires restart)
+#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers
+#max_parallel_apply_workers_per_subscription = 2 # taken from max_logical_replication_workers
+
+
+#------------------------------------------------------------------------------
+# QUERY TUNING
+#------------------------------------------------------------------------------
+
+# - Planner Method Configuration -
+
+#enable_async_append = on
+#enable_bitmapscan = on
+#enable_gathermerge = on
+#enable_hashagg = on
+#enable_hashjoin = on
+#enable_incremental_sort = on
+#enable_indexscan = on
+#enable_indexonlyscan = on
+#enable_material = on
+#enable_memoize = on
+#enable_mergejoin = on
+#enable_nestloop = on
+#enable_parallel_append = on
+#enable_parallel_hash = on
+#enable_partition_pruning = on
+#enable_partitionwise_join = off
+#enable_partitionwise_aggregate = off
+#enable_presorted_aggregate = on
+#enable_seqscan = on
+#enable_sort = on
+#enable_tidscan = on
+
+# - Planner Cost Constants -
+
+#seq_page_cost = 1.0 # measured on an arbitrary scale
+#random_page_cost = 4.0 # same scale as above
+#cpu_tuple_cost = 0.01 # same scale as above
+#cpu_index_tuple_cost = 0.005 # same scale as above
+#cpu_operator_cost = 0.0025 # same scale as above
+#parallel_setup_cost = 1000.0 # same scale as above
+#parallel_tuple_cost = 0.1 # same scale as above
+#min_parallel_table_scan_size = 8MB
+#min_parallel_index_scan_size = 512kB
+#effective_cache_size = 4GB
+
+#jit_above_cost = 100000 # perform JIT compilation if available
+ # and query more expensive than this;
+ # -1 disables
+#jit_inline_above_cost = 500000 # inline small functions if query is
+ # more expensive than this; -1 disables
+#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if
+ # query is more expensive than this;
+ # -1 disables
+
+# - Genetic Query Optimizer -
+
+#geqo = on
+#geqo_threshold = 12
+#geqo_effort = 5 # range 1-10
+#geqo_pool_size = 0 # selects default based on effort
+#geqo_generations = 0 # selects default based on effort
+#geqo_selection_bias = 2.0 # range 1.5-2.0
+#geqo_seed = 0.0 # range 0.0-1.0
+
+# - Other Planner Options -
+
+#default_statistics_target = 100 # range 1-10000
+#constraint_exclusion = partition # on, off, or partition
+#cursor_tuple_fraction = 0.1 # range 0.0-1.0
+#from_collapse_limit = 8
+#jit = on # allow JIT compilation
+#join_collapse_limit = 8 # 1 disables collapsing of explicit
+ # JOIN clauses
+#plan_cache_mode = auto # auto, force_generic_plan or
+ # force_custom_plan
+#recursive_worktable_factor = 10.0 # range 0.001-1000000
+
+
+#------------------------------------------------------------------------------
+# REPORTING AND LOGGING
+#------------------------------------------------------------------------------
+
+# - Where to Log -
+
+log_destination = 'stderr' # Valid values are combinations of
+ # stderr, csvlog, jsonlog, syslog, and
+ # eventlog, depending on platform.
+ # csvlog and jsonlog require
+ # logging_collector to be on.
+
+# This is used when logging to stderr:
+logging_collector = off # Enable capturing of stderr, jsonlog,
+ # and csvlog into log files. Required
+ # to be on for csvlogs and jsonlogs.
+ # (change requires restart)
+
+# These are only used if logging_collector is on:
+#log_directory = 'log' # directory where log files are written,
+ # can be absolute or relative to PGDATA
+#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern,
+ # can include strftime() escapes
+#log_file_mode = 0600 # creation mode for log files,
+ # begin with 0 to use octal notation
+#log_rotation_age = 1d # Automatic rotation of logfiles will
+ # happen after that time. 0 disables.
+#log_rotation_size = 10MB # Automatic rotation of logfiles will
+ # happen after that much log output.
+ # 0 disables.
+#log_truncate_on_rotation = off # If on, an existing log file with the
+ # same name as the new log file will be
+ # truncated rather than appended to.
+ # But such truncation only occurs on
+ # time-driven rotation, not on restarts
+ # or size-driven rotation. Default is
+ # off, meaning append to existing files
+ # in all cases.
+
+# These are relevant when logging to syslog:
+#syslog_facility = 'LOCAL0'
+#syslog_ident = 'postgres'
+#syslog_sequence_numbers = on
+#syslog_split_messages = on
+
+# This is only relevant when logging to eventlog (Windows):
+# (change requires restart)
+#event_source = 'PostgreSQL'
+
+# - When to Log -
+
+log_min_messages = info # values in order of decreasing detail:
+ # debug5
+ # debug4
+ # debug3
+ # debug2
+ # debug1
+ # info
+ # notice
+ # warning
+ # error
+ # log
+ # fatal
+ # panic
+
+log_min_error_statement = error # values in order of decreasing detail:
+ # debug5
+ # debug4
+ # debug3
+ # debug2
+ # debug1
+ # info
+ # notice
+ # warning
+ # error
+ # log
+ # fatal
+ # panic (effectively off)
+
+#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements
+ # and their durations, > 0 logs only
+ # statements running at least this number
+ # of milliseconds
+
+#log_min_duration_sample = -1 # -1 is disabled, 0 logs a sample of statements
+ # and their durations, > 0 logs only a sample of
+ # statements running at least this number
+ # of milliseconds;
+ # sample fraction is determined by log_statement_sample_rate
+
+#log_statement_sample_rate = 1.0 # fraction of logged statements exceeding
+ # log_min_duration_sample to be logged;
+ # 1.0 logs all such statements, 0.0 never logs
+
+
+#log_transaction_sample_rate = 0.0 # fraction of transactions whose statements
+ # are logged regardless of their duration; 1.0 logs all
+ # statements from all transactions, 0.0 never logs
+
+#log_startup_progress_interval = 10s # Time between progress updates for
+ # long-running startup operations.
+ # 0 disables the feature, > 0 indicates
+ # the interval in milliseconds.
+
+# - What to Log -
+
+#debug_print_parse = off
+#debug_print_rewritten = off
+#debug_print_plan = off
+#debug_pretty_print = on
+#log_autovacuum_min_duration = 10min # log autovacuum activity;
+ # -1 disables, 0 logs all actions and
+ # their durations, > 0 logs only
+ # actions running at least this number
+ # of milliseconds.
+#log_checkpoints = on
+log_connections = on
+log_disconnections = on
+log_duration = on
+#log_error_verbosity = default # terse, default, or verbose messages
+log_hostname = on
+#log_line_prefix = '%m [%p] ' # special values:
+ # %a = application name
+ # %u = user name
+ # %d = database name
+ # %r = remote host and port
+ # %h = remote host
+ # %b = backend type
+ # %p = process ID
+ # %P = process ID of parallel group leader
+ # %t = timestamp without milliseconds
+ # %m = timestamp with milliseconds
+ # %n = timestamp with milliseconds (as a Unix epoch)
+ # %Q = query ID (0 if none or not computed)
+ # %i = command tag
+ # %e = SQL state
+ # %c = session ID
+ # %l = session line number
+ # %s = session start timestamp
+ # %v = virtual transaction ID
+ # %x = transaction ID (0 if none)
+ # %q = stop here in non-session
+ # processes
+ # %% = '%'
+ # e.g. '<%u%%%d> '
+#log_lock_waits = off # log lock waits >= deadlock_timeout
+#log_recovery_conflict_waits = off # log standby recovery conflict waits
+ # >= deadlock_timeout
+#log_parameter_max_length = -1 # when logging statements, limit logged
+ # bind-parameter values to N bytes;
+ # -1 means print in full, 0 disables
+#log_parameter_max_length_on_error = 0 # when logging an error, limit logged
+ # bind-parameter values to N bytes;
+ # -1 means print in full, 0 disables
+log_statement = 'all' # none, ddl, mod, all
+#log_replication_commands = off
+#log_temp_files = -1 # log temporary files equal or larger
+ # than the specified size in kilobytes;
+ # -1 disables, 0 logs all temp files
+#log_timezone = 'GMT'
+
+# - Process Title -
+
+#cluster_name = '' # added to process titles if nonempty
+ # (change requires restart)
+#update_process_title = on
+
+
+#------------------------------------------------------------------------------
+# STATISTICS
+#------------------------------------------------------------------------------
+
+# - Cumulative Query and Index Statistics -
+
+#track_activities = on
+#track_activity_query_size = 1024 # (change requires restart)
+track_counts = on
+#track_io_timing = off
+#track_wal_io_timing = off
+#track_functions = none # none, pl, all
+#stats_fetch_consistency = cache # cache, none, snapshot
+
+
+# - Monitoring -
+
+#compute_query_id = auto
+#log_statement_stats = off
+#log_parser_stats = off
+#log_planner_stats = off
+#log_executor_stats = off
+
+
+#------------------------------------------------------------------------------
+# AUTOVACUUM
+#------------------------------------------------------------------------------
+
+autovacuum = on # Enable autovacuum subprocess? 'on'
+ # requires track_counts to also be on.
+#autovacuum_max_workers = 3 # max number of autovacuum subprocesses
+ # (change requires restart)
+#autovacuum_naptime = 1min # time between autovacuum runs
+#autovacuum_vacuum_threshold = 50 # min number of row updates before
+ # vacuum
+#autovacuum_vacuum_insert_threshold = 1000 # min number of row inserts
+ # before vacuum; -1 disables insert
+ # vacuums
+#autovacuum_analyze_threshold = 50 # min number of row updates before
+ # analyze
+#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum
+#autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of inserts over table
+ # size before insert vacuum
+#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze
+#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum
+ # (change requires restart)
+#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age
+ # before forced vacuum
+ # (change requires restart)
+#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for
+ # autovacuum, in milliseconds;
+ # -1 means use vacuum_cost_delay
+#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for
+ # autovacuum, -1 means use
+ # vacuum_cost_limit
+
+
+#------------------------------------------------------------------------------
+# CLIENT CONNECTION DEFAULTS
+#------------------------------------------------------------------------------
+
+# - Statement Behavior -
+
+#client_min_messages = notice # values in order of decreasing detail:
+ # debug5
+ # debug4
+ # debug3
+ # debug2
+ # debug1
+ # log
+ # notice
+ # warning
+ # error
+#search_path = '"$user", public' # schema names
+#row_security = on
+#default_table_access_method = 'heap'
+#default_tablespace = '' # a tablespace name, '' uses the default
+#default_toast_compression = 'pglz' # 'pglz' or 'lz4'
+#temp_tablespaces = '' # a list of tablespace names, '' uses
+ # only default tablespace
+#check_function_bodies = on
+#default_transaction_isolation = 'read committed'
+#default_transaction_read_only = off
+#default_transaction_deferrable = off
+#session_replication_role = 'origin'
+#statement_timeout = 0 # in milliseconds, 0 is disabled
+#lock_timeout = 0 # in milliseconds, 0 is disabled
+#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled
+#idle_session_timeout = 0 # in milliseconds, 0 is disabled
+#vacuum_freeze_table_age = 150000000
+#vacuum_freeze_min_age = 50000000
+#vacuum_failsafe_age = 1600000000
+#vacuum_multixact_freeze_table_age = 150000000
+#vacuum_multixact_freeze_min_age = 5000000
+#vacuum_multixact_failsafe_age = 1600000000
+#bytea_output = 'hex' # hex, escape
+#xmlbinary = 'base64'
+#xmloption = 'content'
+#gin_pending_list_limit = 4MB
+#createrole_self_grant = '' # set and/or inherit
+
+# - Locale and Formatting -
+
+#datestyle = 'iso, mdy'
+#intervalstyle = 'postgres'
+#timezone = 'GMT'
+#timezone_abbreviations = 'Default' # Select the set of available time zone
+ # abbreviations. Currently, there are
+ # Default
+ # Australia (historical usage)
+ # India
+ # You can create your own file in
+ # share/timezonesets/.
+#extra_float_digits = 1 # min -15, max 3; any value >0 actually
+ # selects precise output mode
+#client_encoding = sql_ascii # actually, defaults to database
+ # encoding
+
+# These settings are initialized by initdb, but they can be changed.
+#lc_messages = 'C' # locale for system error message
+ # strings
+#lc_monetary = 'C' # locale for monetary formatting
+#lc_numeric = 'C' # locale for number formatting
+#lc_time = 'C' # locale for time formatting
+
+#icu_validation_level = warning # report ICU locale validation
+ # errors at the given level
+
+# default configuration for text search
+#default_text_search_config = 'pg_catalog.simple'
+
+# - Shared Library Preloading -
+
+#local_preload_libraries = ''
+#session_preload_libraries = ''
+#shared_preload_libraries = '' # (change requires restart)
+#jit_provider = 'llvmjit' # JIT library to use
+
+# - Other Defaults -
+
+#dynamic_library_path = '$libdir'
+#gin_fuzzy_search_limit = 0
+
+
+#------------------------------------------------------------------------------
+# LOCK MANAGEMENT
+#------------------------------------------------------------------------------
+
+#deadlock_timeout = 1s
+#max_locks_per_transaction = 64 # min 10
+ # (change requires restart)
+#max_pred_locks_per_transaction = 64 # min 10
+ # (change requires restart)
+#max_pred_locks_per_relation = -2 # negative values mean
+ # (max_pred_locks_per_transaction
+ # / -max_pred_locks_per_relation) - 1
+#max_pred_locks_per_page = 2 # min 0
+
+
+#------------------------------------------------------------------------------
+# VERSION AND PLATFORM COMPATIBILITY
+#------------------------------------------------------------------------------
+
+# - Previous PostgreSQL Versions -
+
+#array_nulls = on
+#backslash_quote = safe_encoding # on, off, or safe_encoding
+#escape_string_warning = on
+#lo_compat_privileges = off
+#quote_all_identifiers = off
+#standard_conforming_strings = on
+#synchronize_seqscans = on
+
+# - Other Platforms and Clients -
+
+#transform_null_equals = off
+
+
+#------------------------------------------------------------------------------
+# ERROR HANDLING
+#------------------------------------------------------------------------------
+
+#exit_on_error = off # terminate session on any error?
+#restart_after_crash = on # reinitialize after backend crash?
+#data_sync_retry = off # retry or panic on failure to fsync
+ # data?
+ # (change requires restart)
+#recovery_init_sync_method = fsync # fsync, syncfs (Linux 5.8+)
+
+
+#------------------------------------------------------------------------------
+# CONFIG FILE INCLUDES
+#------------------------------------------------------------------------------
+
+# These options allow settings to be loaded from files other than the
+# default postgresql.conf. Note that these are directives, not variable
+# assignments, so they can usefully be given more than once.
+
+#include_dir = '...' # include files ending in '.conf' from
+ # a directory, e.g., 'conf.d'
+#include_if_exists = '...' # include file only if it exists
+#include = '...' # include file
+
+
+#------------------------------------------------------------------------------
+# CUSTOMIZED OPTIONS
+#------------------------------------------------------------------------------
+
+# Add settings for extensions here
+
+
+# https://pgtune.leopard.in.ua/#/
+# DB Version: 15
+# OS Type: linux
+# DB Type: web
+# Total Memory (RAM): 1 GB
+# CPUs num: 1
+# Data Storage: ssd
+max_connections = 200
+shared_buffers = 256MB
+effective_cache_size = 768MB
+maintenance_work_mem = 64MB
+checkpoint_completion_target = 0.9
+wal_buffers = 7864kB
+default_statistics_target = 100
+random_page_cost = 1.1
+effective_io_concurrency = 200
+work_mem = 655kB
+min_wal_size = 1GB
+max_wal_size = 4GB
diff --git a/deploy/postgresql/schema_postgres.sql b/deploy/postgresql/schema_postgres.sql
new file mode 100644
index 000000000..a5ff4d5aa
--- /dev/null
+++ b/deploy/postgresql/schema_postgres.sql
@@ -0,0 +1,420 @@
+CREATE TABLE IF NOT EXISTS setting
+(
+ "key" VARCHAR(32) NOT NULL,
+ "value" TEXT NOT NULL,
+ PRIMARY KEY ("key")
+);
+
+CREATE TABLE IF NOT EXISTS account
+(
+ "id" SERIAL PRIMARY KEY NOT NULL,
+ "name" TEXT NOT NULL,
+ "normal_name" TEXT NOT NULL,
+ "hash" TEXT NOT NULL,
+ "mail" TEXT NOT NULL,
+ "mail_verified" BOOLEAN NOT NULL,
+ "mail_verified_at" TIMESTAMP WITH TIME ZONE DEFAULT NULL,
+ "mail_token" TEXT DEFAULT NULL,
+ "password_token" TEXT DEFAULT NULL,
+ "login_token" TEXT DEFAULT NULL,
+ "login_token_created" TIMESTAMP WITH TIME ZONE DEFAULT NULL,
+ "state" INTEGER NOT NULL,
+ "last_login" TIMESTAMP WITH TIME ZONE DEFAULT NULL,
+ "created" TIMESTAMP WITH TIME ZONE NOT NULL,
+ CONSTRAINT uq_account_name UNIQUE ("name"),
+ CONSTRAINT uq_account_normal_name UNIQUE ("normal_name"),
+ CONSTRAINT uq_account_login_token UNIQUE ("login_token"),
+ CONSTRAINT uq_account_mail UNIQUE ("mail")
+);
+
+CREATE TABLE IF NOT EXISTS ddon_character_common
+(
+ "character_common_id" SERIAL PRIMARY KEY NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "hide_equip_head" BOOLEAN NOT NULL,
+ "hide_equip_lantern" BOOLEAN NOT NULL,
+ "jewelry_slot_num" SMALLINT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS ddon_character
+(
+ "character_id" SERIAL PRIMARY KEY NOT NULL,
+ "character_common_id" INTEGER NOT NULL,
+ "account_id" INTEGER NOT NULL,
+ "version" INTEGER NOT NULL,
+ "first_name" TEXT NOT NULL,
+ "last_name" TEXT NOT NULL,
+ "created" TIMESTAMP WITH TIME ZONE NOT NULL,
+ "my_pawn_slot_num" SMALLINT NOT NULL,
+ "rental_pawn_slot_num" SMALLINT NOT NULL,
+ "hide_equip_head_pawn" BOOLEAN NOT NULL,
+ "hide_equip_lantern_pawn" BOOLEAN NOT NULL,
+ "arisen_profile_share_range" SMALLINT NOT NULL,
+ CONSTRAINT fk_character_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE,
+ CONSTRAINT fk_character_account_id FOREIGN KEY ("account_id") REFERENCES account ("id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_pawn
+(
+ "pawn_id" SERIAL PRIMARY KEY NOT NULL,
+ "character_common_id" INTEGER NOT NULL,
+ "character_id" INTEGER NOT NULL,
+ "name" TEXT NOT NULL,
+ "hm_type" SMALLINT NOT NULL,
+ "pawn_type" SMALLINT NOT NULL,
+ CONSTRAINT fk_pawn_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE,
+ CONSTRAINT fk_character_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_edit_info
+(
+ "character_common_id" INTEGER PRIMARY KEY NOT NULL,
+ "sex" SMALLINT NOT NULL,
+ "voice" SMALLINT NOT NULL,
+ "voice_pitch" SMALLINT NOT NULL,
+ "personality" SMALLINT NOT NULL,
+ "speech_freq" SMALLINT NOT NULL,
+ "body_type" SMALLINT NOT NULL,
+ "hair" SMALLINT NOT NULL,
+ "beard" SMALLINT NOT NULL,
+ "makeup" SMALLINT NOT NULL,
+ "scar" SMALLINT NOT NULL,
+ "eye_preset_no" SMALLINT NOT NULL,
+ "nose_preset_no" SMALLINT NOT NULL,
+ "mouth_preset_no" SMALLINT NOT NULL,
+ "eyebrow_tex_no" SMALLINT NOT NULL,
+ "color_skin" SMALLINT NOT NULL,
+ "color_hair" SMALLINT NOT NULL,
+ "color_beard" SMALLINT NOT NULL,
+ "color_eyebrow" SMALLINT NOT NULL,
+ "color_r_eye" SMALLINT NOT NULL,
+ "color_l_eye" SMALLINT NOT NULL,
+ "color_makeup" SMALLINT NOT NULL,
+ "sokutobu" SMALLINT NOT NULL,
+ "hitai" SMALLINT NOT NULL,
+ "mimi_jyouge" SMALLINT NOT NULL,
+ "kannkaku" SMALLINT NOT NULL,
+ "mabisasi_jyouge" SMALLINT NOT NULL,
+ "hanakuchi_jyouge" SMALLINT NOT NULL,
+ "ago_saki_haba" SMALLINT NOT NULL,
+ "ago_zengo" SMALLINT NOT NULL,
+ "ago_saki_jyouge" SMALLINT NOT NULL,
+ "hitomi_ookisa" SMALLINT NOT NULL,
+ "me_ookisa" SMALLINT NOT NULL,
+ "me_kaiten" SMALLINT NOT NULL,
+ "mayu_kaiten" SMALLINT NOT NULL,
+ "mimi_ookisa" SMALLINT NOT NULL,
+ "mimi_muki" SMALLINT NOT NULL,
+ "elf_mimi" SMALLINT NOT NULL,
+ "miken_takasa" SMALLINT NOT NULL,
+ "miken_haba" SMALLINT NOT NULL,
+ "hohobone_ryou" SMALLINT NOT NULL,
+ "hohobone_jyouge" SMALLINT NOT NULL,
+ "hohoniku" SMALLINT NOT NULL,
+ "erahone_jyouge" SMALLINT NOT NULL,
+ "erahone_haba" SMALLINT NOT NULL,
+ "hana_jyouge" SMALLINT NOT NULL,
+ "hana_haba" SMALLINT NOT NULL,
+ "hana_takasa" SMALLINT NOT NULL,
+ "hana_kakudo" SMALLINT NOT NULL,
+ "kuchi_haba" SMALLINT NOT NULL,
+ "kuchi_atsusa" SMALLINT NOT NULL,
+ "eyebrow_uv_offset_x" SMALLINT NOT NULL,
+ "eyebrow_uv_offset_y" SMALLINT NOT NULL,
+ "wrinkle" SMALLINT NOT NULL,
+ "wrinkle_albedo_blend_rate" SMALLINT NOT NULL,
+ "wrinkle_detail_normal_power" SMALLINT NOT NULL,
+ "muscle_albedo_blend_rate" SMALLINT NOT NULL,
+ "muscle_detail_normal_power" SMALLINT NOT NULL,
+ "height" SMALLINT NOT NULL,
+ "head_size" SMALLINT NOT NULL,
+ "neck_offset" SMALLINT NOT NULL,
+ "neck_scale" SMALLINT NOT NULL,
+ "upper_body_scale_x" SMALLINT NOT NULL,
+ "belly_size" SMALLINT NOT NULL,
+ "teat_scale" SMALLINT NOT NULL,
+ "tekubi_size" SMALLINT NOT NULL,
+ "koshi_offset" SMALLINT NOT NULL,
+ "koshi_size" SMALLINT NOT NULL,
+ "ankle_offset" SMALLINT NOT NULL,
+ "fat" SMALLINT NOT NULL,
+ "muscle" SMALLINT NOT NULL,
+ "motion_filter" SMALLINT NOT NULL,
+ CONSTRAINT fk_edit_info_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_status_info
+(
+ "character_common_id" INTEGER PRIMARY KEY NOT NULL,
+ "hp" INTEGER NOT NULL,
+ "stamina" INTEGER NOT NULL,
+ "revive_point" SMALLINT NOT NULL,
+ "max_hp" INTEGER NOT NULL,
+ "max_stamina" INTEGER NOT NULL,
+ "white_hp" INTEGER NOT NULL,
+ "gain_hp" INTEGER NOT NULL,
+ "gain_stamina" INTEGER NOT NULL,
+ "gain_attack" INTEGER NOT NULL,
+ "gain_defense" INTEGER NOT NULL,
+ "gain_magic_attack" INTEGER NOT NULL,
+ "gain_magic_defense" INTEGER NOT NULL,
+ CONSTRAINT fk_status_info_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_character_matching_profile
+(
+ "character_id" INTEGER PRIMARY KEY NOT NULL,
+ "entry_job" SMALLINT NOT NULL,
+ "entry_job_level" INTEGER NOT NULL,
+ "current_job" SMALLINT NOT NULL,
+ "current_job_level" INTEGER NOT NULL,
+ objective_type1 INTEGER NOT NULL,
+ objective_type2 INTEGER NOT NULL,
+ "play_style" INTEGER NOT NULL,
+ "comment" TEXT NOT NULL,
+ "is_join_party" BOOLEAN NOT NULL,
+ CONSTRAINT fk_matching_profile_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_character_arisen_profile
+(
+ "character_id" INTEGER PRIMARY KEY NOT NULL,
+ "background_id" SMALLINT NOT NULL,
+ "title_uid" INTEGER NOT NULL,
+ "title_index" INTEGER NOT NULL,
+ "motion_id" SMALLINT NOT NULL,
+ "motion_frame_no" INTEGER NOT NULL,
+ CONSTRAINT fk_arisen_profile_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_character_job_data
+(
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "exp" INTEGER NOT NULL,
+ "job_point" INTEGER NOT NULL,
+ "lv" INTEGER NOT NULL,
+ "atk" SMALLINT NOT NULL,
+ "def" SMALLINT NOT NULL,
+ "m_atk" SMALLINT NOT NULL,
+ "m_def" SMALLINT NOT NULL,
+ "strength" SMALLINT NOT NULL,
+ "down_power" SMALLINT NOT NULL,
+ "shake_power" SMALLINT NOT NULL,
+ "stun_power" SMALLINT NOT NULL,
+ "consitution" SMALLINT NOT NULL,
+ "guts" SMALLINT NOT NULL,
+ "fire_resist" SMALLINT NOT NULL,
+ "ice_resist" SMALLINT NOT NULL,
+ "thunder_resist" SMALLINT NOT NULL,
+ "holy_resist" SMALLINT NOT NULL,
+ "dark_resist" SMALLINT NOT NULL,
+ "spread_resist" SMALLINT NOT NULL,
+ "freeze_resist" SMALLINT NOT NULL,
+ "shock_resist" SMALLINT NOT NULL,
+ "absorb_resist" SMALLINT NOT NULL,
+ "dark_elm_resist" SMALLINT NOT NULL,
+ "poison_resist" SMALLINT NOT NULL,
+ "slow_resist" SMALLINT NOT NULL,
+ "sleep_resist" SMALLINT NOT NULL,
+ "stun_resist" SMALLINT NOT NULL,
+ "wet_resist" SMALLINT NOT NULL,
+ "oil_resist" SMALLINT NOT NULL,
+ "seal_resist" SMALLINT NOT NULL,
+ "curse_resist" SMALLINT NOT NULL,
+ "soft_resist" SMALLINT NOT NULL,
+ "stone_resist" SMALLINT NOT NULL,
+ "gold_resist" SMALLINT NOT NULL,
+ "fire_reduce_resist" SMALLINT NOT NULL,
+ "ice_reduce_resist" SMALLINT NOT NULL,
+ "thunder_reduce_resist" SMALLINT NOT NULL,
+ "holy_reduce_resist" SMALLINT NOT NULL,
+ "dark_reduce_resist" SMALLINT NOT NULL,
+ "atk_down_resist" SMALLINT NOT NULL,
+ "def_down_resist" SMALLINT NOT NULL,
+ "m_atk_down_resist" SMALLINT NOT NULL,
+ "m_def_down_resist" SMALLINT NOT NULL,
+ CONSTRAINT pk_character_job_data PRIMARY KEY (character_common_id, job),
+ CONSTRAINT fk_character_job_data_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_storage
+(
+ "character_id" INTEGER NOT NULL,
+ "storage_type" SMALLINT NOT NULL,
+ "slot_max" SMALLINT NOT NULL,
+ "item_sort" BYTEA NOT NULL,
+ CONSTRAINT pk_ddon_storage PRIMARY KEY (character_id, storage_type),
+ CONSTRAINT fk_storage_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_wallet_point
+(
+ "character_id" INTEGER NOT NULL,
+ "type" SMALLINT NOT NULL,
+ "value" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_wallet_point PRIMARY KEY (character_id, type),
+ CONSTRAINT fk_wallet_point_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_item
+(
+ -- See Item.cs, uid is at most of size 8.
+ "uid" VARCHAR(8) NOT NULL,
+ "item_id" INTEGER NOT NULL,
+ unk3 SMALLINT NOT NULL,
+ "color" SMALLINT NOT NULL,
+ "plus_value" SMALLINT NOT NULL,
+ PRIMARY KEY ("uid")
+);
+
+CREATE TABLE IF NOT EXISTS ddon_storage_item
+(
+ "item_uid" VARCHAR(8) NOT NULL,
+ "character_id" INTEGER NOT NULL,
+ "storage_type" SMALLINT NOT NULL,
+ "slot_no" SMALLINT NOT NULL,
+ "item_num" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_storage_item PRIMARY KEY (character_id, storage_type, slot_no),
+ CONSTRAINT fk_storage_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE,
+ CONSTRAINT fk_storage_item_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_equip_item
+(
+ "item_uid" VARCHAR(8) NOT NULL,
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "equip_type" SMALLINT NOT NULL,
+ "equip_slot" SMALLINT NOT NULL,
+ CONSTRAINT pk_ddon_equip_item PRIMARY KEY (character_common_id, job, equip_type, equip_slot),
+ CONSTRAINT fk_equip_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE,
+ CONSTRAINT fk_equip_item_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_equip_job_item
+(
+ "item_uid" VARCHAR(8) NOT NULL,
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "equip_slot" SMALLINT NOT NULL,
+ CONSTRAINT pk_ddon_equip_job_item PRIMARY KEY (character_common_id, job, equip_slot),
+ CONSTRAINT fk_equip_job_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE,
+ CONSTRAINT fk_equip_job_item_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_normal_skill_param
+(
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "skill_no" INTEGER NOT NULL,
+ "index" INTEGER NOT NULL,
+ "pre_skill_no" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_normal_skill_param PRIMARY KEY (character_common_id, job, skill_no),
+ CONSTRAINT fk_normal_skill_param_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_learned_custom_skill
+(
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "skill_id" INTEGER NOT NULL,
+ "skill_lv" SMALLINT NOT NULL,
+ PRIMARY KEY (character_common_id, job, skill_id),
+ CONSTRAINT fk_learned_custom_skill_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_equipped_custom_skill
+(
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "slot_no" SMALLINT NOT NULL,
+ "skill_id" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_equipped_custom_skill PRIMARY KEY (character_common_id, job, slot_no),
+ CONSTRAINT fk_equipped_custom_skill_character_common_id FOREIGN KEY (character_common_id, job, skill_id) REFERENCES ddon_learned_custom_skill (character_common_id, job, skill_id) ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_learned_ability
+(
+ "character_common_id" INTEGER NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "ability_id" INTEGER NOT NULL,
+ "ability_lv" SMALLINT NOT NULL,
+ PRIMARY KEY (character_common_id, job, ability_id),
+ CONSTRAINT fk_learned_ability_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_equipped_ability
+(
+ "character_common_id" INTEGER NOT NULL,
+ "equipped_to_job" SMALLINT NOT NULL,
+ "job" SMALLINT NOT NULL,
+ "slot_no" SMALLINT NOT NULL,
+ "ability_id" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_equipped_ability PRIMARY KEY (character_common_id, equipped_to_job, slot_no),
+ CONSTRAINT fk_equipped_ability_character_common_id FOREIGN KEY (character_common_id, job, ability_id) REFERENCES ddon_learned_ability (character_common_id, job, ability_id) ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_shortcut
+(
+ "character_id" INTEGER NOT NULL,
+ "page_no" INTEGER NOT NULL,
+ "button_no" INTEGER NOT NULL,
+ "shortcut_id" INTEGER NOT NULL,
+ u32_data INTEGER NOT NULL,
+ f32_data INTEGER NOT NULL,
+ "exex_type" SMALLINT NOT NULL,
+ CONSTRAINT pk_ddon_shortcut PRIMARY KEY (character_id, page_no, button_no),
+ CONSTRAINT fk_shortcut_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_communication_shortcut
+(
+ "character_id" INTEGER NOT NULL,
+ "page_no" INTEGER NOT NULL,
+ "button_no" INTEGER NOT NULL,
+ "type" SMALLINT NOT NULL,
+ "category" SMALLINT NOT NULL,
+ "id" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_communication_shortcut PRIMARY KEY (character_id, page_no, button_no),
+ CONSTRAINT fk_communication_shortcut_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_pawn_reaction
+(
+ "pawn_id" INTEGER NOT NULL,
+ "reaction_type" SMALLINT NOT NULL,
+ "motion_no" INTEGER NOT NULL,
+ CONSTRAINT pk_ddon_pawn_reaction PRIMARY KEY (pawn_id, reaction_type),
+ CONSTRAINT fk_pawn_reaction_pawn_id FOREIGN KEY ("pawn_id") REFERENCES ddon_pawn ("pawn_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_sp_skill
+(
+ "pawn_id" INTEGER NOT NULL,
+ "sp_skill_id" SMALLINT NOT NULL,
+ "sp_skill_lv" SMALLINT NOT NULL,
+ CONSTRAINT pk_ddon_sp_skill PRIMARY KEY ("pawn_id"),
+ CONSTRAINT fk_sp_skill_pawn_id FOREIGN KEY ("pawn_id") REFERENCES ddon_pawn ("pawn_id") ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS ddon_game_token
+(
+ "account_id" INTEGER PRIMARY KEY NOT NULL,
+ "character_id" INTEGER NOT NULL,
+ "token" TEXT NOT NULL,
+ "created" TIMESTAMP WITH TIME ZONE NOT NULL,
+ CONSTRAINT uq_game_token_token UNIQUE ("token"),
+ CONSTRAINT fk_game_token_account_id FOREIGN KEY ("account_id") REFERENCES account ("id"),
+ CONSTRAINT fk_game_token_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id")
+);
+
+CREATE TABLE IF NOT EXISTS ddon_connection
+(
+ "server_id" INTEGER NOT NULL,
+ "account_id" INTEGER NOT NULL,
+ "type" INTEGER NOT NULL,
+ "created" TIMESTAMP WITH TIME ZONE NOT NULL,
+ CONSTRAINT uq_connection_server_id_account_id UNIQUE (server_id, account_id),
+ CONSTRAINT fk_connection_token_account_id FOREIGN KEY ("account_id") REFERENCES account ("id")
+);
diff --git a/docker-compose.mariadb.yml b/docker-compose.mariadb.yml
new file mode 100644
index 000000000..94133e1b7
--- /dev/null
+++ b/docker-compose.mariadb.yml
@@ -0,0 +1,68 @@
+version: '3.8'
+
+volumes:
+ ddon-server-mariadb-volume:
+
+networks:
+ ddon-network:
+
+services:
+ app:
+ container_name: ddon-server
+ build:
+ context: .
+ args:
+ - RUNTIME=${RUNTIME}
+ restart: no
+ ports:
+ # Game server
+ - "52000:52000"
+ # Web server
+ - "52099:52099"
+ # Login server
+ - "52100:52100"
+ environment:
+ - DB_TYPE=mariadb
+ - DB_DATABASE=mariadb
+ - DB_FOLDER=/var/ddon/server/Files/Database
+ - DB_HOST=db
+ - DB_USER=admin
+ - DB_PASS=admin
+ - DB_PORT=3306
+ - DB_WIPE_ON_STARTUP=false
+ volumes:
+ - ./Arrowgene.Ddon.config.mariadb.local_dev.json:/var/ddon/server/Files/Arrowgene.Ddon.config.json:ro
+ - ./Arrowgene.Ddon.Shared/Files/Assets:/var/ddon/server/Files/Assets:ro
+ networks:
+ - ddon-network
+ # Workaround: cli expects to be able to process keyboard input
+ tty: true
+ depends_on:
+ db:
+ condition: service_healthy
+ command: /var/ddon/server/Arrowgene.Ddon.Cli server start
+
+ db:
+ container_name: ddon-db
+ image: mariadb:11
+ ports:
+ # Database
+ - "3306:3306"
+ restart: no
+ volumes:
+ - ddon-server-mariadb-volume:/var/lib/mysql
+ - $PWD/deploy/mariadb/mariadb.cnf:/etc/mysql/mariadb.conf.d/mariadb.cnf:ro
+ networks:
+ - ddon-network
+ environment:
+ - MARIADB_USER=admin
+ - MARIADB_ROOT_PASSWORD=admin
+ - MARIADB_PASSWORD=admin
+ - MARIADB_DATABASE=mariadb
+ - LANG=C.UTF_8
+ command: ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"]
+ healthcheck:
+ test: ["CMD-SHELL", "mariadb --defaults-file=/var/lib/mysql/.my-healthcheck.cnf --skip-column-names -h localhost -e \"select 1 from information_schema.ENGINES WHERE ENGINE='InnoDB' AND support in ('YES', 'DEFAULT', 'ENABLED')\""]
+ interval: 3s
+ timeout: 3s
+ retries: 10
diff --git a/docker-compose.psql.yml b/docker-compose.psql.yml
new file mode 100644
index 000000000..8c01354bf
--- /dev/null
+++ b/docker-compose.psql.yml
@@ -0,0 +1,67 @@
+version: '3.8'
+
+volumes:
+ ddon-server-psql-volume:
+
+networks:
+ ddon-network:
+
+services:
+ app:
+ container_name: ddon-server
+ build:
+ context: .
+ args:
+ - RUNTIME=${RUNTIME}
+ restart: no
+ ports:
+ # Game server
+ - "52000:52000"
+ # Web server
+ - "52099:52099"
+ # Login server
+ - "52100:52100"
+ environment:
+ - DB_TYPE=postgresql
+ - DB_DATABASE=postgres
+ - DB_FOLDER=/var/ddon/server/Files/Database
+ - DB_HOST=db
+ - DB_USER=root
+ - DB_PASS=root
+ - DB_PORT=5432
+ - DB_WIPE_ON_STARTUP=false
+ volumes:
+ - ./Arrowgene.Ddon.config.psql.local_dev.json:/var/ddon/server/Files/Arrowgene.Ddon.config.json:ro
+ - ./Arrowgene.Ddon.Shared/Files/Assets:/var/ddon/server/Files/Assets:ro
+ networks:
+ - ddon-network
+ # Workaround: cli expects to be able to process keyboard input
+ tty: true
+ depends_on:
+ db:
+ condition: service_healthy
+ command: /var/ddon/server/Arrowgene.Ddon.Cli server start
+
+ db:
+ container_name: ddon-db
+ image: postgres:16-alpine
+ ports:
+ # Database
+ - "5432:5432"
+ restart: no
+ volumes:
+ - ddon-server-psql-volume:/var/lib/postgresql/data
+ - $PWD/deploy/postgresql/postgresql.conf:/etc/postgresql/postgresql.conf:ro
+ networks:
+ - ddon-network
+ environment:
+ - POSTGRES_USER=root
+ - POSTGRES_PASSWORD=root
+ - POSTGRES_DB=postgres
+ - POSTGRES_INITDB_ARGS=--auth=scram-sha-256 --lc-numeric=en_US.UTF-8
+ command: postgres -c config_file=/etc/postgresql/postgresql.conf
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready", "-d", "postgres"]
+ interval: 10s
+ timeout: 1s
+ retries: 3
diff --git a/docker-compose.yml b/docker-compose.yml
index 8ffd66331..5b4e622b3 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,26 +1,40 @@
version: '3.8'
volumes:
- ddon-server-database-volume:
+ ddon-server-sqlite-volume:
+
+networks:
+ ddon-network:
services:
app:
container_name: ddon-server
- build: .
+ build:
+ context: .
+ args:
+ - RUNTIME=${RUNTIME}
restart: no
ports:
# Database
- '3306:3306'
# Game server
- - '52000:52000'
+ - "52000:52000"
# Web server
- - '52099:52099'
+ - "52099:52099"
# Login server
- - '52100:52100'
+ - "52100:52100"
+ environment:
+ - DB_TYPE=sqlite
+ - DB_DATABASE=Ddon
+ - DB_FOLDER=/var/ddon/server/Files/Database
+ - DB_PORT=3306
+ - DB_WIPE_ON_STARTUP=false
volumes:
- - ddon-server-database-volume:/var/ddon/server/Files/Database
+ - ddon-server-sqlite-volume:/var/ddon/server/Files/Database
- ./Arrowgene.Ddon.config.local_dev.json:/var/ddon/server/Files/Arrowgene.Ddon.config.json:ro
- ./Arrowgene.Ddon.Shared/Files/Assets:/var/ddon/server/Files/Assets:ro
+ networks:
+ - ddon-network
# Workaround: cli expects to be able to process keyboard input
tty: true
command: /var/ddon/server/Arrowgene.Ddon.Cli server start