Большая пачка обновлений

This commit is contained in:
2025-04-21 08:36:46 +04:00
parent be8963a073
commit beb61b7449
39 changed files with 1068 additions and 301 deletions
@@ -1,4 +1,6 @@
using Microsoft.EntityFrameworkCore;
using LinqToDB.Reflection;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Pf2eModel;
using Pf2eModel.Model;
@@ -13,13 +15,13 @@ using System.Text.RegularExpressions;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
internal abstract partial class AbstractProcessor(ProcessorInput input)
internal abstract partial class AbstractProcessor(AonprdProcessorInput input)
{
protected DatabaseHelper Helper = DatabaseHelper.Instance;
public readonly Pf2eDbContext Context = DatabaseHelper.DbContext;
public readonly ProcessorInput Input = input;
public readonly AonprdProcessorInput Input = input;
public Entity Entity { get; set; } = new();
@@ -27,6 +29,8 @@ internal abstract partial class AbstractProcessor(ProcessorInput input)
public abstract IEntity Object { get; }
protected abstract void Add();
public virtual void Process()
{
if (Input.GetOptional<JArray>("remaster_id") is not null)
@@ -47,6 +51,7 @@ internal abstract partial class AbstractProcessor(ProcessorInput input)
public void Save()
{
Add();
Context.SaveChanges();
}
@@ -68,6 +73,7 @@ internal abstract partial class AbstractProcessor(ProcessorInput input)
Entity.IsTranslated = false;
Entity.IsChecked = false;
Object.Entity = Entity;
Object.Id = Entity.Id;
Context.Entities.Add(Entity);
@@ -229,6 +235,18 @@ internal abstract partial class AbstractProcessor(ProcessorInput input)
}
}
public string GenerateExtra(params string[] args)
{
Dictionary<string, object?> data = [];
foreach (string key in args)
{
data.Add(key, Input.Input.SelectToken(key));
}
return JsonConvert.SerializeObject(data, new JsonSerializerSettings() { });
}
public static string Md5(string input, bool isLowercase = false)
{
using MD5 md5 = MD5.Create();
@@ -247,4 +265,6 @@ internal abstract partial class AbstractProcessor(ProcessorInput input)
private static partial Regex SourceSuffixRegex();
public class LegacyItemException(string name) : Exception($"Item '{name}' is linked with newer version, skipping.") { }
public class SkipItemException(string name, string reason) : Exception($"Item '{name}' is skipped, reason: {reason}.") { }
}
@@ -39,7 +39,7 @@ internal class AonprdDataBuilder(AonprdLoader loader)
Window output = new(0, 4, Console.WindowWidth, Console.WindowHeight - 4, window);
ProgressBar bar1 = new(bars, Loader.ItemCount, 15);
ProgressBar bar1 = new(bars, Processors.Items.Count, 15);
ProgressBar bar2 = new(bars, 1, 15);
int i = 0;
@@ -66,6 +66,10 @@ internal class AonprdDataBuilder(AonprdLoader loader)
{
output.WriteLine(ex.Message);
}
catch (SkipItemException ex)
{
output.WriteLine(ex.Message);
}
}
bar2.Refresh(j, "Processor completed");
@@ -4,19 +4,6 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
public static class AonprdEnumResolver
{
private static readonly ICollection<Solver> Solvers = [
new Solver<ActionType>(new()
{
{ "Single Action", ActionType.SingleAction },
{ "Reaction or One Action", ActionType.SingleAction },
{ "Free Action or One Action", ActionType.SingleAction },
{ "Two Actions", ActionType.TwoActions },
{ "Three Actions", ActionType.ThreeActions },
{ "Single Action to Three Actions", ActionType.SingleToThreeActions },
{ "Single Action or more Actions", ActionType.SingleToThreeActions },
{ "Single Action or Two Actions", ActionType.SingleToTwoActions },
{ "Single Action to Two Actions", ActionType.SingleToTwoActions },
}),
new Solver<SourceCategory>(new()
{
{ "Rulebooks", SourceCategory.Rulebooks },
@@ -36,7 +23,9 @@ public static class AonprdEnumResolver
new Solver<AttributeValues>(new()
{
{ "Consitution", AttributeValues.Constitution }
{ "Consitution", AttributeValues.Constitution },
{ "Other", AttributeValues.Free },
{ "other", AttributeValues.Free },
})
];
@@ -9,13 +9,13 @@ public class AonprdProcessorAttribute(string name, string type, string[] depende
public readonly IEnumerable<string> Dependencies = dependencies;
public AonprdProcessorAttribute(string type) : this(type, type, ["trait"])
public AonprdProcessorAttribute(string type) : this(type, type, ["trait", "source"])
{
}
public AonprdProcessorAttribute(string type, string[] dependencies) : this(type, type, dependencies)
{
}
public AonprdProcessorAttribute(string name, string type) : this(name, type, ["trait"])
public AonprdProcessorAttribute(string name, string type) : this(name, type, ["trait", "source"])
{
}
}
@@ -31,7 +31,7 @@ internal class AonprdProcessorCollection
AonprdProcessorAttribute attribute = type.GetCustomAttribute<AonprdProcessorAttribute>()!;
ConstructorInfo constructor = type.GetConstructor([typeof(ProcessorInput)])!;
ConstructorInfo constructor = type.GetConstructor([typeof(AonprdProcessorInput)])!;
ProcessorItem? collision = Items.FirstOrDefault(i => i.Attribute.Name == attribute.Name);
if (collision is not null)
@@ -15,4 +15,80 @@ internal class AonprdProcessorInput(JObject input) : ProcessorInput(input)
return base.Enum<T>(value);
}
}
public ActionStruct ResolveActionString(string input)
{
ActionStruct result = new();
switch (input)
{
case "Single Action":
result.Actions = result.MaxActions = 1;
break;
case "Two Actions":
result.Actions = result.MaxActions = 2;
break;
case "Three Actions":
result.Actions = result.MaxActions = 3;
break;
case "Reaction":
result.IsReaction = true;
break;
case "Free Action":
result.IsFreeAction = false;
break;
case "Single Action to Three Actions":
case "Single Action or more Actions":
result.Actions = 1;
result.MaxActions = 3;
break;
case "Single Action or Two Actions":
case "Single Action to Two Actions":
result.Actions = 1;
result.MaxActions = 2;
break;
case "Two Actions or Three Actions":
result.Actions = 2;
result.MaxActions = 3;
break;
case "Reaction or One Action":
result.IsReaction = true;
result.Actions = result.MaxActions = 1;
break;
case "Reaction or Two Actions":
result.IsReaction = true;
result.Actions = result.MaxActions = 2;
break;
case "Free Action or One Action":
result.IsFreeAction = false;
result.Actions = result.MaxActions = 1;
break;
default:
result.IsPassive = true;
break;
}
return result;
}
public class ActionStruct
{
public int Actions = 0;
public int MaxActions = 0;
public bool IsPassive = false;
public bool IsReaction = false;
public bool IsFreeAction = false;
}
}
@@ -1,75 +1,31 @@
using Newtonsoft.Json.Linq;
using Pf2eModel.Model;
using Pf2eModel.Model.Enum;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
[AonprdProcessor("action")]
internal class ActionProcessor(ProcessorInput input) : AbstractProcessor(input)
internal class ActionProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public override EntityType Type => EntityType.Action;
public override Pf2eModel.Model.Structure.Action Object { get; } = new();
protected override void Add() => Context.Actions.Add(Object);
public override void Process()
{
base.Process();
switch (Input.GetOptional<string>("actions"))
{
case "Single Action":
Object.Actions = Object.MaxActions = 1;
break;
AonprdProcessorInput.ActionStruct action = Input.ResolveActionString(Input.GetOptional<string>("actions") ?? "");
case "Two Actions":
Object.Actions = Object.MaxActions = 2;
break;
case "Three Actions":
Object.Actions = Object.MaxActions = 3;
break;
case "Reaction":
Object.IsReaction = true;
break;
case "Free Action":
Object.IsFreeAction = false;
break;
case "Single Action to Three Actions":
case "Single Action or more Actions":
Object.Actions = 1;
Object.MaxActions = 3;
break;
case "Single Action or Two Actions":
case "Single Action to Two Actions":
Object.Actions = 1;
Object.MaxActions = 2;
break;
case "Reaction or One Action":
Object.IsReaction = true;
Object.Actions = Object.MaxActions = 1;
break;
case "Free Action or One Action":
Object.IsFreeAction = false;
Object.Actions = Object.MaxActions = 1;
break;
default:
Object.IsPassive = true;
break;
}
Object.Extra = new JObject()
{
{ "school", Input.GetOptional<JToken>("school") },
}.ToString();
Context.Actions.Add(Object);
Object.Actions = action.Actions;
Object.MaxActions = action.MaxActions;
Object.IsFreeAction = action.IsFreeAction;
Object.IsPassive = action.IsPassive;
Object.IsReaction = action.IsReaction;
;
Object.Extra = GenerateExtra("school");
}
protected override string GetDescription()
@@ -9,11 +9,13 @@ using System.Text.RegularExpressions;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
[AonprdProcessor("ancestry", ["trait", "language"])]
internal class AncestryProcessor(ProcessorInput input) : AbstractProcessor(input)
internal class AncestryProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public override EntityType Type { get; } = EntityType.Ancestry;
public override EntityType Type => EntityType.Ancestry;
public override Ancestry Object { get; } = new Ancestry();
public override Ancestry Object { get; } = new();
protected override void Add() => Context.Ancestries.Add(Object);
public override void Process()
{
@@ -21,7 +23,7 @@ internal class AncestryProcessor(ProcessorInput input) : AbstractProcessor(input
if (type != "Ancestry")
{
return;
throw new SkipItemException(Input.GetRequired<string>("name"), "not an ancestry");
}
base.Process();
@@ -66,13 +68,4 @@ internal class AncestryProcessor(ProcessorInput input) : AbstractProcessor(input
}
}
protected override string GetDescription()
=> base.GetDescription() + AddedDescription([
"source_markdown",
"summary_markdown",
"trait_markdown",
"search_markdown"
]);
}
@@ -0,0 +1,50 @@
using Newtonsoft.Json.Linq;
using Pf2eModel.Model;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
using Pf2eModel.Model.Structure.BackgroundRelation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
[AonprdProcessor("background")]
internal class BackgroundProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public override EntityType Type => EntityType.Background;
public override Background Object { get; } = new();
protected override void Add() => Context.Backgrounds.Add(Object);
public override void Process()
{
base.Process();
foreach (JToken token in Input.GetOptional<JArray>("attribute") ?? [])
{
BackgroundBoost boost = new()
{
Background = Object,
Attribute = Input.Enum<AttributeValues>(token.ToString())
};
Object.BackgroundBoosts.Add(boost);
Context.BackgroundBoosts.Add(boost);
}
}
protected override string GetDescription()
=> base.GetDescription() + AddedDescription([
"feat_markdown",
"summary_markdown",
"skill_markdown",
"prerequisite_markdown",
]);
}
@@ -0,0 +1,195 @@
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
using Pf2eModel.Model;
using Pf2eModel.Model.Common;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
using Pf2eModel.Model.Structure.ClassRelation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
[AonprdProcessor("class", ["trait", "source", "skill"])]
internal class ClassProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public override EntityType Type => EntityType.Class;
public override Class Object { get; } = new();
protected override void Add() => Context.Classes.Add(Object);
public override void Process()
{
base.Process();
Object.Hp = Convert.ToInt32(Input.GetRequired<string>("hp_raw"));
AttributeValues[] attributes = Input.GetRequired<JArray>("attribute")
.Select(token => Input.Enum<AttributeValues>(token.ToString()))
.ToArray();
foreach (AttributeValues attribute in attributes)
{
ClassKeyAbility classKeyAbility = new ClassKeyAbility()
{
Class = Object,
Value = attribute
};
Context.ClassKeyAbilities.Add(classKeyAbility);
Object.ClassKeyAbilities.Add(classKeyAbility);
}
ClassTraining[] classTrainings =
[
.. ProcessTraining(Input.GetRequired<JArray>("attack_proficiency"), TrainingType.Weapon),
.. ProcessTraining(Input.GetRequired<JArray>("defense_proficiency"), TrainingType.Armor),
.. GetSkillTrainings(),
.. GetSaveTrainings(),
];
foreach (ClassTraining classTraining in classTrainings)
{
Context.ClassTrainings.Add(classTraining);
Object.ClassTrainings.Add(classTraining);
}
JArray tradition = Input.GetOptional<JArray>("tradition") ?? [];
switch (tradition.Count)
{
case 0:
Object.Spellcasting = false;
Object.Tradition = SpellTradition.None;
break;
case 1:
Object.Spellcasting = true;
Object.Tradition = Input.Enum<SpellTradition>(tradition[0].ToString());
break;
default:
Object.Spellcasting = true;
Object.Tradition = SpellTradition.All;
break;
}
}
protected ICollection<ClassTraining> GetSkillTrainings()
{
string[] skillNames = Input.GetRequired<JArray>("skill_proficiency")
.Select(token => token.ToString())
.ToArray();
Training[] skills = Context.Trainings.Where(t => skillNames.Contains(t.Name)).ToArray();
return skills.Select(skill => new ClassTraining()
{
Class = Object,
Training = skill,
Stage = TrainingStage.Trained,
}).ToArray();
}
protected ICollection<ClassTraining> ProcessTraining(JArray array, TrainingType type)
{
const string pattern = @"^(.*?) in\s+(?:the)?\s*(.*)$";
List<ClassTraining> trainings = [];
foreach (string value in array.Select(token => token.ToString()))
{
MatchCollection m = Regex.Matches(value, pattern);
if (m.Count == 0)
{
throw new Exception($"Can't parse training string '{value}'");
}
TrainingStage stage = Input.Enum<TrainingStage>(m[0].Groups[1].Value);
string[] parts = GetTrainingString(m[0].Groups[2].Value);
foreach (string part in parts)
{
Training? training = Context.Trainings
.FirstOrDefault(t => t.Name == part && t.Type == type);
if (training == null)
{
training = new Training()
{
Name = part,
Type = type,
};
Context.Trainings.Add(training);
}
ClassTraining classTraining = new()
{
Class = Object,
Training = training,
Stage = stage
};
trainings.Add(classTraining);
}
}
return trainings;
}
protected string[] GetTrainingString(string value)
{
if (value.StartsWith("favored weapon of your deity"))
{
return ["Favored weapon of your deity"];
}
if (value == "simple and martial weapons")
{
return ["Simple weapon", "Martial weapon"];
}
return Regex.Split(value, @"\s*(?:,|and)\s*")
.Where(s => s != "")
.Select(s => string.Concat(s[0].ToString().ToUpper(), s.AsSpan(1)))
.ToArray();
;
}
protected ICollection<ClassTraining> GetSaveTrainings()
{
Dictionary<string, Training> result = Context.Trainings
.Where(t => t.Type == TrainingType.Save)
.ToDictionary(t => t.Name);
foreach (string name in new[] { "Will", "Fortitude", "Reflex", "Perception" })
{
if (result.ContainsKey(name))
{
continue;
}
Training training = new() { Name = name, Type = (name == "Perception") ? TrainingType.Perception : TrainingType.Save };
result.Add(name, training);
Context.Trainings.Add(training);
}
return result.Select(pair =>
{
ClassTraining classTraining = new()
{
Class = Object,
Training = pair.Value,
Stage = Input.EnumRequired<TrainingStage>($"{pair.Key.ToLower()}_proficiency"),
};
return classTraining;
}).ToList();
}
}
@@ -0,0 +1,61 @@
using Newtonsoft.Json.Linq;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
[AonprdProcessor("feat", ["trait", "source", "skill"])]
internal class FeatProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public override EntityType Type => EntityType.Feat;
public override Feat Object { get; } = new();
protected override void Add() => Context.Feats.Add(Object);
public override void Process()
{
base.Process();
Object.Level = Input.GetRequired<int>("level");
AonprdProcessorInput.ActionStruct action = Input.ResolveActionString(Input.GetOptional<string>("actions") ?? "");
Object.Actions = action.Actions;
Object.MaxActions = action.MaxActions;
Object.IsFreeAction = action.IsFreeAction;
Object.IsPassive = action.IsPassive;
Object.IsReaction = action.IsReaction;
Object.Extra = GenerateExtra(
"archetype",
"element",
"heighten",
"heighten_group",
"heighten_level",
"school",
"frequency"
);
}
protected override string GetDescription()
=> base.GetDescription() + AddedDescription([
"cost_markdown",
"feat_markdown",
"summary_markdown",
"prerequisite_markdown",
"requirement_markdown",
"trigger_markdown"
]);
}
@@ -4,10 +4,16 @@ using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
[AonprdProcessor("language")]
internal class LanguageProcessor(ProcessorInput input) : AbstractProcessor(input)
internal class LanguageProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public override EntityType Type { get; } = EntityType.Language;
public override EntityType Type => EntityType.Language;
public override Language Object { get; } = new Language();
public override Language Object { get; } = new();
protected override void Add() => Context.Languages.Add(Object);
public override void Process()
{
base.Process();
}
}
@@ -0,0 +1,35 @@
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
using Pf2eModel.Model;
using Pf2eModel.Model.Base.Source;
using Pf2eModel.Model.Enum;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
[AonprdProcessor("source", new string[] { })]
internal class SourceProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public override EntityType Type => EntityType.Unknown;
public override IEntity Object => null;
protected override void Add() { }
public override void Process()
{
// base.Process();
Source source = new()
{
Name = Input.GetRequired<string>("name"),
Category = Input.EnumRequired<SourceCategory>("primary_source_category"),
ReleaseDate = DateOnly.Parse(Input.GetRequired<string>("release_date")),
};
Context.Sources.Add(source);
}
}
@@ -0,0 +1,34 @@
using Pf2eModel.Model.Base.Source;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model;
using Pf2eModel.Model.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
[AonprdProcessor("skill", new string[] { })]
internal class TrainingProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public override EntityType Type => EntityType.Unknown;
public override IEntity Object => null;
protected override void Add() { }
public override void Process()
{
// base.Process();
Training training = new()
{
Name = Input.GetRequired<string>("name"),
Type = TrainingType.Skill,
};
Context.Trainings.Add(training);
}
}
@@ -4,12 +4,14 @@ using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
[AonprdProcessor("trait", new string[] { })]
internal class TraitProcessor(ProcessorInput input) : AbstractProcessor(input)
internal class TraitProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public override EntityType Type => EntityType.Trait;
public override Trait Object { get; } = new();
protected override void Add() => Context.Traits.Add(Object);
public override void Process()
{
base.Process();