diff --git a/Pf2eDataScrapper.sln.DotSettings b/Pf2eDataScrapper.sln.DotSettings index 06f6382..c2f1f1d 100644 --- a/Pf2eDataScrapper.sln.DotSettings +++ b/Pf2eDataScrapper.sln.DotSettings @@ -46,4 +46,5 @@ True True 2025.1.0.0 - 2025.1.0.0 \ No newline at end of file + 2025.1.0.0 + True \ No newline at end of file diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorCollection.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorCollection.cs index c10f7d1..5cd4052 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorCollection.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorCollection.cs @@ -52,8 +52,26 @@ internal class AonprdProcessorCollection } } + private void CheckDependencies() + { + foreach (ProcessorItem item in Items) + { + foreach (string dependency in item.Attribute.Dependencies) + { + if (Items.All(i => i.Attribute.Name != dependency)) + { + throw new( + $"Dependency '{dependency}' for processor '{item.Attribute.Name}' not found." + ); + } + } + } + } + private void SortItems() { + CheckDependencies(); + Dictionary priorities = []; while (priorities.Count < Items.Count) diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorInput.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorInput.cs index 67e9b3f..6f9aa5f 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorInput.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdProcessorInput.cs @@ -60,6 +60,12 @@ internal class AonprdProcessorInput(JObject input) : ProcessorInput(input) result.MaxActions = 3; break; + case "Two Actions to 2 rounds": + result.Actions = 2; + result.MaxActions = 3; + result.TwoRounds = true; + break; + case "Reaction or One Action": result.IsReaction = true; result.Actions = result.MaxActions = 1; @@ -90,5 +96,6 @@ internal class AonprdProcessorInput(JObject input) : ProcessorInput(input) public bool IsPassive; public bool IsReaction; public bool IsFreeAction; + public bool TwoRounds; } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/AonprdEnumResolver.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/AonprdEnumResolver.cs index 8b3af91..f503a86 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/AonprdEnumResolver.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/AonprdEnumResolver.cs @@ -33,6 +33,33 @@ public static class AonprdEnumResolver { "other", AttributeValues.Free }, } ), + new Solver( + new() + { + { "Single Action", ActionEnum.SingleAction }, + { "Two Actions", ActionEnum.TwoActions }, + { "Three Actions", ActionEnum.ThreeActions }, + { "Reaction", ActionEnum.Reaction }, + { "Free Action", ActionEnum.FreeAction }, + { "Single Action to Three Actions", ActionEnum.SingleActionToThreeActions }, + { "Single Action or more Actions", ActionEnum.SingleActionToThreeActions }, + { "Single Action or Two Actions", ActionEnum.SingleActionToTwoActions }, + { "Single Action to Two Actions", ActionEnum.SingleActionToTwoActions }, + { "Two Actions or Three Actions", ActionEnum.TwoActionsToThreeActions }, + { "Two Actions to 2 rounds", ActionEnum.TwoActionsToTwoRounds }, + { "Reaction or One Action", ActionEnum.ReactionOrOneAction }, + { "Reaction or Two Actions", ActionEnum.ReactionOrTwoAction }, + { "Reaction or Three Actions", ActionEnum.ReactionOrThreeAction }, + { "Free Action or One Action", ActionEnum.FreeActionOrOneAction }, + { "1 Minute", ActionEnum.OneMinute }, + { "1 minute", ActionEnum.OneMinute }, + { "5 minutes", ActionEnum.FiveMinutes }, + { "10 minutes", ActionEnum.TenMinutes }, + { "30 minutes", ActionEnum.ThirtyMinutes }, + { "1 hour", ActionEnum.OneHour }, + { "Nethys Note: No cast time was printed", ActionEnum.Unknown }, + } + ), ]; public static T Resolve(string value) diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/StringHelper.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/StringHelper.cs index 9968b36..134fbf2 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/StringHelper.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Helpers/StringHelper.cs @@ -6,6 +6,24 @@ internal partial class StringHelper { private StringHelper() { } + public List ParseMarkdown(string input) + { + List links = []; + Match match = MarkdownRegex().Match(input); + if (match.Captures.Count > 0) + { + links.Add( + new() + { + Prefix = match.Groups[1].Value.Trim(), + Text = match.Groups[2].Value.Trim(), + Url = match.Groups[3].Value.Trim(), + } + ); + } + return links; + } + public string ResolveSource(string input) { Match match = SourceSuffixRegex().Match(input); @@ -16,4 +34,14 @@ internal partial class StringHelper [GeneratedRegex(@"^\[.*?\]\(.*?\)\s+(.*)$")] private static partial Regex SourceSuffixRegex(); + + [GeneratedRegex(@"(.*?)\[(.*?)\]\((.*?)\)")] + private static partial Regex MarkdownRegex(); +} + +internal class MarkdownLink +{ + public string Prefix { get; set; } = ""; + public string Text { get; set; } = ""; + public string Url { get; set; } = ""; } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/TrainingProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SkillProcessor.cs similarity index 55% rename from Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/TrainingProcessor.cs rename to Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SkillProcessor.cs index 80342ce..1c8a112 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/TrainingProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Common/SkillProcessor.cs @@ -1,16 +1,21 @@ using Pf2eModel.Model.Common; +using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common; [AonprdProcessor("skill", [])] -internal class TrainingProcessor(AonprdProcessorInput input) : AbstractProcessor(input) +internal class SkillProcessor(AonprdProcessorInput input) : AbstractProcessor(input) { public Training Training = null!; public override void Process() { - Training = new() { Name = Input.GetRequired("name"), Type = TrainingType.Skill }; + Training = new() + { + Name = new() { { LocaleType.English, Input.GetRequired("name") } }, + Type = TrainingType.Skill, + }; } public override void Save() diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Subclasses/ApparitionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Subclasses/ApparitionProcessor.cs index e3e01cb..b094c8b 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Subclasses/ApparitionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Subclasses/ApparitionProcessor.cs @@ -1,8 +1,15 @@ -using Pf2eModel.Model.Structure; +using Microsoft.EntityFrameworkCore; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; +using Pf2eModel; +using Pf2eModel.Model.Base; +using Pf2eModel.Model.Common; +using Pf2eModel.Model.Common.I18n; +using Pf2eModel.Model.Enum; +using Pf2eModel.Model.Structure; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Subclasses; -[AonprdProcessor("apparition", ["class"])] +[AonprdProcessor("apparition", ["class", "spell", "trainings"])] internal class ApparitionProcessor(AonprdProcessorInput input) : AbstractSubclassProcessor(input) { public override Subclass Object { get; } = new(); @@ -11,7 +18,54 @@ internal class ApparitionProcessor(AonprdProcessorInput input) : AbstractSubclas public override string GroupName => "Apparition"; - public override void ProcessObject() { } + public override void ProcessObject() + { + // Spells + string spellMd = Input.GetRequired("spells_markdown"); + List spellUrls = StringHelper.ParseMarkdown(spellMd).Select(l => l.Url).ToList(); + + List spellIds = Context + .EntityExternals.Where(x => spellUrls.Contains(x.Suffix)) + .Select(x => x.Entity.Id) + .ToList(); + + List spells = Context + .Spells.Where(s => spellIds.Contains(s.Id)) + .Select(s => s.Entity) + .ToList(); + + foreach (Entity spellEntity in spells) + { + Object.Entities.Add(spellEntity); + } + + // Trainings + string trainingMd = Input.GetRequired("skill_markdown"); + + List trainingNames = trainingMd.Split(", ").ToList(); + List trainings = Context + .Trainings.Where(t => Pf2eDbContext.LocaleSearchArray(t.Name, trainingNames)) + .ToList(); + + foreach (string name in trainingNames) + { + Training? training = trainings.FirstOrDefault(t => t.Name!.Contains(name)); + training ??= new() + { + Name = new() { { LocaleType.English, name } }, + Type = TrainingType.Lore, + }; + + Object.Trainings.Add( + new() + { + Subclass = Object, + Training = training, + Stage = TrainingStage.Trained, + } + ); + } + } protected override string GetDescription() => base.GetDescription() diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ActionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ActionProcessor.cs index 3f45cf6..e236e34 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ActionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ActionProcessor.cs @@ -2,7 +2,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; -[AonprdProcessor("action")] +// [AonprdProcessor("action")] internal class ActionProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) { public override EntityType Type => EntityType.Action; @@ -29,16 +29,7 @@ internal class ActionProcessor(AonprdProcessorInput input) : AbstractEntityProce base.Process(); - AonprdProcessorInput.ActionStruct action = Input.ResolveActionString( - Input.GetOptional("actions") ?? "" - ); - - Object.Actions = action.Actions; - Object.MaxActions = action.MaxActions; - Object.IsFreeAction = action.IsFreeAction; - Object.IsPassive = action.IsPassive; - Object.IsReaction = action.IsReaction; - + Object.Actions = Input.EnumOptional("actions", ActionEnum.Passive); Object.Extra = GenerateExtra("school"); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ClassProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ClassProcessor.cs index 10ae2e4..bff7d17 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ClassProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/ClassProcessor.cs @@ -1,6 +1,8 @@ using System.Text.RegularExpressions; using Newtonsoft.Json.Linq; +using Pf2eModel; using Pf2eModel.Model.Common; +using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure.ClassRelation; @@ -82,13 +84,14 @@ internal class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProces protected ICollection GetSkillTrainings() { - string[] skillNames = Input + List skillNames = Input .GetRequired("skill_proficiency") .Select(token => token.ToString()) - .ToArray(); + .ToList(); - Training[] skills = Context.Trainings.Where(t => skillNames.Contains(t.Name)).ToArray(); - // Training[] skills = Context.Trainings.Where(t => Pf2eDbContext.LocaleSearchArray(t.Name, skillNames)).ToArray(); + Training[] skills = Context + .Trainings.Where(t => Pf2eDbContext.LocaleSearchArray(t.Name, skillNames)) + .ToArray(); return skills .Select(skill => new ClassTraining() @@ -121,12 +124,16 @@ internal class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProces foreach (string part in parts) { Training? training = Context.Trainings.FirstOrDefault(t => - t.Name == part && t.Type == type + Pf2eDbContext.LocaleSearch(t.Name, part) && t.Type == type ); if (training == null) { - training = new Training() { Name = part, Type = type }; + training = new Training() + { + Name = new() { [LocaleType.English] = part }, + Type = type, + }; Context.Trainings.Add(training); } @@ -160,7 +167,7 @@ internal class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProces { Dictionary result = Context .Trainings.Where(t => t.Type == TrainingType.Save) - .ToDictionary(t => t.Name); + .ToDictionary(t => t.Name[LocaleType.English]); foreach (string name in new[] { "Will", "Fortitude", "Reflex", "Perception" }) { @@ -171,7 +178,7 @@ internal class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProces Training training = new() { - Name = name, + Name = new() { [LocaleType.English] = name }, Type = (name == "Perception") ? TrainingType.Perception : TrainingType.Save, }; diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/SpellProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/SpellProcessor.cs new file mode 100644 index 0000000..59158f2 --- /dev/null +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/Processors/Types/SpellProcessor.cs @@ -0,0 +1,67 @@ +using Pf2eModel.Model.Common.I18n; +using Pf2eModel.Model.Enum; +using Pf2eModel.Model.Structure; +using Pf2eModel.Model.Structure.SpellRelation; + +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; + +[AonprdProcessor("spell")] +internal class SpellProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) +{ + public override EntityType Type => EntityType.Spell; + + public override Spell Object { get; } = new(); + + public override void Add() => Context.Spells.Add(Object); + + public override void Process() + { + base.Process(); + + Object.Level = Input.GetRequired("level"); + Object.Actions = Input.EnumOptional("actions", ActionEnum.Unknown); + Object.IsFocus = Input.GetRequired("spell_type") == "Focus"; + + switch (Input.GetRequired("spell_type")) + { + case "Focus": + Object.IsFocus = true; + break; + case "Cantrip": + Object.Level = 0; + break; + } + + foreach (string areaType in Input.GetOptional("area_type") ?? []) + { + Object.SpellAreas.Add( + new() + { + Spell = Object, + Type = Input.EnumRequired(areaType), + Value = 0, + } + ); + } + } + + protected override string GetDescription() => + base.GetDescription() + + AddedDescription( + [ + "area_raw", + "bloodline_markdown", + "deity_markdown", + "domain_markdown", + "lesson_markdown", + "mystery_markdown", + "requirement_markdown", + "summary_markdown", + "tradition_markdown", + "patron_theme_markdown", + "trigger_markdown", + "saving_throw_markdown", + "target_markdown", + ] + ); +} diff --git a/Pf2eModel/Migrations/20250428065903_1.Designer.cs b/Pf2eModel/Migrations/20250505114131_1.Designer.cs similarity index 95% rename from Pf2eModel/Migrations/20250428065903_1.Designer.cs rename to Pf2eModel/Migrations/20250505114131_1.Designer.cs index 21536e1..9ac18ea 100644 --- a/Pf2eModel/Migrations/20250428065903_1.Designer.cs +++ b/Pf2eModel/Migrations/20250505114131_1.Designer.cs @@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum; namespace Pf2eModel.Migrations { [DbContext(typeof(Pf2eDbContext))] - [Migration("20250428065903_1")] + [Migration("20250505114131_1")] partial class _1 { /// @@ -417,8 +417,7 @@ namespace Pf2eModel.Migrations b.Property("Name") .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)") + .HasColumnType("jsonb") .HasColumnName("name") .HasColumnOrder(2); @@ -873,16 +872,10 @@ namespace Pf2eModel.Migrations .HasColumnName("class_id") .HasColumnOrder(2); - b.Property("Name") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("name") - .HasColumnOrder(4); - b.Property("Stage") .HasColumnType("training_stage") .HasColumnName("stage") - .HasColumnOrder(5); + .HasColumnOrder(4); b.Property("TrainingId") .HasColumnType("integer") @@ -901,6 +894,62 @@ namespace Pf2eModel.Migrations b.ToTable("class_trainings", (string)null); }); + modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.SubclassProvidedEntities", b => + { + b.Property("SubclassId") + .HasColumnType("integer") + .HasColumnName("subclass_id"); + + b.Property("EntityId") + .HasColumnType("integer") + .HasColumnName("entity_id"); + + b.HasKey("SubclassId", "EntityId") + .HasName("pk_subclass_provided_entities"); + + b.HasIndex("EntityId") + .HasDatabaseName("ix_subclass_provided_entities_entity_id"); + + b.ToTable("subclass_provided_entities", (string)null); + }); + + modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.SubclassTrainings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasColumnOrder(1); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Stage") + .HasColumnType("training_stage") + .HasColumnName("stage") + .HasColumnOrder(4); + + b.Property("SubclassId") + .HasColumnType("integer") + .HasColumnName("subclass_id") + .HasColumnOrder(2); + + b.Property("TrainingId") + .HasColumnType("integer") + .HasColumnName("training_id") + .HasColumnOrder(3); + + b.HasKey("Id") + .HasName("pk_subclass_trainings"); + + b.HasIndex("SubclassId") + .HasDatabaseName("ix_subclass_trainings_subclass_id"); + + b.HasIndex("TrainingId") + .HasDatabaseName("ix_subclass_trainings_training_id"); + + b.ToTable("subclass_trainings", (string)null); + }); + modelBuilder.Entity("Pf2eModel.Model.Structure.Condition", b => { b.Property("Id") @@ -1870,6 +1919,44 @@ namespace Pf2eModel.Migrations b.Navigation("Training"); }); + modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.SubclassProvidedEntities", b => + { + b.HasOne("Pf2eModel.Model.Base.Entity", null) + .WithMany() + .HasForeignKey("EntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_subclass_provided_entities_entities_entity_id"); + + b.HasOne("Pf2eModel.Model.Structure.Subclass", null) + .WithMany() + .HasForeignKey("SubclassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_subclass_provided_entities_subclasses_subclass_id"); + }); + + modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.SubclassTrainings", b => + { + b.HasOne("Pf2eModel.Model.Structure.Subclass", "Subclass") + .WithMany("Trainings") + .HasForeignKey("SubclassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_subclass_trainings_subclasses_subclass_id"); + + b.HasOne("Pf2eModel.Model.Common.Training", "Training") + .WithMany() + .HasForeignKey("TrainingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_subclass_trainings_trainings_training_id"); + + b.Navigation("Subclass"); + + b.Navigation("Training"); + }); + modelBuilder.Entity("Pf2eModel.Model.Structure.Condition", b => { b.HasOne("Pf2eModel.Model.Base.Entity", "Entity") @@ -2198,6 +2285,11 @@ namespace Pf2eModel.Migrations b.Navigation("SpellRitual"); }); + modelBuilder.Entity("Pf2eModel.Model.Structure.Subclass", b => + { + b.Navigation("Trainings"); + }); + modelBuilder.Entity("Pf2eModel.Model.Structure.SubclassGroup", b => { b.Navigation("Subclasses"); diff --git a/Pf2eModel/Migrations/20250428065903_1.cs b/Pf2eModel/Migrations/20250505114131_1.cs similarity index 94% rename from Pf2eModel/Migrations/20250428065903_1.cs rename to Pf2eModel/Migrations/20250505114131_1.cs index 750c5af..7ac8590 100644 --- a/Pf2eModel/Migrations/20250428065903_1.cs +++ b/Pf2eModel/Migrations/20250505114131_1.cs @@ -120,7 +120,7 @@ namespace Pf2eModel.Migrations { id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + name = table.Column(type: "jsonb", nullable: false), type = table.Column(type: "integer", nullable: false) }, constraints: table => @@ -633,7 +633,6 @@ namespace Pf2eModel.Migrations .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), class_id = table.Column(type: "integer", nullable: false), training_id = table.Column(type: "integer", nullable: true), - name = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), stage = table.Column(type: "training_stage", nullable: false) }, constraints: table => @@ -1020,6 +1019,57 @@ namespace Pf2eModel.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "subclass_provided_entities", + columns: table => new + { + subclass_id = table.Column(type: "integer", nullable: false), + entity_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_subclass_provided_entities", x => new { x.subclass_id, x.entity_id }); + table.ForeignKey( + name: "fk_subclass_provided_entities_entities_entity_id", + column: x => x.entity_id, + principalTable: "entities", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_subclass_provided_entities_subclasses_subclass_id", + column: x => x.subclass_id, + principalTable: "subclasses", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "subclass_trainings", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + subclass_id = table.Column(type: "integer", nullable: false), + training_id = table.Column(type: "integer", nullable: false), + stage = table.Column(type: "training_stage", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_subclass_trainings", x => x.id); + table.ForeignKey( + name: "fk_subclass_trainings_subclasses_subclass_id", + column: x => x.subclass_id, + principalTable: "subclasses", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_subclass_trainings_trainings_training_id", + column: x => x.training_id, + principalTable: "trainings", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateIndex( name: "ix_ancestry_boosts_ancestry_id", table: "ancestry_boosts", @@ -1171,6 +1221,21 @@ namespace Pf2eModel.Migrations table: "subclass_groups", column: "class_id"); + migrationBuilder.CreateIndex( + name: "ix_subclass_provided_entities_entity_id", + table: "subclass_provided_entities", + column: "entity_id"); + + migrationBuilder.CreateIndex( + name: "ix_subclass_trainings_subclass_id", + table: "subclass_trainings", + column: "subclass_id"); + + migrationBuilder.CreateIndex( + name: "ix_subclass_trainings_training_id", + table: "subclass_trainings", + column: "training_id"); + migrationBuilder.CreateIndex( name: "ix_subclasses_group_id", table: "subclasses", @@ -1268,7 +1333,10 @@ namespace Pf2eModel.Migrations name: "spell_rituals"); migrationBuilder.DropTable( - name: "subclasses"); + name: "subclass_provided_entities"); + + migrationBuilder.DropTable( + name: "subclass_trainings"); migrationBuilder.DropTable( name: "languages"); @@ -1279,9 +1347,6 @@ namespace Pf2eModel.Migrations migrationBuilder.DropTable( name: "backgrounds"); - migrationBuilder.DropTable( - name: "trainings"); - migrationBuilder.DropTable( name: "deities"); @@ -1304,11 +1369,17 @@ namespace Pf2eModel.Migrations name: "spells"); migrationBuilder.DropTable( - name: "subclass_groups"); + name: "subclasses"); + + migrationBuilder.DropTable( + name: "trainings"); migrationBuilder.DropTable( name: "feats"); + migrationBuilder.DropTable( + name: "subclass_groups"); + migrationBuilder.DropTable( name: "classes"); diff --git a/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs b/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs index 15fd196..67456e4 100644 --- a/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs +++ b/Pf2eModel/Migrations/Pf2eDbContextModelSnapshot.cs @@ -414,8 +414,7 @@ namespace Pf2eModel.Migrations b.Property("Name") .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)") + .HasColumnType("jsonb") .HasColumnName("name") .HasColumnOrder(2); @@ -870,16 +869,10 @@ namespace Pf2eModel.Migrations .HasColumnName("class_id") .HasColumnOrder(2); - b.Property("Name") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("name") - .HasColumnOrder(4); - b.Property("Stage") .HasColumnType("training_stage") .HasColumnName("stage") - .HasColumnOrder(5); + .HasColumnOrder(4); b.Property("TrainingId") .HasColumnType("integer") @@ -898,6 +891,62 @@ namespace Pf2eModel.Migrations b.ToTable("class_trainings", (string)null); }); + modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.SubclassProvidedEntities", b => + { + b.Property("SubclassId") + .HasColumnType("integer") + .HasColumnName("subclass_id"); + + b.Property("EntityId") + .HasColumnType("integer") + .HasColumnName("entity_id"); + + b.HasKey("SubclassId", "EntityId") + .HasName("pk_subclass_provided_entities"); + + b.HasIndex("EntityId") + .HasDatabaseName("ix_subclass_provided_entities_entity_id"); + + b.ToTable("subclass_provided_entities", (string)null); + }); + + modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.SubclassTrainings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasColumnOrder(1); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Stage") + .HasColumnType("training_stage") + .HasColumnName("stage") + .HasColumnOrder(4); + + b.Property("SubclassId") + .HasColumnType("integer") + .HasColumnName("subclass_id") + .HasColumnOrder(2); + + b.Property("TrainingId") + .HasColumnType("integer") + .HasColumnName("training_id") + .HasColumnOrder(3); + + b.HasKey("Id") + .HasName("pk_subclass_trainings"); + + b.HasIndex("SubclassId") + .HasDatabaseName("ix_subclass_trainings_subclass_id"); + + b.HasIndex("TrainingId") + .HasDatabaseName("ix_subclass_trainings_training_id"); + + b.ToTable("subclass_trainings", (string)null); + }); + modelBuilder.Entity("Pf2eModel.Model.Structure.Condition", b => { b.Property("Id") @@ -1867,6 +1916,44 @@ namespace Pf2eModel.Migrations b.Navigation("Training"); }); + modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.SubclassProvidedEntities", b => + { + b.HasOne("Pf2eModel.Model.Base.Entity", null) + .WithMany() + .HasForeignKey("EntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_subclass_provided_entities_entities_entity_id"); + + b.HasOne("Pf2eModel.Model.Structure.Subclass", null) + .WithMany() + .HasForeignKey("SubclassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_subclass_provided_entities_subclasses_subclass_id"); + }); + + modelBuilder.Entity("Pf2eModel.Model.Structure.ClassRelation.SubclassTrainings", b => + { + b.HasOne("Pf2eModel.Model.Structure.Subclass", "Subclass") + .WithMany("Trainings") + .HasForeignKey("SubclassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_subclass_trainings_subclasses_subclass_id"); + + b.HasOne("Pf2eModel.Model.Common.Training", "Training") + .WithMany() + .HasForeignKey("TrainingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_subclass_trainings_trainings_training_id"); + + b.Navigation("Subclass"); + + b.Navigation("Training"); + }); + modelBuilder.Entity("Pf2eModel.Model.Structure.Condition", b => { b.HasOne("Pf2eModel.Model.Base.Entity", "Entity") @@ -2195,6 +2282,11 @@ namespace Pf2eModel.Migrations b.Navigation("SpellRitual"); }); + modelBuilder.Entity("Pf2eModel.Model.Structure.Subclass", b => + { + b.Navigation("Trainings"); + }); + modelBuilder.Entity("Pf2eModel.Model.Structure.SubclassGroup", b => { b.Navigation("Subclasses"); diff --git a/Pf2eModel/Model/Base/Targets/EntityExternal.cs b/Pf2eModel/Model/Base/Targets/EntityExternal.cs index 2081ef2..0cbdc76 100644 --- a/Pf2eModel/Model/Base/Targets/EntityExternal.cs +++ b/Pf2eModel/Model/Base/Targets/EntityExternal.cs @@ -7,18 +7,24 @@ namespace Pf2eModel.Model.Base.Targets; [PrimaryKey(nameof(EntityId), nameof(TargetId))] public class EntityExternal { - [Column(Order = 0)] + [Column(Order = 1)] public int EntityId { get; set; } - [Column(Order = 1)] + [Column(Order = 2)] public int TargetId { get; set; } - [Column(Order = 2)] + /* + [Column(Order = 3)] + [MaxLength(255)] + public string ExternalId { get; set; } = ""; + */ + + [Column(Order = 4)] [MaxLength(255)] [Comment("Часть URL до объекта. Для источников Foundry - полный UUID объекта")] public string Suffix { get; set; } = ""; - [Column(Order = 3)] + [Column(Order = 5)] [MaxLength(32)] [Comment("Контроль версии объекта")] public string Md5 { get; set; } = null!; diff --git a/Pf2eModel/Model/Common/Training.cs b/Pf2eModel/Model/Common/Training.cs index 6bb92da..f9244e6 100644 --- a/Pf2eModel/Model/Common/Training.cs +++ b/Pf2eModel/Model/Common/Training.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; namespace Pf2eModel.Model.Common; @@ -12,8 +13,7 @@ public class Training public int Id { get; set; } [Column(Order = 2)] - [MaxLength(Pf2eDbContext.NameLength)] - public string Name { get; set; } = null!; + public LocaleString Name { get; set; } = []; [Column(Order = 3)] public TrainingType Type { get; set; } diff --git a/Pf2eModel/Model/Enum/ActionEnum.cs b/Pf2eModel/Model/Enum/ActionEnum.cs new file mode 100644 index 0000000..ca54d62 --- /dev/null +++ b/Pf2eModel/Model/Enum/ActionEnum.cs @@ -0,0 +1,69 @@ +using NpgsqlTypes; + +namespace Pf2eModel.Model.Enum; + +public enum ActionEnum +{ + [PgName("unknown")] + Unknown, + + [PgName("1")] + SingleAction, + + [PgName("2")] + TwoActions, + + [PgName("3")] + ThreeActions, + + [PgName("reaction")] + Reaction, + + [PgName("free")] + FreeAction, + + [PgName("passive")] + Passive, + + [PgName("other")] + Other, + + [PgName("1-3")] + SingleActionToThreeActions, + + [PgName("1-2")] + SingleActionToTwoActions, + + [PgName("2-3")] + TwoActionsToThreeActions, + + [PgName("2/2 rounds")] + TwoActionsToTwoRounds, + + [PgName("reaction/1")] + ReactionOrOneAction, + + [PgName("reaction/2")] + ReactionOrTwoAction, + + [PgName("reaction/3")] + ReactionOrThreeAction, + + [PgName("free/1")] + FreeActionOrOneAction, + + [PgName("1 minute")] + OneMinute, + + [PgName("5 minutes")] + FiveMinutes, + + [PgName("10 minutes")] + TenMinutes, + + [PgName("30 minutes")] + ThirtyMinutes, + + [PgName("1 hour")] + OneHour, +} diff --git a/Pf2eModel/Model/Enum/SpellAreaType.cs b/Pf2eModel/Model/Enum/SpellAreaType.cs index f01b2aa..b0c266b 100644 --- a/Pf2eModel/Model/Enum/SpellAreaType.cs +++ b/Pf2eModel/Model/Enum/SpellAreaType.cs @@ -24,4 +24,16 @@ public enum SpellAreaType [PgName("cylinder")] Cylinder, + + [PgName("varies")] + Varies, + + [PgName("cuboid")] + Cuboid, + + [PgName("circle")] + Circle, + + [PgName("other")] + Other, } diff --git a/Pf2eModel/Model/Structure/Action.cs b/Pf2eModel/Model/Structure/Action.cs index ff0bbbb..1da0baf 100644 --- a/Pf2eModel/Model/Structure/Action.cs +++ b/Pf2eModel/Model/Structure/Action.cs @@ -12,22 +12,10 @@ public class Action : IEntity [Column(Order = 1)] public int Id { get; set; } + [Column(Order = 2)] + public ActionEnum Actions { get; set; } = ActionEnum.Unknown; + [Column(Order = 3)] - public int? Actions { get; set; } = null; - - [Column(Order = 4)] - public int? MaxActions { get; set; } = null; - - [Column(Order = 5)] - public bool IsReaction { get; set; } = false; - - [Column(Order = 6)] - public bool IsFreeAction { get; set; } = false; - - [Column(Order = 6)] - public bool IsPassive { get; set; } = false; - - [Column(Order = 6)] // ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength public string? Extra { get; set; } diff --git a/Pf2eModel/Model/Structure/ArchetypeRelation/ArchetypeFeats.cs b/Pf2eModel/Model/Structure/ArchetypeRelation/ArchetypeFeats.cs index 23dbba2..2828a0b 100644 --- a/Pf2eModel/Model/Structure/ArchetypeRelation/ArchetypeFeats.cs +++ b/Pf2eModel/Model/Structure/ArchetypeRelation/ArchetypeFeats.cs @@ -3,7 +3,7 @@ namespace Pf2eModel.Model.Structure.ArchetypeRelation; [PrimaryKey(nameof(ArchetypeId), nameof(FeatId))] -internal class ArchetypeFeats +public class ArchetypeFeats { public int ArchetypeId { get; set; } diff --git a/Pf2eModel/Model/Structure/ClassRelation/ClassTraining.cs b/Pf2eModel/Model/Structure/ClassRelation/ClassTraining.cs index 0cbde59..d7e66ac 100644 --- a/Pf2eModel/Model/Structure/ClassRelation/ClassTraining.cs +++ b/Pf2eModel/Model/Structure/ClassRelation/ClassTraining.cs @@ -20,10 +20,6 @@ public class ClassTraining public int? TrainingId { get; set; } [Column(Order = 4)] - [MaxLength(Pf2eDbContext.NameLength)] - public string? Name { get; set; } - - [Column(Order = 5)] public TrainingStage Stage { get; set; } [JsonIgnore] diff --git a/Pf2eModel/Model/Structure/ClassRelation/SubclassProvidedEntities.cs b/Pf2eModel/Model/Structure/ClassRelation/SubclassProvidedEntities.cs new file mode 100644 index 0000000..39d5869 --- /dev/null +++ b/Pf2eModel/Model/Structure/ClassRelation/SubclassProvidedEntities.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; + +namespace Pf2eModel.Model.Structure.ClassRelation; + +[PrimaryKey(nameof(SubclassId), nameof(EntityId))] +public class SubclassProvidedEntities +{ + public int SubclassId { get; set; } + + public int EntityId { get; set; } +} diff --git a/Pf2eModel/Model/Structure/ClassRelation/SubclassTrainings.cs b/Pf2eModel/Model/Structure/ClassRelation/SubclassTrainings.cs new file mode 100644 index 0000000..75ac908 --- /dev/null +++ b/Pf2eModel/Model/Structure/ClassRelation/SubclassTrainings.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Pf2eModel.Model.Common; +using Pf2eModel.Model.Enum; + +namespace Pf2eModel.Model.Structure.ClassRelation; + +public class SubclassTrainings +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Column(Order = 1)] + public int Id { get; set; } + + [Column(Order = 2)] + public int SubclassId { get; set; } + + [Column(Order = 3)] + public int TrainingId { get; set; } + + [Column(Order = 4)] + public TrainingStage Stage { get; set; } = TrainingStage.Trained; + + public Subclass Subclass { get; set; } = null!; + + public Training Training { get; set; } = null!; +} diff --git a/Pf2eModel/Model/Structure/Spell.cs b/Pf2eModel/Model/Structure/Spell.cs index b0c6637..a5d6bca 100644 --- a/Pf2eModel/Model/Structure/Spell.cs +++ b/Pf2eModel/Model/Structure/Spell.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Pf2eModel.Model.Base; +using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; using Pf2eModel.Model.Structure.SpellRelation; @@ -20,7 +21,12 @@ public class Spell : IEntity public int Level { get; set; } [Column(Order = 3)] - public int Cost { get; set; } + public ActionEnum Actions { get; set; } = ActionEnum.Other; + + [Column(Order = 4)] + public bool IsFocus { get; set; } + + // --- [Column(Order = 4)] public bool Counteraction { get; set; } @@ -51,7 +57,9 @@ public class Spell : IEntity [Column(Order = 11)] public bool CanHeightening { get; set; } - public virtual SpellArea? SpellArea { get; set; } + // --- + + public virtual ICollection SpellAreas { get; set; } = []; public virtual SpellRitual? SpellRitual { get; set; } diff --git a/Pf2eModel/Model/Structure/SpellRelation/SpellArea.cs b/Pf2eModel/Model/Structure/SpellRelation/SpellArea.cs index ed9dae6..1480916 100644 --- a/Pf2eModel/Model/Structure/SpellRelation/SpellArea.cs +++ b/Pf2eModel/Model/Structure/SpellRelation/SpellArea.cs @@ -9,16 +9,19 @@ public class SpellArea [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] [Column(Order = 1)] - public int SpellId { get; set; } + public int Id { get; set; } [Column(Order = 2)] + public int SpellId { get; set; } + + [Column(Order = 3)] [MaxLength(255)] public string? Details { get; set; } - [Column(Order = 3)] + [Column(Order = 4)] public SpellAreaType Type { get; set; } - [Column(Order = 4)] + [Column(Order = 5)] public int Value { get; set; } public virtual Spell Spell { get; set; } = null!; diff --git a/Pf2eModel/Model/Structure/Subclass.cs b/Pf2eModel/Model/Structure/Subclass.cs index 6e418ca..8b1e4e3 100644 --- a/Pf2eModel/Model/Structure/Subclass.cs +++ b/Pf2eModel/Model/Structure/Subclass.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema; using Pf2eModel.Model.Base; using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Enum; +using Pf2eModel.Model.Structure.ClassRelation; namespace Pf2eModel.Model.Structure; @@ -26,4 +27,8 @@ public class Subclass : IEntity [Column(Order = 4)] // ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength public string Extra { get; set; } = "{}"; + + public virtual ICollection Entities { get; set; } = []; + + public virtual ICollection Trainings { get; set; } = []; } diff --git a/Pf2eModel/Pf2eDbContextModel.cs b/Pf2eModel/Pf2eDbContextModel.cs index bffb505..da54736 100644 --- a/Pf2eModel/Pf2eDbContextModel.cs +++ b/Pf2eModel/Pf2eDbContextModel.cs @@ -159,7 +159,10 @@ public partial class Pf2eDbContext modelBuilder.Entity(entity => { }); - modelBuilder.Entity(entity => { }); + modelBuilder.Entity(entity => + { + BindLocaleString(entity, t => t.Name); + }); } private void DefineStructure(ModelBuilder modelBuilder) @@ -331,6 +334,28 @@ public partial class Pf2eDbContext .WithMany(g => g.Subclasses) .HasForeignKey(s => s.GroupId) .OnDelete(DeleteBehavior.Cascade); + + entity + .HasMany(e => e.Entities) + .WithMany() + .UsingEntity( + l => l.HasOne().WithMany().HasForeignKey(se => se.EntityId), + r => r.HasOne().WithMany().HasForeignKey(se => se.SubclassId) + ); + + entity + .HasMany(e => e.Trainings) + .WithOne(t => t.Subclass) + .HasForeignKey(t => t.SubclassId) + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity(entity => + { + entity + .HasOne(d => d.Subclass) + .WithMany(p => p.Trainings) + .HasForeignKey(d => d.SubclassId) + .OnDelete(DeleteBehavior.Cascade); }); // Condition @@ -413,11 +438,10 @@ public partial class Pf2eDbContext BindEntity(entity); entity - .HasOne(e => e.SpellArea) + .HasMany(e => e.SpellAreas) .WithOne(e => e.Spell) - .HasForeignKey(e => e.SpellId) - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(false); + .HasForeignKey(e => e.SpellId) + .OnDelete(DeleteBehavior.Cascade); entity .HasOne(e => e.SpellRitual)