From 05c2624a4a566cda726bfd76e8a8dc4affc463b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Sat, 29 Jan 2022 23:24:57 +0100 Subject: [PATCH] feat(data): add database nostale data implementation --- Data/NosSmooth.Data.Database/Data/ItemInfo.cs | 53 +++++ Data/NosSmooth.Data.Database/Data/MapInfo.cs | 63 ++++++ .../Data/MonsterInfo.cs | 42 ++++ .../NosSmooth.Data.Database/Data/SkillInfo.cs | 67 ++++++ .../Data/Translation.cs | 35 ++++ .../DatabaseInfoService.cs | 79 +++++++ .../DatabaseLanguageService.cs | 79 +++++++ .../DatabaseMigrator.cs | 193 ++++++++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 49 +++++ .../NosSmooth.Data.Database.csproj | 10 + .../NostaleDataContext.cs | 70 +++++++ .../NostaleDataContextOptions.cs | 18 ++ 12 files changed, 758 insertions(+) create mode 100644 Data/NosSmooth.Data.Database/Data/ItemInfo.cs create mode 100644 Data/NosSmooth.Data.Database/Data/MapInfo.cs create mode 100644 Data/NosSmooth.Data.Database/Data/MonsterInfo.cs create mode 100644 Data/NosSmooth.Data.Database/Data/SkillInfo.cs create mode 100644 Data/NosSmooth.Data.Database/Data/Translation.cs create mode 100644 Data/NosSmooth.Data.Database/DatabaseInfoService.cs create mode 100644 Data/NosSmooth.Data.Database/DatabaseLanguageService.cs create mode 100644 Data/NosSmooth.Data.Database/DatabaseMigrator.cs create mode 100644 Data/NosSmooth.Data.Database/Extensions/ServiceCollectionExtensions.cs create mode 100644 Data/NosSmooth.Data.Database/NostaleDataContext.cs create mode 100644 Data/NosSmooth.Data.Database/NostaleDataContextOptions.cs diff --git a/Data/NosSmooth.Data.Database/Data/ItemInfo.cs b/Data/NosSmooth.Data.Database/Data/ItemInfo.cs new file mode 100644 index 0000000..7a2c3fb --- /dev/null +++ b/Data/NosSmooth.Data.Database/Data/ItemInfo.cs @@ -0,0 +1,53 @@ +// +// ItemInfo.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Runtime.Serialization; +using NosSmooth.Data.Abstractions.Enums; +using NosSmooth.Data.Abstractions.Infos; +using NosSmooth.Data.Abstractions.Language; + +namespace NosSmooth.Data.Database.Data; + +/// +public class ItemInfo : IItemInfo +{ + private string _nameKey = null!; + + /// + [DatabaseGenerated(DatabaseGeneratedOption.None)] + [Key] + public int VNum { get; set; } + + /// + /// The name translation key. + /// + public string NameKey + { + get => _nameKey; + set + { + _nameKey = value; + Name = new TranslatableString(TranslationRoot.Item, value); + } + } + + /// + public TranslatableString Name { get; set; } + + /// + public int Type { get; set; } + + /// + public int SubType { get; set; } + + /// + public BagType BagType { get; set; } + + /// + public string[] Data { get; set; } = null!; +} \ No newline at end of file diff --git a/Data/NosSmooth.Data.Database/Data/MapInfo.cs b/Data/NosSmooth.Data.Database/Data/MapInfo.cs new file mode 100644 index 0000000..f867189 --- /dev/null +++ b/Data/NosSmooth.Data.Database/Data/MapInfo.cs @@ -0,0 +1,63 @@ +// +// MapInfo.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using NosSmooth.Data.Abstractions.Infos; +using NosSmooth.Data.Abstractions.Language; + +namespace NosSmooth.Data.Database.Data; + +/// +public class MapInfo : IMapInfo +{ + private string _nameKey = null!; + + /// + [DatabaseGenerated(DatabaseGeneratedOption.None)] + [Key] + public int Id { get; set; } + + /// + /// The name translation key. + /// + public string NameKey + { + get => _nameKey; + set + { + _nameKey = value; + Name = new TranslatableString(TranslationRoot.Map, value); + } + } + + /// + public TranslatableString Name { get; set; } + + /// + public short Width { get; set; } + + /// + public short Height { get; set; } + + /// + /// Gets or sets the grid data of the map. + /// + public byte[] Grid { get; set; } = null!; + + /// + public byte GetData(short x, short y) + { + return Grid[(y * Width) + x]; + } + + /// + public bool IsWalkable(short x, short y) + { + var val = GetData(x, y); + return val == 0 || val == 2 || (val >= 16 && val <= 19); + } +} \ No newline at end of file diff --git a/Data/NosSmooth.Data.Database/Data/MonsterInfo.cs b/Data/NosSmooth.Data.Database/Data/MonsterInfo.cs new file mode 100644 index 0000000..1d6f256 --- /dev/null +++ b/Data/NosSmooth.Data.Database/Data/MonsterInfo.cs @@ -0,0 +1,42 @@ +// +// MonsterInfo.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using NosSmooth.Data.Abstractions.Infos; +using NosSmooth.Data.Abstractions.Language; + +namespace NosSmooth.Data.Database.Data; + +/// +public class MonsterInfo : IMonsterInfo +{ + private string _nameKey = null!; + + /// + [DatabaseGenerated(DatabaseGeneratedOption.None)] + [Key] + public int VNum { get; set; } + + /// + /// The name translation key. + /// + public string NameKey + { + get => _nameKey; + set + { + _nameKey = value; + Name = new TranslatableString(TranslationRoot.Monster, value); + } + } + + /// + public TranslatableString Name { get; set; } + + /// + public int Level { get; set; } +} \ No newline at end of file diff --git a/Data/NosSmooth.Data.Database/Data/SkillInfo.cs b/Data/NosSmooth.Data.Database/Data/SkillInfo.cs new file mode 100644 index 0000000..cbb0371 --- /dev/null +++ b/Data/NosSmooth.Data.Database/Data/SkillInfo.cs @@ -0,0 +1,67 @@ +// +// SkillInfo.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using NosSmooth.Data.Abstractions.Enums; +using NosSmooth.Data.Abstractions.Infos; +using NosSmooth.Data.Abstractions.Language; + +namespace NosSmooth.Data.Database.Data; + +/// +public class SkillInfo : ISkillInfo +{ + private string _nameKey = null!; + + /// + [DatabaseGenerated(DatabaseGeneratedOption.None)] + [Key] + public int VNum { get; set; } + + /// + /// The name translation key. + /// + public string NameKey + { + get => _nameKey; + set + { + _nameKey = value; + Name = new TranslatableString(TranslationRoot.Skill, value); + } + } + + /// + public TranslatableString Name { get; set; } + + /// + public short Range { get; set; } + + /// + public short ZoneRange { get; set; } + + /// + public int CastTime { get; set; } + + /// + public int Cooldown { get; set; } + + /// + public SkillType SkillType { get; set; } + + /// + public int MpCost { get; set; } + + /// + public short CastId { get; set; } + + /// + public TargetType TargetType { get; set; } + + /// + public HitType HitType { get; set; } +} \ No newline at end of file diff --git a/Data/NosSmooth.Data.Database/Data/Translation.cs b/Data/NosSmooth.Data.Database/Data/Translation.cs new file mode 100644 index 0000000..c4db437 --- /dev/null +++ b/Data/NosSmooth.Data.Database/Data/Translation.cs @@ -0,0 +1,35 @@ +// +// Translation.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using NosSmooth.Data.Abstractions.Language; + +namespace NosSmooth.Data.Database.Data; + +/// +/// A translation of NosTale string. +/// +public class Translation +{ + /// + /// Gets or sets the language of the translation. + /// + public Language Language { get; set; } + + /// + /// Gets or sets the root key of the translation. + /// + public TranslationRoot Root { get; set; } + + /// + /// Gets or sets the key of the translation. + /// + public string Key { get; set; } = null!; + + /// + /// Gets or sets the translation string. + /// + public string Translated { get; set; } = null!; +} \ No newline at end of file diff --git a/Data/NosSmooth.Data.Database/DatabaseInfoService.cs b/Data/NosSmooth.Data.Database/DatabaseInfoService.cs new file mode 100644 index 0000000..d07ecbb --- /dev/null +++ b/Data/NosSmooth.Data.Database/DatabaseInfoService.cs @@ -0,0 +1,79 @@ +// +// DatabaseInfoService.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.EntityFrameworkCore; +using NosSmooth.Data.Abstractions; +using NosSmooth.Data.Abstractions.Infos; +using Remora.Results; + +namespace NosSmooth.Data.Database; + +/// +public class DatabaseInfoService : IInfoService +{ + private readonly IDbContextFactory _dbContextFactory; + + /// + /// Initializes a new instance of the class. + /// + /// The database context factory. + public DatabaseInfoService(IDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + /// + public async Task> GetItemInfoAsync(int vnum, CancellationToken ct = default) + { + await using var context = await _dbContextFactory.CreateDbContextAsync(ct); + var item = await context.Items.AsNoTracking().FirstOrDefaultAsync(x => x.VNum == vnum, ct); + if (item is null) + { + return new NotFoundError($"Couldn't find item {vnum}"); + } + + return item; + } + + /// + public async Task> GetMapInfoAsync(int id, CancellationToken ct = default) + { + await using var context = await _dbContextFactory.CreateDbContextAsync(ct); + var item = await context.Maps.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id, ct); + if (item is null) + { + return new NotFoundError($"Couldn't find map {id}"); + } + + return item; + } + + /// + public async Task> GetMonsterInfoAsync(int vnum, CancellationToken ct = default) + { + await using var context = await _dbContextFactory.CreateDbContextAsync(ct); + var item = await context.Monsters.AsNoTracking().FirstOrDefaultAsync(x => x.VNum == vnum, ct); + if (item is null) + { + return new NotFoundError($"Couldn't find monster {vnum}"); + } + + return item; + } + + /// + public async Task> GetSkillInfoAsync(int vnum, CancellationToken ct = default) + { + await using var context = await _dbContextFactory.CreateDbContextAsync(ct); + var item = await context.Skills.AsNoTracking().FirstOrDefaultAsync(x => x.VNum == vnum, ct); + if (item is null) + { + return new NotFoundError($"Couldn't find skill {vnum}"); + } + + return item; + } +} \ No newline at end of file diff --git a/Data/NosSmooth.Data.Database/DatabaseLanguageService.cs b/Data/NosSmooth.Data.Database/DatabaseLanguageService.cs new file mode 100644 index 0000000..cc135be --- /dev/null +++ b/Data/NosSmooth.Data.Database/DatabaseLanguageService.cs @@ -0,0 +1,79 @@ +// +// DatabaseLanguageService.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.InteropServices.ComTypes; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using NosSmooth.Data.Abstractions.Language; +using Remora.Results; + +namespace NosSmooth.Data.Database; + +/// +public class DatabaseLanguageService : ILanguageService +{ + private readonly IDbContextFactory _dbContextFactory; + private readonly LanguageServiceOptions _options; + + /// + /// Initializes a new instance of the class. + /// + /// The database context factory. + /// The options. + public DatabaseLanguageService + ( + IDbContextFactory dbContextFactory, + IOptions options + ) + { + CurrentLanguage = options.Value.Language; + _dbContextFactory = dbContextFactory; + _options = options.Value; + } + + /// + public Language CurrentLanguage { get; set; } + + /// + public async Task> GetTranslationAsync(TranslationRoot root, string key, Language? language = default, CancellationToken ct = default) + { + try + { + language ??= CurrentLanguage; + await using var context = await _dbContextFactory.CreateDbContextAsync(ct); + var translation = await context.Translations.FirstOrDefaultAsync + (x => x.Root == root && x.Key == key && x.Language == language, ct); + if (translation is null) + { + return new NotFoundError($"Could not find translation for {root} {key}"); + } + + return translation.Translated; + } + catch (Exception e) + { + return e; + } + } + + /// + public async Task> GetTranslationAsync + (TranslatableString translatableString, Language? language = default, CancellationToken ct = default) + { + var translation = await GetTranslationAsync(translatableString.Root, translatableString.Key, language, ct); + if (!translation.IsSuccess) + { + return translation; + } + + if (_options.FillTranslatableStrings) + { + translatableString.Fill(translation.Entity); + } + + return translation; + } +} \ No newline at end of file diff --git a/Data/NosSmooth.Data.Database/DatabaseMigrator.cs b/Data/NosSmooth.Data.Database/DatabaseMigrator.cs new file mode 100644 index 0000000..f8ffcc7 --- /dev/null +++ b/Data/NosSmooth.Data.Database/DatabaseMigrator.cs @@ -0,0 +1,193 @@ +// +// DatabaseMigrator.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.EntityFrameworkCore; +using NosSmooth.Data.Abstractions; +using NosSmooth.Data.Database.Data; +using Remora.Results; + +namespace NosSmooth.Data.Database; + +/// +/// Migrates Nostale data into sqlite database. +/// +public class DatabaseMigrator +{ + private readonly IDbContextFactory _dbContextFactory; + + /// + /// Initializes a new instance of the class. + /// + /// The database context factory. + public DatabaseMigrator(IDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + /// + /// Migrates the data into the database. + /// + /// The NosTale data. + /// The cancellation token for cancelling the operation. + /// A representing the result of the asynchronous operation. + public async Task Migrate(NostaleData data, CancellationToken ct = default) + { + await using var context = await _dbContextFactory.CreateDbContextAsync(ct); + + var itemsResult = await MigrateItems(context, data); + if (!itemsResult.IsSuccess) + { + return itemsResult; + } + + var skillsResult = await MigrateSkills(context, data); + if (!skillsResult.IsSuccess) + { + return skillsResult; + } + + var monstersResult = await MigrateMonsters(context, data); + if (!monstersResult.IsSuccess) + { + return monstersResult; + } + + var mapsResult = await MigrateMaps(context, data); + if (!mapsResult.IsSuccess) + { + return mapsResult; + } + + var translationsResult = await MigrateTranslations(context, data); + if (!translationsResult.IsSuccess) + { + return translationsResult; + } + + await context.Database.EnsureDeletedAsync(ct); + await context.Database.EnsureCreatedAsync(ct); + + try + { + await context.SaveChangesAsync(ct); + } + catch (Exception e) + { + return e; + } + + return Result.FromSuccess(); + } + + private async Task MigrateTranslations(NostaleDataContext context, NostaleData data) + { + foreach (var languageTranslation in data.Translations) + { + foreach (var rootTranslations in languageTranslation.Value) + { + foreach (var translations in rootTranslations.Value) + { + var translation = new Translation + { + Key = translations.Key, + Root = rootTranslations.Key, + Language = languageTranslation.Key, + Translated = translations.Value + }; + context.Add(translation); + } + } + } + + return Result.FromSuccess(); + } + + private Task MigrateItems(NostaleDataContext dbContext, NostaleData data) + { + foreach (var item in data.Items.Values) + { + var itemInfo = new ItemInfo + { + BagType = item.BagType, + Data = item.Data, + NameKey = item.Name.Key, + SubType = item.SubType, + Type = item.Type, + VNum = item.VNum + }; + dbContext.Add(itemInfo); + } + + return Task.FromResult(Result.FromSuccess()); + } + + private Task MigrateSkills(NostaleDataContext dbContext, NostaleData data) + { + foreach (var skill in data.Skills.Values) + { + var skillInfo = new SkillInfo + { + CastId = skill.CastId, + CastTime = skill.CastTime, + Cooldown = skill.Cooldown, + HitType = skill.HitType, + MpCost = skill.MpCost, + NameKey = skill.Name.Key, + Range = skill.Range, + SkillType = skill.SkillType, + TargetType = skill.TargetType, + VNum = skill.VNum, + ZoneRange = skill.ZoneRange + }; + dbContext.Add(skillInfo); + } + + return Task.FromResult(Result.FromSuccess()); + } + + private Task MigrateMonsters(NostaleDataContext dbContext, NostaleData data) + { + foreach (var monster in data.Monsters.Values) + { + var monsterInfo = new MonsterInfo + { + VNum = monster.VNum, + NameKey = monster.Name.Key, + Level = monster.Level + }; + dbContext.Add(monsterInfo); + } + + return Task.FromResult(Result.FromSuccess()); + } + + private Task MigrateMaps(NostaleDataContext dbContext, NostaleData data) + { + foreach (var map in data.Maps.Values) + { + var grid = new byte[map.Height * map.Width]; + for (short y = 0; y < map.Height; y++) + { + for (short x = 0; x < map.Width; x++) + { + grid[(y * map.Width) + x] = map.GetData(x, y); + } + } + + var mapInfo = new MapInfo + { + Height = map.Height, + Width = map.Width, + NameKey = map.Name.Key, + Id = map.Id, + Grid = grid + }; + dbContext.Add(mapInfo); + } + + return Task.FromResult(Result.FromSuccess()); + } +} \ No newline at end of file diff --git a/Data/NosSmooth.Data.Database/Extensions/ServiceCollectionExtensions.cs b/Data/NosSmooth.Data.Database/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..377b6b7 --- /dev/null +++ b/Data/NosSmooth.Data.Database/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,49 @@ +// +// ServiceCollectionExtensions.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using NosSmooth.Data.Abstractions; +using NosSmooth.Data.Abstractions.Language; + +namespace NosSmooth.Data.Database.Extensions; + +/// +/// Extension methods for . +/// +public static class ServiceCollectionExtensions +{ + /// + /// Adds NosTale data language and info services using a database. + /// + /// The service collection. + /// The collection. + public static IServiceCollection AddNostaleDataDatabase + (this IServiceCollection serviceCollection) + { + return serviceCollection + .AddSingleton() + .AddSingleton() + .AddDbContextFactory + ( + (provider, builder) => builder.UseSqlite + (provider.GetRequiredService>().Value.ConnectionString) + ); + } + + /// + /// Adds used for migrating data to the database. + /// + /// The service collection. + /// The collection. + public static IServiceCollection AddNostaleDatabaseMigrator(this IServiceCollection serviceCollection) + { + return serviceCollection + .AddNostaleDataDatabase() + .AddSingleton(); + } +} \ No newline at end of file diff --git a/Data/NosSmooth.Data.Database/NosSmooth.Data.Database.csproj b/Data/NosSmooth.Data.Database/NosSmooth.Data.Database.csproj index eb2460e..73b814e 100644 --- a/Data/NosSmooth.Data.Database/NosSmooth.Data.Database.csproj +++ b/Data/NosSmooth.Data.Database/NosSmooth.Data.Database.csproj @@ -6,4 +6,14 @@ enable + + + + + + + + + + diff --git a/Data/NosSmooth.Data.Database/NostaleDataContext.cs b/Data/NosSmooth.Data.Database/NostaleDataContext.cs new file mode 100644 index 0000000..b07293a --- /dev/null +++ b/Data/NosSmooth.Data.Database/NostaleDataContext.cs @@ -0,0 +1,70 @@ +// +// NostaleDataContext.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.EntityFrameworkCore; +using NosSmooth.Data.Database.Data; + +namespace NosSmooth.Data.Database; + +/// +/// Database context with NosTale data. +/// +public class NostaleDataContext : DbContext +{ + /// + /// Initializes a new instance of the class. + /// + /// The options. + public NostaleDataContext(DbContextOptions options) + : base(options) + { + } + + /// + /// Gets the translations set. + /// + public DbSet Translations => Set(); + + /// + /// Gets the items set. + /// + public DbSet Items => Set(); + + /// + /// Gets the maps set. + /// + public DbSet Maps => Set(); + + /// + /// Gets the monsters set. + /// + public DbSet Monsters => Set(); + + /// + /// Gets the skills set. + /// + public DbSet Skills => Set(); + + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .Property(x => x.Data) + .HasConversion + ( + x => string.Join("|", x), + x => x.Split(',', StringSplitOptions.RemoveEmptyEntries) + ); + + modelBuilder.Entity().HasKey("Language", "Root", "Key"); + + modelBuilder.Entity().Ignore(x => x.Name); + modelBuilder.Entity().Ignore(x => x.Name); + modelBuilder.Entity().Ignore(x => x.Name); + modelBuilder.Entity().Ignore(x => x.Name); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/Data/NosSmooth.Data.Database/NostaleDataContextOptions.cs b/Data/NosSmooth.Data.Database/NostaleDataContextOptions.cs new file mode 100644 index 0000000..02f2916 --- /dev/null +++ b/Data/NosSmooth.Data.Database/NostaleDataContextOptions.cs @@ -0,0 +1,18 @@ +// +// NostaleDataContextOptions.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace NosSmooth.Data.Database; + +/// +/// Options for . +/// +public class NostaleDataContextOptions +{ + /// + /// Gets or sets the sqlite3 connection string. + /// + public string ConnectionString { get; set; } = "Data Source=nossmooth.sqlite3;"; +} \ No newline at end of file -- 2.48.1