mirror of
https://github.com/xivdev/EXDSchema.git
synced 2025-06-05 23:57:46 +00:00
Commit first-pass of schemas and schema validator
This commit is contained in:
parent
4a605383ec
commit
7b6e016950
816 changed files with 13870 additions and 123 deletions
|
@ -226,7 +226,7 @@ public class SchemaConverter
|
|||
return (null, null);
|
||||
}
|
||||
var condition = new Condition();
|
||||
condition.Switch = oldLink.Links[0].When.Key;
|
||||
condition.Switch = Util.StripDefinitionName(oldLink.Links[0].When.Key);
|
||||
condition.Cases = new Dictionary<int, List<string>>();
|
||||
foreach (var oldLinkLink in oldLink.Links)
|
||||
condition.Cases.Add(oldLinkLink.When.Value, oldLinkLink.LinkedSheet == null ? oldLinkLink.Sheets : new List<string> { oldLinkLink.LinkedSheet });
|
||||
|
|
|
@ -1,48 +1,53 @@
|
|||
// using YamlDotNet.Core;
|
||||
// using YamlDotNet.Core.Events;
|
||||
// using YamlDotNet.Serialization;
|
||||
// using YamlDotNet.Serialization.EventEmitters;
|
||||
// using YamlDotNet.Serialization.NamingConventions;
|
||||
//
|
||||
// namespace SchemaConverter;
|
||||
//
|
||||
// public static class SerializeUtil
|
||||
// {
|
||||
// private static readonly ISerializer _serializer;
|
||||
//
|
||||
// static SerializeUtil()
|
||||
// {
|
||||
// _serializer = new SerializerBuilder()
|
||||
// .WithIndentedSequences()
|
||||
// .WithNamingConvention(CamelCaseNamingConvention.Instance)
|
||||
// .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults)
|
||||
// .DisableAliases()
|
||||
// // .WithEventEmitter(nextEmitter => new FlowEverythingEmitter(nextEmitter))
|
||||
// .Build();
|
||||
// }
|
||||
//
|
||||
// public static string Serialize(object o)
|
||||
// {
|
||||
// return _serializer.Serialize(o);
|
||||
// }
|
||||
//
|
||||
// public class FlowEverythingEmitter : ChainedEventEmitter
|
||||
// {
|
||||
// public FlowEverythingEmitter(IEventEmitter nextEmitter) : base(nextEmitter) { }
|
||||
//
|
||||
// public override void Emit(MappingStartEventInfo eventInfo, IEmitter emitter)
|
||||
// {
|
||||
// Console.WriteLine($"Type: {eventInfo.Source.Type} Style: {eventInfo.Source.StaticType} Value: {eventInfo.Source.Value}");
|
||||
//
|
||||
// eventInfo.Style = MappingStyle.Flow;
|
||||
// base.Emit(eventInfo, emitter);
|
||||
// }
|
||||
//
|
||||
// public override void Emit(SequenceStartEventInfo eventInfo, IEmitter emitter)
|
||||
// {
|
||||
// Console.WriteLine($"Type: {eventInfo.Source.Type} StaticType: {eventInfo.Source.StaticType} Value: {eventInfo.Source.Value}");
|
||||
// eventInfo.Style = SequenceStyle.Flow;
|
||||
// nextEmitter.Emit(eventInfo, emitter);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
using SharpYaml;
|
||||
using SharpYaml.Events;
|
||||
using SharpYaml.Serialization;
|
||||
using SharpYaml.Serialization.Serializers;
|
||||
|
||||
namespace SchemaConverter;
|
||||
|
||||
public static class SerializeUtil
|
||||
{
|
||||
private static readonly Serializer _serializer;
|
||||
|
||||
static SerializeUtil()
|
||||
{
|
||||
var settings = new SerializerSettings
|
||||
{
|
||||
EmitAlias = false,
|
||||
EmitDefaultValues = false,
|
||||
NamingConvention = new CamelCaseNamingConvention(),
|
||||
IgnoreNulls = true,
|
||||
};
|
||||
settings.RegisterSerializer(typeof(Dictionary<int, List<string>>), new CustomDictionarySerializer());
|
||||
settings.RegisterSerializer(typeof(New.FieldType), new CustomFieldTypeSerializer());
|
||||
|
||||
_serializer = new Serializer(settings);
|
||||
}
|
||||
|
||||
public static string Serialize(object o)
|
||||
{
|
||||
return _serializer.Serialize(o);
|
||||
}
|
||||
}
|
||||
|
||||
internal class CustomDictionarySerializer : DictionarySerializer
|
||||
{
|
||||
protected override void WriteDictionaryItem(ref ObjectContext objectContext, KeyValuePair<object, object?> keyValue, KeyValuePair<Type, Type> types)
|
||||
{
|
||||
objectContext.SerializerContext.WriteYaml(keyValue.Key, types.Key);
|
||||
objectContext.SerializerContext.WriteYaml(keyValue.Value, types.Value, YamlStyle.Flow);
|
||||
}
|
||||
}
|
||||
|
||||
internal class CustomFieldTypeSerializer : ScalarSerializerBase
|
||||
{
|
||||
public override object? ConvertFrom(ref ObjectContext context, Scalar fromScalar)
|
||||
{
|
||||
return Enum.Parse<New.FieldType>(new PascalNamingConvention().Convert(fromScalar.Value));
|
||||
}
|
||||
|
||||
public override string ConvertTo(ref ObjectContext objectContext)
|
||||
{
|
||||
return objectContext.Settings.NamingConvention.Convert(objectContext.Instance.ToString());
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// using YamlMap;
|
||||
//
|
||||
// namespace SchemaConverter;
|
||||
//
|
||||
// public static class SerializeUtil2
|
||||
// {
|
||||
// private static readonly YamlWriter _serializer;
|
||||
//
|
||||
// static SerializeUtil2()
|
||||
// {
|
||||
// _serializer = new YamlWriter();
|
||||
// }
|
||||
//
|
||||
// public static string Serialize(object o)
|
||||
// {
|
||||
// return _serializer.Write(o);
|
||||
// }
|
||||
// }
|
|
@ -1,53 +0,0 @@
|
|||
using SharpYaml;
|
||||
using SharpYaml.Events;
|
||||
using SharpYaml.Serialization;
|
||||
using SharpYaml.Serialization.Serializers;
|
||||
|
||||
namespace SchemaConverter;
|
||||
|
||||
public static class SerializeUtil3
|
||||
{
|
||||
private static readonly Serializer _serializer;
|
||||
|
||||
static SerializeUtil3()
|
||||
{
|
||||
var settings = new SerializerSettings
|
||||
{
|
||||
EmitAlias = false,
|
||||
EmitDefaultValues = false,
|
||||
NamingConvention = new CamelCaseNamingConvention(),
|
||||
IgnoreNulls = true,
|
||||
};
|
||||
settings.RegisterSerializer(typeof(Dictionary<int, List<string>>), new CustomDictionarySerializer());
|
||||
settings.RegisterSerializer(typeof(New.FieldType), new CustomFieldTypeSerializer());
|
||||
|
||||
_serializer = new Serializer(settings);
|
||||
}
|
||||
|
||||
public static string Serialize(object o)
|
||||
{
|
||||
return _serializer.Serialize(o);
|
||||
}
|
||||
}
|
||||
|
||||
internal class CustomDictionarySerializer : DictionarySerializer
|
||||
{
|
||||
protected override void WriteDictionaryItem(ref ObjectContext objectContext, KeyValuePair<object, object?> keyValue, KeyValuePair<Type, Type> types)
|
||||
{
|
||||
objectContext.SerializerContext.WriteYaml(keyValue.Key, types.Key);
|
||||
objectContext.SerializerContext.WriteYaml(keyValue.Value, types.Value, YamlStyle.Flow);
|
||||
}
|
||||
}
|
||||
|
||||
internal class CustomFieldTypeSerializer : ScalarSerializerBase
|
||||
{
|
||||
public override object? ConvertFrom(ref ObjectContext context, Scalar fromScalar)
|
||||
{
|
||||
return Enum.Parse<New.FieldType>(new PascalNamingConvention().Convert(fromScalar.Value));
|
||||
}
|
||||
|
||||
public override string ConvertTo(ref ObjectContext objectContext)
|
||||
{
|
||||
return objectContext.Settings.NamingConvention.Convert(objectContext.Instance.ToString());
|
||||
}
|
||||
}
|
46
SchemaValidator/DefinedColumn.cs
Normal file
46
SchemaValidator/DefinedColumn.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using Lumina.Data.Structs.Excel;
|
||||
using SchemaValidator.New;
|
||||
|
||||
namespace SchemaValidator;
|
||||
|
||||
public class DefinedColumn
|
||||
{
|
||||
public ExcelColumnDefinition Definition { get; set; }
|
||||
public Field Field { get; set; }
|
||||
|
||||
private int? _bitOffset;
|
||||
public int BitOffset {
|
||||
get
|
||||
{
|
||||
if (_bitOffset == null)
|
||||
_bitOffset = CalculateBitOffset(Definition.Offset, Definition.Type);
|
||||
return _bitOffset.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => $"{Field} @ 0x{BitOffset / 8:X}&{BitOffset % 8}";
|
||||
|
||||
public static int CalculateBitOffset(ExcelColumnDefinition def)
|
||||
{
|
||||
return CalculateBitOffset(def.Offset, def.Type);
|
||||
}
|
||||
|
||||
public static int CalculateBitOffset(int offset, ExcelColumnDataType type)
|
||||
{
|
||||
var bitOffset = offset * 8;
|
||||
return type switch
|
||||
{
|
||||
ExcelColumnDataType.PackedBool0 => bitOffset + 0,
|
||||
ExcelColumnDataType.PackedBool1 => bitOffset + 1,
|
||||
ExcelColumnDataType.PackedBool2 => bitOffset + 2,
|
||||
ExcelColumnDataType.PackedBool3 => bitOffset + 3,
|
||||
ExcelColumnDataType.PackedBool4 => bitOffset + 4,
|
||||
ExcelColumnDataType.PackedBool5 => bitOffset + 5,
|
||||
ExcelColumnDataType.PackedBool6 => bitOffset + 6,
|
||||
ExcelColumnDataType.PackedBool7 => bitOffset + 7,
|
||||
_ => bitOffset,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
60
SchemaValidator/NewSheetDefinition.cs
Normal file
60
SchemaValidator/NewSheetDefinition.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace SchemaValidator.New;
|
||||
|
||||
public enum FieldType
|
||||
{
|
||||
Scalar,
|
||||
Array,
|
||||
Icon,
|
||||
ModelId,
|
||||
Color,
|
||||
Link,
|
||||
}
|
||||
|
||||
public class Sheet
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string? DisplayField { get; set; }
|
||||
public List<Field> Fields { get; set; }
|
||||
}
|
||||
|
||||
public class Field
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public int? Count { get; set; }
|
||||
[DefaultValue(FieldType.Scalar)]
|
||||
[JsonConverter(typeof(StringEnumConverter), true)]
|
||||
public FieldType Type { get; set; }
|
||||
public string? Comment { get; set; }
|
||||
public List<Field>? Fields { get; set; }
|
||||
public Condition? Condition { get; set; }
|
||||
public List<string>? Targets { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var arraySuffix = Count.HasValue ? $"[{Count}]" : "";
|
||||
var name = Name != null ? $"{Name}{arraySuffix}" : "Unknown";
|
||||
return $"{name} ({Type})";
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is not Field other)
|
||||
return false;
|
||||
var fieldsEqual = (Fields == null && other.Fields == null) || (Fields != null && other.Fields != null && Fields.SequenceEqual(other.Fields));
|
||||
var targetsEqual = (Targets == null && other.Targets == null) || (Targets != null && other.Targets != null && Targets.SequenceEqual(other.Targets));
|
||||
return Name == other.Name && Count == other.Count && Type == other.Type && Comment == other.Comment && Condition == other.Condition && fieldsEqual && targetsEqual;
|
||||
}
|
||||
}
|
||||
|
||||
public class Condition
|
||||
{
|
||||
public string? Switch { get; set; }
|
||||
public Dictionary<int, List<string>>? Cases { get; set; }
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
// See https://aka.ms/new-console-template for more information
|
||||
|
||||
Console.WriteLine("Hello, World!");
|
213
SchemaValidator/SchemaSchema.json
Normal file
213
SchemaValidator/SchemaSchema.json
Normal file
|
@ -0,0 +1,213 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$ref": "#/definitions/Sheet",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"sheet": {
|
||||
"$ref": "#/definitions/Sheet"
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"Sheet": {
|
||||
"title": "Sheet",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "The name of the sheet.",
|
||||
"type": "string"
|
||||
},
|
||||
"displayField": {
|
||||
"description": "The name of the field to use for displaying a reference to this sheet in a cell. Useful only for UI-based consumption.",
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"description": "The fields of the sheet. Sheets must specify all fields present in the EXH file for that sheet, meaning they all must have at least one field.",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/definitions/Field"
|
||||
}
|
||||
},
|
||||
"comment": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{ "required": ["name", "fields"] }
|
||||
]
|
||||
},
|
||||
"Field": {
|
||||
"description": "A field in a sheet. Describes one or more columns.",
|
||||
"title": "Field",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "The name of the field.",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "Defines the type of the field. Scalar should be assumed by default, and has no meaning. The only other type that affects parsing is array.",
|
||||
"type": "string",
|
||||
"enum": ["scalar", "link", "array", "icon", "modelId", "color"]
|
||||
},
|
||||
"count": {
|
||||
"description": "Only valid for array types. Defines the number of elements in the array.",
|
||||
"type": "integer"
|
||||
},
|
||||
"targets": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"condition": {
|
||||
"$ref": "#/definitions/Condition"
|
||||
},
|
||||
"fields": {
|
||||
"description": "Only valid for array types. Defines the fields of the array. Fields are not available on non-array types because grouping non-array types is meaningless. They should be defined at the top-level.",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/definitions/Field"
|
||||
}
|
||||
},
|
||||
"comment": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"description": "Arrays require a count.",
|
||||
"if": {
|
||||
"required": ["type"],
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": ["count"]
|
||||
},
|
||||
"else": {
|
||||
"not": {
|
||||
"required": ["count"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Fields with a fields list must be an array.",
|
||||
"if": {
|
||||
"required": ["fields"]
|
||||
},
|
||||
"then": {
|
||||
"required": ["type"],
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "array"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Fields with a count must be an array.",
|
||||
"if": {
|
||||
"required": ["count"]
|
||||
},
|
||||
"then": {
|
||||
"required": ["type"],
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "array"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Fields can have only one of condition or targets.",
|
||||
"allOf": [
|
||||
{
|
||||
"description": "Fields with targets cannot have a condition.",
|
||||
"if": {
|
||||
"required": ["targets"]
|
||||
},
|
||||
"then": {
|
||||
"not": {
|
||||
"required": ["condition"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Fields with a condition cannot have targets.",
|
||||
"if": {
|
||||
"required": ["condition"]
|
||||
},
|
||||
"then": {
|
||||
"not": {
|
||||
"required": ["targets"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Arrays can have neither condition or targets.",
|
||||
"not": {
|
||||
"required": ["condition", "targets"]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Fields with a link type must have a condition or targets.",
|
||||
"if": {
|
||||
"required": ["type"],
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "link"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"oneOf": [
|
||||
{
|
||||
"required": ["condition"],
|
||||
"not": {
|
||||
"required": ["targets"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"required": ["targets"],
|
||||
"not": {
|
||||
"required": ["condition"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Condition": {
|
||||
"title": "Condition",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"switch": {
|
||||
"type": "string"
|
||||
},
|
||||
"cases": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["cases", "switch"]
|
||||
}
|
||||
}
|
||||
}
|
103
SchemaValidator/SchemaValidator.cs
Normal file
103
SchemaValidator/SchemaValidator.cs
Normal file
|
@ -0,0 +1,103 @@
|
|||
using Lumina;
|
||||
using Lumina.Data.Files.Excel;
|
||||
using SchemaValidator.New;
|
||||
using SchemaValidator.Util;
|
||||
using SchemaValidator.Validation;
|
||||
using SchemaValidator.Validation.Validators;
|
||||
|
||||
namespace SchemaValidator;
|
||||
|
||||
public class SchemaValidator
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// we need 3 args
|
||||
if (args.Length != 3)
|
||||
{
|
||||
Console.WriteLine("Usage: SchemaValidator.exe <game install directory> <json schema file> <schema directory>");
|
||||
return;
|
||||
}
|
||||
|
||||
var gameDir = args[0];
|
||||
var schemaFile = args[1];
|
||||
var schemaDir = args[2];
|
||||
|
||||
var gameData = new GameData(gameDir);
|
||||
var schemaText = File.ReadAllText(schemaFile);
|
||||
|
||||
var testDict = new Dictionary<string, List<int>>()
|
||||
{
|
||||
{"all", new() {0}},
|
||||
};
|
||||
|
||||
var validators = new List<Validator>
|
||||
{
|
||||
new SchemaFileValidator(gameData, schemaText),
|
||||
new ColumnCountValidator(gameData),
|
||||
new IconTypeValidator(gameData),
|
||||
new NamedInnerNamedOuterValidator(gameData),
|
||||
new ModelIdTypeValidator(gameData),
|
||||
new ColorTypeValidator(gameData),
|
||||
new IconPathExistsValidator(gameData),
|
||||
new SingleLinkRefValidator(gameData, testDict),
|
||||
new MultiLinkRefValidator(gameData, testDict),
|
||||
new ConditionValidator(gameData),
|
||||
new ConditionRefValidator(gameData, testDict),
|
||||
};
|
||||
|
||||
// var exl = gameData.GetFile<ExcelListFile>("exd/root.exl");
|
||||
var results = new ValidationResults();
|
||||
|
||||
foreach (var schemaPath in Directory.GetFiles(schemaDir, "*.yml"))
|
||||
{
|
||||
var sheetName = Path.GetFileNameWithoutExtension(schemaPath);
|
||||
var exh = gameData.GetFile<ExcelHeaderFile>($"exd/{sheetName}.exh");
|
||||
if (exh == null)
|
||||
{
|
||||
Console.Error.WriteLine($"Sheet {sheetName} does not exist!");
|
||||
continue;
|
||||
}
|
||||
|
||||
Sheet sheet;
|
||||
try
|
||||
{
|
||||
sheet = SerializeUtil.Deserialize<Sheet>(File.ReadAllText(schemaPath));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.Error.WriteLine($"Sheet {sheetName} encountered an exception when deserializing!");
|
||||
Console.Error.WriteLine(e.Message);
|
||||
Console.Error.WriteLine(e.StackTrace);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sheet == null)
|
||||
{
|
||||
Console.Error.WriteLine($"Sheet {sheetName} could not be deserialized!");
|
||||
continue;
|
||||
}
|
||||
|
||||
// SerializeUtil.EvaluateSchema(schemaPath);
|
||||
|
||||
foreach (var validator in validators)
|
||||
results.Add(validator.Validate(exh, sheet));
|
||||
}
|
||||
|
||||
foreach (var result in results.Results.Where(r => r.Status == ValidationStatus.Warning))
|
||||
{
|
||||
var msgfmt = result.Message == "" ? "" : $" - ";
|
||||
Console.WriteLine($"{result.Status}: {result.SheetName} - {result.ValidatorName}{msgfmt}{result.Message}");
|
||||
}
|
||||
|
||||
foreach (var result in results.Results.Where(r => r.Status == ValidationStatus.Error))
|
||||
{
|
||||
var msgfmt = result.Message == "" ? "" : $" - ";
|
||||
Console.WriteLine($"{result.Status}: {result.SheetName} - {result.ValidatorName}{msgfmt}{result.Message}");
|
||||
}
|
||||
|
||||
var successCount = results.Results.Count(r => r.Status == ValidationStatus.Success);
|
||||
var warningCount = results.Results.Count(r => r.Status == ValidationStatus.Warning);
|
||||
var errorCount = results.Results.Count(r => r.Status == ValidationStatus.Error);
|
||||
Console.WriteLine($"{successCount} success, {warningCount} warnings, {errorCount} errors");
|
||||
}
|
||||
}
|
|
@ -5,6 +5,17 @@
|
|||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JsonSchema.Net" Version="5.2.5" />
|
||||
<PackageReference Include="Lumina" Version="3.11.0" />
|
||||
<PackageReference Include="NJsonSchema" Version="10.9.0" />
|
||||
<PackageReference Include="SharpYaml" Version="2.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
</Project>
|
||||
|
|
120
SchemaValidator/Util/SchemaUtil.cs
Normal file
120
SchemaValidator/Util/SchemaUtil.cs
Normal file
|
@ -0,0 +1,120 @@
|
|||
using Lumina.Data.Files.Excel;
|
||||
using SchemaValidator.New;
|
||||
|
||||
namespace SchemaValidator.Util;
|
||||
|
||||
/// <summary>
|
||||
/// Useful methods for working with the EXDSchema object model.
|
||||
/// </summary>
|
||||
public static class SchemaUtil
|
||||
{
|
||||
public static int GetColumnCount(Sheet sheet)
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var field in sheet.Fields)
|
||||
total += GetFieldCount(field);
|
||||
return total;
|
||||
}
|
||||
|
||||
public static List<DefinedColumn> Flatten(ExcelHeaderFile exh, Sheet sheet)
|
||||
{
|
||||
var fields = new List<DefinedColumn>();
|
||||
foreach (var field in sheet.Fields)
|
||||
Emit(fields, field);
|
||||
|
||||
var exhDefList = exh.ColumnDefinitions.ToList();
|
||||
exhDefList.Sort((c1, c2) => DefinedColumn.CalculateBitOffset(c1).CompareTo(DefinedColumn.CalculateBitOffset(c2)));
|
||||
|
||||
var min = Math.Min(exhDefList.Count, fields.Count);
|
||||
for(int i = 0; i < min; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
field.Definition = exhDefList[i];
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private static void Emit(List<DefinedColumn> list, Field field, string nameOverride = "")
|
||||
{
|
||||
if (field.Type != FieldType.Array)
|
||||
{
|
||||
// Single field
|
||||
list.Add(new DefinedColumn { Field = CreateField(field, nameOverride) });
|
||||
}
|
||||
else if (field.Type == FieldType.Array)
|
||||
{
|
||||
// We can have an array without fields, it's just scalars
|
||||
if (field.Fields == null)
|
||||
{
|
||||
for (int i = 0; i < field.Count.Value; i++)
|
||||
{
|
||||
list.Add(new DefinedColumn { Field = CreateField(field, "") });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < field.Count.Value; i++)
|
||||
{
|
||||
foreach (var nestedField in field.Fields)
|
||||
{
|
||||
Emit(list, nestedField, field.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Field CreateField(Field baseField, string nameOverride)
|
||||
{
|
||||
var addedField = new Field
|
||||
{
|
||||
Name = baseField.Name,
|
||||
Comment = baseField.Comment,
|
||||
Count = null,
|
||||
Type = baseField.Type == FieldType.Array ? FieldType.Scalar : baseField.Type,
|
||||
Fields = null,
|
||||
Condition = baseField.Condition,
|
||||
Targets = baseField.Targets,
|
||||
};
|
||||
|
||||
// This is for unnamed inner fields of arrays such as arrays of links
|
||||
// We don't want to override the name of unnamed scalars though
|
||||
if (baseField.Name == null && baseField.Type != FieldType.Scalar && nameOverride != "")
|
||||
addedField.Name = nameOverride;
|
||||
return addedField;
|
||||
}
|
||||
|
||||
private static int GetFieldCount(Field field)
|
||||
{
|
||||
if (field.Type == FieldType.Array)
|
||||
{
|
||||
var total = 0;
|
||||
if (field.Fields != null)
|
||||
{
|
||||
foreach (var nestedField in field.Fields)
|
||||
total += GetFieldCount(nestedField);
|
||||
}
|
||||
else
|
||||
{
|
||||
total = 1;
|
||||
}
|
||||
return total * field.Count.Value;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public static Field GetFieldByIndex(Sheet schema, int index)
|
||||
{
|
||||
foreach (var field in schema.Fields)
|
||||
{
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Field GetFieldByIndex(Field field, int index, int baseIndex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
93
SchemaValidator/Util/SerializeUtil.cs
Normal file
93
SchemaValidator/Util/SerializeUtil.cs
Normal file
|
@ -0,0 +1,93 @@
|
|||
using System.Text.Json.Nodes;
|
||||
using Json.Schema;
|
||||
using Newtonsoft.Json;
|
||||
using SchemaValidator.New;
|
||||
using SharpYaml;
|
||||
using SharpYaml.Events;
|
||||
using SharpYaml.Serialization;
|
||||
using SharpYaml.Serialization.Serializers;
|
||||
using JsonSchema = Json.Schema.JsonSchema;
|
||||
|
||||
namespace SchemaValidator.Util;
|
||||
|
||||
public static class SerializeUtil
|
||||
{
|
||||
private static readonly Serializer _serializer;
|
||||
|
||||
static SerializeUtil()
|
||||
{
|
||||
var settings = new SerializerSettings
|
||||
{
|
||||
EmitAlias = false,
|
||||
EmitDefaultValues = false,
|
||||
NamingConvention = new CamelCaseNamingConvention(),
|
||||
IgnoreNulls = true,
|
||||
};
|
||||
settings.RegisterSerializer(typeof(Dictionary<int, List<string>>), new CustomDictionarySerializer());
|
||||
settings.RegisterSerializer(typeof(FieldType), new CustomFieldTypeSerializer());
|
||||
|
||||
_serializer = new Serializer(settings);
|
||||
}
|
||||
|
||||
public static string Serialize(object o)
|
||||
{
|
||||
return _serializer.Serialize(o);
|
||||
}
|
||||
|
||||
public static T? Deserialize<T>(string s)
|
||||
{
|
||||
return _serializer.Deserialize<T>(s);
|
||||
}
|
||||
|
||||
public static object? Deserialize(string s)
|
||||
{
|
||||
return _serializer.Deserialize(s);
|
||||
}
|
||||
|
||||
public static EvaluationResults? EvaluateSchema(string filePath)
|
||||
{
|
||||
var yamlText = File.ReadAllText(filePath);
|
||||
object? yamlObject;
|
||||
try
|
||||
{
|
||||
yamlObject = _serializer.Deserialize(yamlText);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return null;
|
||||
}
|
||||
if (yamlObject == null) return null;
|
||||
|
||||
var json = JsonConvert.SerializeObject(yamlObject);
|
||||
// Console.WriteLine(json);
|
||||
// File.WriteAllText(@"C:\Users\Liam\Documents\repos\EXDSchema\SchemaValidator\test.json", json);
|
||||
|
||||
var schemaText = File.ReadAllText(@"C:\Users\Liam\Documents\repos\EXDSchema\SchemaValidator\SchemaSchema.json");
|
||||
var schema = JsonSchema.FromText(schemaText);
|
||||
var node = JsonNode.Parse(json);
|
||||
return schema.Evaluate(node);
|
||||
}
|
||||
}
|
||||
|
||||
internal class CustomDictionarySerializer : DictionarySerializer
|
||||
{
|
||||
protected override void WriteDictionaryItem(ref ObjectContext objectContext, KeyValuePair<object, object?> keyValue, KeyValuePair<Type, Type> types)
|
||||
{
|
||||
objectContext.SerializerContext.WriteYaml(keyValue.Key, types.Key);
|
||||
objectContext.SerializerContext.WriteYaml(keyValue.Value, types.Value, YamlStyle.Flow);
|
||||
}
|
||||
}
|
||||
|
||||
internal class CustomFieldTypeSerializer : ScalarSerializerBase
|
||||
{
|
||||
public override object? ConvertFrom(ref ObjectContext context, Scalar fromScalar)
|
||||
{
|
||||
return Enum.Parse<FieldType>(new PascalNamingConvention().Convert(fromScalar.Value));
|
||||
}
|
||||
|
||||
public override string ConvertTo(ref ObjectContext objectContext)
|
||||
{
|
||||
return objectContext.Settings.NamingConvention.Convert(objectContext.Instance.ToString());
|
||||
}
|
||||
}
|
8
SchemaValidator/Util/StringToEnumCamelCaseConverter.cs
Normal file
8
SchemaValidator/Util/StringToEnumCamelCaseConverter.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace SchemaValidator.Util;
|
||||
|
||||
public class StringToEnumCamelCaseConverter : StringEnumConverter
|
||||
{
|
||||
|
||||
}
|
92
SchemaValidator/Validation/ValidationResult.cs
Normal file
92
SchemaValidator/Validation/ValidationResult.cs
Normal file
|
@ -0,0 +1,92 @@
|
|||
namespace SchemaValidator.Validation;
|
||||
|
||||
public enum ValidationStatus
|
||||
{
|
||||
Success,
|
||||
Error,
|
||||
Warning,
|
||||
Failed,
|
||||
Info,
|
||||
}
|
||||
|
||||
public class ValidationResults
|
||||
{
|
||||
public List<ValidationResult> Results { get; set; } = new();
|
||||
|
||||
public ValidationResults() { }
|
||||
public ValidationResults(ValidationResult result) => Results.Add(result);
|
||||
|
||||
public void Add(ValidationResult result) => Results.Add(result);
|
||||
public void Add(ValidationResults results) => Results.AddRange(results.Results);
|
||||
|
||||
public static ValidationResults Success(string sheetName, string validatorName, string message = "") => new(ValidationResult.Success(sheetName, validatorName, message));
|
||||
public static ValidationResults Error(string sheetName, string validatorName, string message = "") => new(ValidationResult.Error(sheetName, validatorName, message));
|
||||
public static ValidationResults Warning(string sheetName, string validatorName, string message = "") => new(ValidationResult.Warning(sheetName, validatorName, message));
|
||||
public static ValidationResults Failed(string sheetName, string validatorName, string message = "") => new(ValidationResult.Failed(sheetName, validatorName, message));
|
||||
public static ValidationResults Info(string sheetName, string validatorName, string message = "") => new(ValidationResult.Info(sheetName, validatorName, message));
|
||||
}
|
||||
|
||||
public class ValidationResult
|
||||
{
|
||||
public ValidationStatus Status { get; set; }
|
||||
public string SheetName { get; set; }
|
||||
public string ValidatorName { get; set; }
|
||||
public string Message { get; set; }
|
||||
|
||||
private ValidationResult() {}
|
||||
|
||||
public static ValidationResult Success(string sheetName, string validatorName, string message = "")
|
||||
{
|
||||
return new ValidationResult
|
||||
{
|
||||
SheetName = sheetName,
|
||||
ValidatorName = validatorName,
|
||||
Status = ValidationStatus.Success,
|
||||
Message = message,
|
||||
};
|
||||
}
|
||||
|
||||
public static ValidationResult Error(string sheetName, string validatorName, string message = "")
|
||||
{
|
||||
return new ValidationResult
|
||||
{
|
||||
SheetName = sheetName,
|
||||
ValidatorName = validatorName,
|
||||
Status = ValidationStatus.Error,
|
||||
Message = message,
|
||||
};
|
||||
}
|
||||
|
||||
public static ValidationResult Warning(string sheetName, string validatorName, string message = "")
|
||||
{
|
||||
return new ValidationResult
|
||||
{
|
||||
SheetName = sheetName,
|
||||
ValidatorName = validatorName,
|
||||
Status = ValidationStatus.Warning,
|
||||
Message = message,
|
||||
};
|
||||
}
|
||||
|
||||
public static ValidationResult Failed(string sheetName, string validatorName, string message = "")
|
||||
{
|
||||
return new ValidationResult
|
||||
{
|
||||
SheetName = sheetName,
|
||||
ValidatorName = validatorName,
|
||||
Status = ValidationStatus.Failed,
|
||||
Message = message,
|
||||
};
|
||||
}
|
||||
|
||||
public static ValidationResult Info(string sheetName, string validatorName, string message = "")
|
||||
{
|
||||
return new ValidationResult
|
||||
{
|
||||
SheetName = sheetName,
|
||||
ValidatorName = validatorName,
|
||||
Status = ValidationStatus.Info,
|
||||
Message = message,
|
||||
};
|
||||
}
|
||||
}
|
42
SchemaValidator/Validation/Validator.cs
Normal file
42
SchemaValidator/Validation/Validator.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using Lumina;
|
||||
using Lumina.Data.Files.Excel;
|
||||
using Lumina.Data.Structs.Excel;
|
||||
using Lumina.Excel;
|
||||
using SchemaValidator.New;
|
||||
|
||||
namespace SchemaValidator.Validation;
|
||||
|
||||
public abstract class Validator
|
||||
{
|
||||
protected GameData GameData;
|
||||
|
||||
public Validator(GameData gameData)
|
||||
{
|
||||
GameData = gameData;
|
||||
}
|
||||
|
||||
public abstract string ValidatorName();
|
||||
public abstract ValidationResults Validate(ExcelHeaderFile exh, Sheet sheet);
|
||||
|
||||
protected long? ReadColumnIntegerValue(RawExcelSheet sheet, RowParser parser, DefinedColumn column)
|
||||
{
|
||||
var offset = column.Definition.Offset;
|
||||
var type = column.Definition.Type;
|
||||
Int128? value = type switch
|
||||
{
|
||||
ExcelColumnDataType.Int8 => parser.ReadOffset<sbyte>(offset),
|
||||
ExcelColumnDataType.UInt8 => parser.ReadOffset<byte>(offset),
|
||||
ExcelColumnDataType.Int16 => parser.ReadOffset<short>(offset),
|
||||
ExcelColumnDataType.UInt16 => parser.ReadOffset<ushort>(offset),
|
||||
ExcelColumnDataType.Int32 => parser.ReadOffset<int>(offset),
|
||||
ExcelColumnDataType.UInt32 => parser.ReadOffset<uint>(offset),
|
||||
ExcelColumnDataType.Int64 => parser.ReadOffset<long>(offset),
|
||||
ExcelColumnDataType.UInt64 => parser.ReadOffset<ulong>(offset),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (value != null)
|
||||
return (long)value;
|
||||
return null;
|
||||
}
|
||||
}
|
32
SchemaValidator/Validation/Validators/ColorTypeValidator.cs
Normal file
32
SchemaValidator/Validation/Validators/ColorTypeValidator.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using Lumina;
|
||||
using Lumina.Data.Files.Excel;
|
||||
using Lumina.Data.Structs.Excel;
|
||||
using SchemaValidator.New;
|
||||
using SchemaValidator.Util;
|
||||
|
||||
namespace SchemaValidator.Validation.Validators;
|
||||
|
||||
public class ColorTypeValidator : Validator
|
||||
{
|
||||
public override string ValidatorName() => "ColorTypeValidator";
|
||||
|
||||
public ColorTypeValidator(GameData gameData) : base(gameData) { }
|
||||
|
||||
public override ValidationResults Validate(ExcelHeaderFile exh, Sheet sheet)
|
||||
{
|
||||
var results = new ValidationResults();
|
||||
var fields = SchemaUtil.Flatten(exh, sheet);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (field.Field.Type == FieldType.Color && field.Definition.Type != ExcelColumnDataType.UInt32)
|
||||
{
|
||||
var msg = $"Column {field.Field.Name}@0x{field.Definition.Offset:X} type {field.Definition.Type} is not valid for type 'color'.";
|
||||
results.Results.Add(ValidationResult.Error(sheet.Name, ValidatorName(), msg));
|
||||
}
|
||||
}
|
||||
|
||||
if (results.Results.Count == 0)
|
||||
return ValidationResults.Success(sheet.Name, ValidatorName());
|
||||
return results;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using Lumina;
|
||||
using Lumina.Data.Files.Excel;
|
||||
using SchemaValidator.New;
|
||||
using SchemaValidator.Util;
|
||||
|
||||
namespace SchemaValidator.Validation.Validators;
|
||||
|
||||
public class ColumnCountValidator : Validator
|
||||
{
|
||||
public override string ValidatorName() => "ColumnCountValidator";
|
||||
|
||||
public ColumnCountValidator(GameData gameData) : base(gameData) { }
|
||||
|
||||
public override ValidationResults Validate(ExcelHeaderFile exh, Sheet sheet)
|
||||
{
|
||||
var colCount = SchemaUtil.GetColumnCount(sheet);
|
||||
if (colCount != exh.ColumnDefinitions.Length)
|
||||
return ValidationResults.Error(sheet.Name, ValidatorName(), $"Column count mismatch! exh count {exh.ColumnDefinitions.Length} != schema count {colCount}");
|
||||
return ValidationResults.Success(sheet.Name, ValidatorName());
|
||||
}
|
||||
}
|
161
SchemaValidator/Validation/Validators/ConditionRefValidator.cs
Normal file
161
SchemaValidator/Validation/Validators/ConditionRefValidator.cs
Normal file
|
@ -0,0 +1,161 @@
|
|||
using System.Diagnostics;
|
||||
using Lumina;
|
||||
using Lumina.Data.Files.Excel;
|
||||
using Lumina.Excel;
|
||||
using SchemaValidator.New;
|
||||
using SchemaValidator.Util;
|
||||
|
||||
namespace SchemaValidator.Validation.Validators;
|
||||
|
||||
public class ConditionRefValidator : Validator
|
||||
{
|
||||
public override string ValidatorName() => "ConditionRefValidator";
|
||||
|
||||
private Dictionary<string, List<int>> _ignoredValues = new();
|
||||
|
||||
public ConditionRefValidator(GameData gameData) : base(gameData) { }
|
||||
|
||||
public ConditionRefValidator(GameData gameData, Dictionary<string, List<int>> ignoredValues) : base(gameData)
|
||||
{
|
||||
_ignoredValues = ignoredValues;
|
||||
}
|
||||
|
||||
private class LinkTargetData
|
||||
{
|
||||
public string SourceSheet { get; set; }
|
||||
public DefinedColumn Source { get; set; }
|
||||
|
||||
public string SwitchField { get; set; }
|
||||
public int SwitchValue { get; set; }
|
||||
|
||||
public HashSet<string> TargetSheets { get; set; }
|
||||
public HashSet<long> TargetKeys { get; set; }
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is not LinkTargetData other)
|
||||
return false;
|
||||
return SourceSheet == other.SourceSheet && Source.Field.Equals(other.Source.Field) && TargetSheets == other.TargetSheets && TargetKeys.SetEquals(other.TargetKeys);
|
||||
}
|
||||
}
|
||||
|
||||
public override ValidationResults Validate(ExcelHeaderFile exh, Sheet sheet)
|
||||
{
|
||||
var results = new ValidationResults();
|
||||
var fields = SchemaUtil.Flatten(exh, sheet);
|
||||
var dataFile = GameData.Excel.GetSheetRaw($"{sheet.Name}");
|
||||
if (dataFile == null)
|
||||
{
|
||||
var msg = $"Failed to obtain sheet {sheet.Name} from game data.";
|
||||
results.Results.Add(ValidationResult.Failed(sheet.Name, ValidatorName(), msg));
|
||||
return results;
|
||||
}
|
||||
|
||||
// Get all link fields with condition
|
||||
var linkFields = fields.Where(f => f.Field is { Type: FieldType.Link, Condition: not null, Targets: null }).ToList();
|
||||
|
||||
var linkVals = new List<LinkTargetData>();
|
||||
foreach (var field in linkFields)
|
||||
{
|
||||
var offset = field.Definition.Offset;
|
||||
var switchOn = field.Field.Condition.Switch;
|
||||
var switchField = fields.First(f => f.Field.Name == switchOn);
|
||||
// var definedSwitchColumnValues = field.Field.Condition.Cases.Keys.ToHashSet();
|
||||
|
||||
// store the column values for each switch value
|
||||
var columnValues = new Dictionary<long, HashSet<long>>();
|
||||
|
||||
foreach (var row in dataFile.GetRowParsers())
|
||||
{
|
||||
var columnValue = ReadColumnIntegerValue(dataFile, row, field);
|
||||
var switchColumnValue = ReadColumnIntegerValue(dataFile, row, switchField);
|
||||
if (columnValue == null || switchColumnValue == null)
|
||||
{
|
||||
var msg = $"Column {field.Field.Name}@0x{offset:X} type {field.Definition.Type} is not valid for type 'link' condition switch, failed to read.";
|
||||
results.Results.Add(ValidationResult.Failed(sheet.Name, ValidatorName(), msg));
|
||||
break; // don't spam the same error
|
||||
}
|
||||
|
||||
if (columnValues.TryGetValue(switchColumnValue.Value, out var values))
|
||||
{
|
||||
values.Add(columnValue.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
columnValues.Add(switchColumnValue.Value, new HashSet<long> { columnValue.Value });
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var valueSet in columnValues)
|
||||
{
|
||||
// Skip if we don't have a defined target set for this value of the switch
|
||||
if (!field.Field.Condition.Cases.TryGetValue((int)valueSet.Key, out var switchTargets))
|
||||
continue;
|
||||
|
||||
var targetData = new LinkTargetData
|
||||
{
|
||||
SourceSheet = sheet.Name,
|
||||
Source = field,
|
||||
TargetSheets = switchTargets.ToHashSet(),
|
||||
TargetKeys = valueSet.Value,
|
||||
SwitchField = switchField.Field.Name,
|
||||
SwitchValue = (int)valueSet.Key,
|
||||
};
|
||||
linkVals.Add(targetData);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var linkData in linkVals.Distinct())
|
||||
{
|
||||
var sheetNames = linkData.TargetSheets;
|
||||
var sheetKeys = linkData.TargetKeys;
|
||||
|
||||
var dataFiles = new Dictionary<string, RawExcelSheet>();
|
||||
foreach (var sheetName in sheetNames)
|
||||
{
|
||||
var tmpDataFile = GameData.Excel.GetSheetRaw(sheetName);
|
||||
if (tmpDataFile == null)
|
||||
{
|
||||
var msg = $"Source {linkData.SourceSheet} field {linkData.Source.Field.Name}@0x{linkData.Source.Definition.Offset:X} references non-existent sheet {sheetName}.";
|
||||
results.Add(ValidationResult.Error(sheet.Name, ValidatorName(), msg));
|
||||
continue;
|
||||
}
|
||||
dataFiles.Add(sheetName, tmpDataFile);
|
||||
}
|
||||
|
||||
foreach (var key in sheetKeys)
|
||||
{
|
||||
if (_ignoredValues.TryGetValue("all", out var ignoredKeys) && ignoredKeys.Contains((int)key))
|
||||
{
|
||||
sheetKeys.Remove(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var linkedDataFile in dataFiles)
|
||||
{
|
||||
if (_ignoredValues.TryGetValue(linkedDataFile.Key, out ignoredKeys) && ignoredKeys.Contains((int)key))
|
||||
{
|
||||
sheetKeys.Remove(key);
|
||||
}
|
||||
else if (linkedDataFile.Value.GetRow((uint)key) != null)
|
||||
{
|
||||
// Console.WriteLine($"removing {key} because of {linkedDataFile.Key}");
|
||||
sheetKeys.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sheetKeys.Count > 0)
|
||||
{
|
||||
var contents = string.Join(", ", sheetKeys);
|
||||
var display = $"{linkData.Source.Field.Name}@0x{linkData.Source.Definition.Offset:X}";
|
||||
var msg = $"Source {linkData.SourceSheet} field {display} contains key references {contents} for case {linkData.SwitchField} = {linkData.SwitchValue} which do not exist in any linked sheet.";
|
||||
results.Add(ValidationResult.Warning(sheet.Name, ValidatorName(), msg));
|
||||
}
|
||||
}
|
||||
|
||||
if (results.Results.Count == 0)
|
||||
return ValidationResults.Success(sheet.Name, ValidatorName());
|
||||
return results;
|
||||
}
|
||||
}
|
67
SchemaValidator/Validation/Validators/ConditionValidator.cs
Normal file
67
SchemaValidator/Validation/Validators/ConditionValidator.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System.Diagnostics;
|
||||
using Lumina;
|
||||
using Lumina.Data.Files.Excel;
|
||||
using SchemaValidator.New;
|
||||
using SchemaValidator.Util;
|
||||
|
||||
namespace SchemaValidator.Validation.Validators;
|
||||
|
||||
public class ConditionValidator : Validator
|
||||
{
|
||||
public override string ValidatorName() => "ConditionValidator";
|
||||
|
||||
public ConditionValidator(GameData gameData) : base(gameData) { }
|
||||
|
||||
public override ValidationResults Validate(ExcelHeaderFile exh, Sheet sheet)
|
||||
{
|
||||
var results = new ValidationResults();
|
||||
var fields = SchemaUtil.Flatten(exh, sheet);
|
||||
var dataFile = GameData.Excel.GetSheetRaw($"{sheet.Name}");
|
||||
|
||||
// Get all link fields with null targets (filters out target-based links)
|
||||
var conditionFields = fields.Where(f => f.Field is { Type: FieldType.Link, Condition: not null, Targets: null }).ToList();
|
||||
|
||||
foreach (var field in conditionFields)
|
||||
{
|
||||
var offset = field.Definition.Offset;
|
||||
var switchOn = field.Field.Condition.Switch;
|
||||
var switchField = fields.First(f => f.Field.Name == switchOn);
|
||||
var definedSwitchColumnValues = field.Field.Condition.Cases.Keys.ToHashSet();
|
||||
var switchColumnValues = new HashSet<long>();
|
||||
|
||||
foreach (var row in dataFile.GetRowParsers())
|
||||
{
|
||||
var columnValue = ReadColumnIntegerValue(dataFile, row, switchField);
|
||||
if (columnValue == null)
|
||||
{
|
||||
var msg = $"Column {field.Field.Name}@0x{offset:X} type {field.Definition.Type} is not valid for type 'link' condition switch, failed to read.";
|
||||
results.Results.Add(ValidationResult.Failed(sheet.Name, ValidatorName(), msg));
|
||||
break; // don't spam the same error
|
||||
}
|
||||
switchColumnValues.Add(columnValue.Value);
|
||||
}
|
||||
|
||||
foreach (var switchColumnValue in switchColumnValues)
|
||||
{
|
||||
if (!definedSwitchColumnValues.Contains((int)switchColumnValue))
|
||||
{
|
||||
var msg = $"Column {field.Field.Name}@0x{offset:X} type {field.Definition.Type} is not valid for type 'link' condition switch, switch column {switchOn} value {switchColumnValue} is not defined in the schema.";
|
||||
results.Add(ValidationResult.Warning(sheet.Name, ValidatorName(), msg));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var definedSwitchColumnValue in definedSwitchColumnValues)
|
||||
{
|
||||
if (!switchColumnValues.Contains(definedSwitchColumnValue))
|
||||
{
|
||||
var msg = $"Column {field.Field.Name}@0x{offset:X} type {field.Definition.Type} is not valid for type 'link' condition switch, switch column {switchOn} value {definedSwitchColumnValue} is not present in the column values.";
|
||||
results.Add(ValidationResult.Warning(sheet.Name, ValidatorName(), msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (results.Results.Count == 0)
|
||||
return ValidationResults.Success(sheet.Name, ValidatorName());
|
||||
return results;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
using Lumina;
|
||||
using Lumina.Data.Files.Excel;
|
||||
using SchemaValidator.New;
|
||||
using SchemaValidator.Util;
|
||||
|
||||
namespace SchemaValidator.Validation.Validators;
|
||||
|
||||
public class IconPathExistsValidator : Validator
|
||||
{
|
||||
public override string ValidatorName() => "IconPathExistsValidator";
|
||||
|
||||
public IconPathExistsValidator(GameData gameData) : base(gameData) { }
|
||||
|
||||
public override ValidationResults Validate(ExcelHeaderFile exh, Sheet sheet)
|
||||
{
|
||||
var results = new ValidationResults();
|
||||
var fields = SchemaUtil.Flatten(exh, sheet);
|
||||
var dataFile = GameData.Excel.GetSheetRaw($"{sheet.Name}");
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (field.Field.Type == FieldType.Icon)
|
||||
{
|
||||
var offset = field.Definition.Offset;
|
||||
foreach (var row in dataFile.GetRowParsers())
|
||||
{
|
||||
var columnValue = ReadColumnIntegerValue(dataFile, row, field);
|
||||
if (columnValue == null)
|
||||
{
|
||||
var msg = $"Column {field.Field.Name}@0x{offset:X} type {field.Definition.Type} is not valid for type 'icon', failed to read.";
|
||||
results.Results.Add(ValidationResult.Failed(sheet.Name, ValidatorName(), msg));
|
||||
break; // don't spam the same error
|
||||
}
|
||||
|
||||
if (!IconPathExists(columnValue.Value))
|
||||
{
|
||||
var msg = $"Column {field.Field.Name}@0x{offset:X} type {field.Definition.Type} at row {row.RowId} icon '{columnValue}' does not exist.";
|
||||
results.Results.Add(ValidationResult.Warning(sheet.Name, ValidatorName(), msg));
|
||||
break; // don't spam the same error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (results.Results.Count == 0)
|
||||
return ValidationResults.Success(sheet.Name, ValidatorName());
|
||||
return results;
|
||||
}
|
||||
|
||||
private bool IconPathExists(long iconId)
|
||||
{
|
||||
var path = $"ui/icon/{iconId / 1000 * 1000:000000}/{iconId:000000}.tex";
|
||||
return GameData.FileExists(path);
|
||||
}
|
||||
}
|
39
SchemaValidator/Validation/Validators/IconTypeValidator.cs
Normal file
39
SchemaValidator/Validation/Validators/IconTypeValidator.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using Lumina;
|
||||
using Lumina.Data.Files.Excel;
|
||||
using Lumina.Data.Structs.Excel;
|
||||
using SchemaValidator.New;
|
||||
using SchemaValidator.Util;
|
||||
|
||||
namespace SchemaValidator.Validation.Validators;
|
||||
|
||||
public class IconTypeValidator : Validator
|
||||
{
|
||||
public override string ValidatorName() => "IconTypeValidator";
|
||||
|
||||
private readonly HashSet<ExcelColumnDataType> _validTypes = new()
|
||||
{
|
||||
ExcelColumnDataType.UInt32,
|
||||
ExcelColumnDataType.Int32,
|
||||
ExcelColumnDataType.UInt16,
|
||||
};
|
||||
|
||||
public IconTypeValidator(GameData gameData) : base(gameData) { }
|
||||
|
||||
public override ValidationResults Validate(ExcelHeaderFile exh, Sheet sheet)
|
||||
{
|
||||
var results = new ValidationResults();
|
||||
var fields = SchemaUtil.Flatten(exh, sheet);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (field.Field.Type == FieldType.Icon && !_validTypes.Contains(field.Definition.Type))
|
||||
{
|
||||
var msg = $"Column {field.Field.Name}@0x{field.Definition.Offset:X} type {field.Definition.Type} is not valid for type 'icon'.";
|
||||
results.Results.Add(ValidationResult.Error(sheet.Name, ValidatorName(), msg));
|
||||
}
|
||||
}
|
||||
|
||||
if (results.Results.Count == 0)
|
||||
return ValidationResults.Success(sheet.Name, ValidatorName());
|
||||
return results;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using Lumina;
|
||||
using Lumina.Data.Files.Excel;
|
||||
using Lumina.Data.Structs.Excel;
|
||||
using SchemaValidator.New;
|
||||
using SchemaValidator.Util;
|
||||
|
||||
namespace SchemaValidator.Validation.Validators;
|
||||
|
||||
public class ModelIdTypeValidator : Validator
|
||||
{
|
||||
public override string ValidatorName() => "ModelIdTypeValidator";
|
||||
|
||||
private readonly HashSet<ExcelColumnDataType> _validTypes = new()
|
||||
{
|
||||
ExcelColumnDataType.UInt32,
|
||||
ExcelColumnDataType.UInt64,
|
||||
};
|
||||
|
||||
public ModelIdTypeValidator(GameData gameData) : base(gameData) { }
|
||||
|
||||
public override ValidationResults Validate(ExcelHeaderFile exh, Sheet sheet)
|
||||
{
|
||||
var results = new ValidationResults();
|
||||
var fields = SchemaUtil.Flatten(exh, sheet);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (field.Field.Type == FieldType.ModelId && !_validTypes.Contains(field.Definition.Type))
|
||||
{
|
||||
var msg = $"Column {field.Field.Name}@0x{field.Definition.Offset:X} type {field.Definition.Type} is not valid for type 'modelId'.";
|
||||
results.Results.Add(ValidationResult.Error(sheet.Name, ValidatorName(), msg));
|
||||
}
|
||||
}
|
||||
|
||||
if (results.Results.Count == 0)
|
||||
return ValidationResults.Success(sheet.Name, ValidatorName());
|
||||
return results;
|
||||
}
|
||||
}
|
136
SchemaValidator/Validation/Validators/MultiLinkRefValidator.cs
Normal file
136
SchemaValidator/Validation/Validators/MultiLinkRefValidator.cs
Normal file
|
@ -0,0 +1,136 @@
|
|||
using System.Diagnostics;
|
||||
using Lumina;
|
||||
using Lumina.Data.Files.Excel;
|
||||
using Lumina.Excel;
|
||||
using SchemaValidator.New;
|
||||
using SchemaValidator.Util;
|
||||
|
||||
namespace SchemaValidator.Validation.Validators;
|
||||
|
||||
public class MultiLinkRefValidator : Validator
|
||||
{
|
||||
public override string ValidatorName() => "MultiLinkRefValidator";
|
||||
|
||||
private Dictionary<string, List<int>> _ignoredValues = new();
|
||||
|
||||
public MultiLinkRefValidator(GameData gameData) : base(gameData) { }
|
||||
|
||||
public MultiLinkRefValidator(GameData gameData, Dictionary<string, List<int>> ignoredValues) : base(gameData)
|
||||
{
|
||||
_ignoredValues = ignoredValues;
|
||||
}
|
||||
|
||||
private class LinkTargetData
|
||||
{
|
||||
public string SourceSheet { get; set; }
|
||||
public DefinedColumn Source { get; set; }
|
||||
|
||||
public HashSet<string> TargetSheets { get; set; }
|
||||
public HashSet<long> TargetKeys { get; set; }
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is not LinkTargetData other)
|
||||
return false;
|
||||
return SourceSheet == other.SourceSheet && Source.Field.Equals(other.Source.Field) && TargetSheets == other.TargetSheets && TargetKeys.SetEquals(other.TargetKeys);
|
||||
}
|
||||
}
|
||||
|
||||
public override ValidationResults Validate(ExcelHeaderFile exh, Sheet sheet)
|
||||
{
|
||||
var results = new ValidationResults();
|
||||
var fields = SchemaUtil.Flatten(exh, sheet);
|
||||
var dataFile = GameData.Excel.GetSheetRaw($"{sheet.Name}");
|
||||
if (dataFile == null)
|
||||
{
|
||||
var msg = $"Failed to obtain sheet {sheet.Name} from game data.";
|
||||
results.Results.Add(ValidationResult.Failed(sheet.Name, ValidatorName(), msg));
|
||||
return results;
|
||||
}
|
||||
|
||||
// Get all link fields with null condition, > 1 target
|
||||
var linkFields = fields.Where(f => f.Field is { Type: FieldType.Link, Condition: null, Targets.Count: > 1 }).ToList();
|
||||
|
||||
var linkVals = new List<LinkTargetData>();
|
||||
foreach (var field in linkFields)
|
||||
{
|
||||
var offset = field.Definition.Offset;
|
||||
|
||||
var columnValues = new HashSet<long>();
|
||||
|
||||
foreach (var row in dataFile.GetRowParsers())
|
||||
{
|
||||
var columnValue = ReadColumnIntegerValue(dataFile, row, field);
|
||||
if (columnValue == null)
|
||||
{
|
||||
var msg = $"Row {row.RowId} column {field.Field.Name}@0x{offset:X} type {field.Definition.Type} failed to read properly.";
|
||||
results.Results.Add(ValidationResult.Failed(sheet.Name, ValidatorName(), msg));
|
||||
break; // don't spam the same error
|
||||
}
|
||||
columnValues.Add(columnValue.Value);
|
||||
}
|
||||
|
||||
var targetData = new LinkTargetData
|
||||
{
|
||||
SourceSheet = sheet.Name,
|
||||
Source = field,
|
||||
TargetSheets = field.Field.Targets.ToHashSet(),
|
||||
TargetKeys = columnValues
|
||||
};
|
||||
linkVals.Add(targetData);
|
||||
}
|
||||
|
||||
foreach (var linkData in linkVals.Distinct())
|
||||
{
|
||||
var sheetNames = linkData.TargetSheets;
|
||||
var sheetKeys = linkData.TargetKeys;
|
||||
|
||||
var dataFiles = new Dictionary<string, RawExcelSheet>();
|
||||
foreach (var sheetName in sheetNames)
|
||||
{
|
||||
var tmpDataFile = GameData.Excel.GetSheetRaw(sheetName);
|
||||
if (tmpDataFile == null)
|
||||
{
|
||||
var msg = $"Source {linkData.SourceSheet} field {linkData.Source.Field.Name}@0x{linkData.Source.Definition.Offset:X} references non-existent sheet {sheetName}.";
|
||||
results.Add(ValidationResult.Error(sheet.Name, ValidatorName(), msg));
|
||||
continue;
|
||||
}
|
||||
dataFiles.Add(sheetName, tmpDataFile);
|
||||
}
|
||||
|
||||
foreach (var key in sheetKeys)
|
||||
{
|
||||
if (_ignoredValues.TryGetValue("all", out var ignoredKeys) && ignoredKeys.Contains((int)key))
|
||||
{
|
||||
sheetKeys.Remove(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var linkedDataFile in dataFiles)
|
||||
{
|
||||
if (_ignoredValues.TryGetValue(linkedDataFile.Key, out ignoredKeys) && ignoredKeys.Contains((int)key))
|
||||
{
|
||||
sheetKeys.Remove(key);
|
||||
}
|
||||
else if (linkedDataFile.Value.GetRow((uint)key) != null)
|
||||
{
|
||||
// Console.WriteLine($"removing {key} because of {linkedDataFile.Key}");
|
||||
sheetKeys.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sheetKeys.Count > 0)
|
||||
{
|
||||
var contents = string.Join(", ", sheetKeys);
|
||||
var display = $"{linkData.Source.Field.Name}@0x{linkData.Source.Definition.Offset:X}";
|
||||
var msg = $"Source {linkData.SourceSheet} field {display} contains key references {contents} which do not exist in any linked sheet.";
|
||||
results.Add(ValidationResult.Warning(sheet.Name, ValidatorName(), msg));
|
||||
}
|
||||
}
|
||||
|
||||
if (results.Results.Count == 0)
|
||||
return ValidationResults.Success(sheet.Name, ValidatorName());
|
||||
return results;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
using Lumina;
|
||||
using Lumina.Data.Files.Excel;
|
||||
using SchemaValidator.New;
|
||||
|
||||
namespace SchemaValidator.Validation.Validators;
|
||||
|
||||
public class NamedInnerNamedOuterValidator : Validator
|
||||
{
|
||||
public override string ValidatorName() => "NamedInnerNamedOuterValidator";
|
||||
private string _sheetName = "";
|
||||
|
||||
public override ValidationResults Validate(ExcelHeaderFile exh, Sheet sheet)
|
||||
{
|
||||
_sheetName = sheet.Name;
|
||||
var results = new ValidationResults();
|
||||
foreach (var field in sheet.Fields)
|
||||
{
|
||||
CheckNames(results, null, field);
|
||||
}
|
||||
|
||||
if (results.Results.Count == 0)
|
||||
return ValidationResults.Success(sheet.Name, ValidatorName());
|
||||
return results;
|
||||
}
|
||||
|
||||
public NamedInnerNamedOuterValidator(GameData gameData) : base(gameData) { }
|
||||
|
||||
private void CheckNames(ValidationResults results, Field? parentField, Field field)
|
||||
{
|
||||
if (parentField != null)
|
||||
{
|
||||
if (parentField.Fields.Count == 1 && !string.IsNullOrEmpty(parentField.Name) && !string.IsNullOrEmpty(field.Name) && field.Type == FieldType.Scalar)
|
||||
{
|
||||
var msg = $"Parent field {parentField.Name} has a single named field {field.Name}.";
|
||||
results.Results.Add(ValidationResult.Warning(_sheetName, ValidatorName(), msg));
|
||||
}
|
||||
}
|
||||
|
||||
if (field.Type != FieldType.Array)
|
||||
{
|
||||
// Single field
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.Type == FieldType.Array)
|
||||
{
|
||||
if (field.Fields != null)
|
||||
{
|
||||
foreach (var nestedField in field.Fields)
|
||||
{
|
||||
CheckNames(results, field, nestedField);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
SchemaValidator/Validation/Validators/SchemaFileValidator.cs
Normal file
43
SchemaValidator/Validation/Validators/SchemaFileValidator.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
using System.Text.Json.Nodes;
|
||||
using Json.Schema;
|
||||
using Lumina;
|
||||
using Lumina.Data.Files.Excel;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using SchemaValidator.New;
|
||||
|
||||
namespace SchemaValidator.Validation.Validators;
|
||||
|
||||
public class SchemaFileValidator : Validator
|
||||
{
|
||||
public override string ValidatorName() => "SchemaFileValidator";
|
||||
|
||||
private readonly JsonSerializerSettings _settings = new()
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
DefaultValueHandling = DefaultValueHandling.Ignore,
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
};
|
||||
|
||||
private readonly JsonSchema _schema;
|
||||
|
||||
public SchemaFileValidator(GameData gameData, string schemaText) : base(gameData)
|
||||
{
|
||||
_schema = JsonSchema.FromText(schemaText);
|
||||
}
|
||||
|
||||
public override ValidationResults Validate(ExcelHeaderFile exh, Sheet sheet)
|
||||
{
|
||||
// Re-serialize the yml sheet into json
|
||||
var json = JsonConvert.SerializeObject(sheet, _settings);
|
||||
|
||||
if (json == null) return ValidationResults.Failed(sheet.Name, ValidatorName(), "Json serialization returned null.");
|
||||
|
||||
var node = JsonNode.Parse(json);
|
||||
var schemaResult = _schema.Evaluate(node);
|
||||
|
||||
if (schemaResult == null) return ValidationResults.Failed(sheet.Name, ValidatorName(), "Schema validation returned null.");
|
||||
if (schemaResult.IsValid) return ValidationResults.Success(sheet.Name, ValidatorName());
|
||||
return ValidationResults.Error(sheet.Name, ValidatorName());
|
||||
}
|
||||
}
|
114
SchemaValidator/Validation/Validators/SingleLinkRefValidator.cs
Normal file
114
SchemaValidator/Validation/Validators/SingleLinkRefValidator.cs
Normal file
|
@ -0,0 +1,114 @@
|
|||
using System.Diagnostics;
|
||||
using Lumina;
|
||||
using Lumina.Data.Files.Excel;
|
||||
using SchemaValidator.New;
|
||||
using SchemaValidator.Util;
|
||||
|
||||
namespace SchemaValidator.Validation.Validators;
|
||||
|
||||
public class SingleLinkRefValidator : Validator
|
||||
{
|
||||
public override string ValidatorName() => "SingleLinkRefValidator";
|
||||
|
||||
private Dictionary<string, List<int>> _ignoredValues = new();
|
||||
|
||||
public SingleLinkRefValidator(GameData gameData) : base(gameData) { }
|
||||
|
||||
public SingleLinkRefValidator(GameData gameData, Dictionary<string, List<int>> ignoredValues) : base(gameData)
|
||||
{
|
||||
_ignoredValues = ignoredValues;
|
||||
}
|
||||
|
||||
private class LinkTargetData
|
||||
{
|
||||
public string SourceSheet { get; set; }
|
||||
public DefinedColumn Source { get; set; }
|
||||
|
||||
public string TargetSheet { get; set; }
|
||||
public HashSet<long> TargetKeys { get; set; }
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is not LinkTargetData other)
|
||||
return false;
|
||||
return SourceSheet == other.SourceSheet && Source.Field.Equals(other.Source.Field) && TargetSheet == other.TargetSheet && TargetKeys.SetEquals(other.TargetKeys);
|
||||
}
|
||||
}
|
||||
|
||||
public override ValidationResults Validate(ExcelHeaderFile exh, Sheet sheet)
|
||||
{
|
||||
var results = new ValidationResults();
|
||||
var fields = SchemaUtil.Flatten(exh, sheet);
|
||||
var dataFile = GameData.Excel.GetSheetRaw($"{sheet.Name}");
|
||||
if (dataFile == null)
|
||||
{
|
||||
var msg = $"Failed to obtain sheet {sheet.Name} from game data.";
|
||||
results.Results.Add(ValidationResult.Failed(sheet.Name, ValidatorName(), msg));
|
||||
return results;
|
||||
}
|
||||
|
||||
// Get all link fields with null condition, 1 target
|
||||
var linkFields = fields.Where(f => f.Field is { Type: FieldType.Link, Condition: null, Targets.Count: 1 }).ToList();
|
||||
|
||||
var linkVals = new List<LinkTargetData>();
|
||||
foreach (var field in linkFields)
|
||||
{
|
||||
var offset = field.Definition.Offset;
|
||||
|
||||
var columnValues = new HashSet<long>();
|
||||
|
||||
foreach (var row in dataFile.GetRowParsers())
|
||||
{
|
||||
var columnValue = ReadColumnIntegerValue(dataFile, row, field);
|
||||
if (columnValue == null)
|
||||
{
|
||||
var msg = $"Row {row.RowId} column {field.Field.Name}@0x{offset:X} type {field.Definition.Type} failed to read properly.";
|
||||
results.Results.Add(ValidationResult.Failed(sheet.Name, ValidatorName(), msg));
|
||||
break; // don't spam the same error
|
||||
}
|
||||
columnValues.Add(columnValue.Value);
|
||||
}
|
||||
|
||||
// our filtering ensures we have exactly one target
|
||||
var targetData = new LinkTargetData
|
||||
{
|
||||
SourceSheet = sheet.Name,
|
||||
Source = field,
|
||||
TargetSheet = field.Field.Targets[0],
|
||||
TargetKeys = columnValues,
|
||||
};
|
||||
linkVals.Add(targetData);
|
||||
}
|
||||
|
||||
foreach (var linkData in linkVals.Distinct())
|
||||
{
|
||||
var sheetName = linkData.TargetSheet;
|
||||
var sheetKeys = linkData.TargetKeys;
|
||||
|
||||
var linkedDataFile = GameData.Excel.GetSheetRaw(sheetName);
|
||||
if (linkedDataFile == null)
|
||||
{
|
||||
var msg = $"Source {linkData.SourceSheet} field {linkData.Source.Field.Name}@0x{linkData.Source.Definition.Offset:X} references non-existent sheet {sheetName}.";
|
||||
results.Add(ValidationResult.Error(sheet.Name, ValidatorName(), msg));
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var key in sheetKeys)
|
||||
{
|
||||
if (_ignoredValues.TryGetValue(sheetName, out var ignoredKeys) && ignoredKeys.Contains((int)key)) continue;
|
||||
if (_ignoredValues.TryGetValue("all", out ignoredKeys) && ignoredKeys.Contains((int)key)) continue;
|
||||
|
||||
if (linkedDataFile.GetRow((uint)key) == null)
|
||||
{
|
||||
var display = $"{linkData.Source.Field.Name}@0x{linkData.Source.Definition.Offset:X}";
|
||||
var msg = $"Source {linkData.SourceSheet} field {display} references {sheetName} key '{key}' which does not exist.";
|
||||
results.Add(ValidationResult.Warning(sheet.Name, ValidatorName(), msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (results.Results.Count == 0)
|
||||
return ValidationResults.Success(sheet.Name, ValidatorName());
|
||||
return results;
|
||||
}
|
||||
}
|
6
Schemas/AOZArrangement.yml
Normal file
6
Schemas/AOZArrangement.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: AOZArrangement
|
||||
fields:
|
||||
- name: AOZContentBriefingBNpc
|
||||
type: link
|
||||
targets: [AOZContentBriefingBNpc]
|
||||
- name: Position
|
7
Schemas/AOZBoss.yml
Normal file
7
Schemas/AOZBoss.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: AOZBoss
|
||||
displayField: Boss
|
||||
fields:
|
||||
- name: Boss
|
||||
type: link
|
||||
targets: [AOZContentBriefingBNpc]
|
||||
- name: Position
|
41
Schemas/AOZContent.yml
Normal file
41
Schemas/AOZContent.yml
Normal file
|
@ -0,0 +1,41 @@
|
|||
name: AOZContent
|
||||
fields:
|
||||
- name: GilReward
|
||||
- name: AlliedSealsReward
|
||||
- name: TomestonesReward
|
||||
- name: ContentEntry
|
||||
type: link
|
||||
targets: [ContentEntry]
|
||||
- name: StandardFinishTime
|
||||
- name: IdealFinishTime
|
||||
- name: Act1
|
||||
type: link
|
||||
condition:
|
||||
switch: Act1FightType
|
||||
cases:
|
||||
1: [AOZArrangement]
|
||||
2: [AOZBoss]
|
||||
- name: Act2
|
||||
type: link
|
||||
condition:
|
||||
switch: Act2FightType
|
||||
cases:
|
||||
1: [AOZArrangement]
|
||||
2: [AOZBoss]
|
||||
- name: Act3
|
||||
type: link
|
||||
condition:
|
||||
switch: Act3FightType
|
||||
cases:
|
||||
1: [AOZArrangement]
|
||||
2: [AOZBoss]
|
||||
- name: Unknown5
|
||||
- name: Unknown9
|
||||
- name: Unknown13
|
||||
- name: Act1FightType
|
||||
- name: Act2FightType
|
||||
- name: Act3FightType
|
||||
- name: ArenaType1
|
||||
- name: ArenaType2
|
||||
- name: ArenaType3
|
||||
- name: Order
|
32
Schemas/AOZContentBriefingBNpc.yml
Normal file
32
Schemas/AOZContentBriefingBNpc.yml
Normal file
|
@ -0,0 +1,32 @@
|
|||
name: AOZContentBriefingBNpc
|
||||
displayField: BNpcName
|
||||
fields:
|
||||
- name: BNpcName
|
||||
type: link
|
||||
targets: [BNpcName]
|
||||
- name: TargetSmall
|
||||
type: icon
|
||||
- name: TargetLarge
|
||||
type: icon
|
||||
- name: Endurance
|
||||
- name: Fire
|
||||
- name: Ice
|
||||
- name: Wind
|
||||
- name: Earth
|
||||
- name: Thunder
|
||||
- name: Water
|
||||
- name: Slashing
|
||||
- name: Piercing
|
||||
- name: Blunt
|
||||
- name: Magic
|
||||
- name: HideStats
|
||||
- name: SlowVuln
|
||||
- name: PetrificationVuln
|
||||
- name: ParalysisVuln
|
||||
- name: InterruptionVuln
|
||||
- name: BlindVuln
|
||||
- name: StunVuln
|
||||
- name: SleepVuln
|
||||
- name: BindVuln
|
||||
- name: HeavyVuln
|
||||
- name: FlatOrDeathVuln
|
5
Schemas/AOZContentBriefingObject.yml
Normal file
5
Schemas/AOZContentBriefingObject.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: AOZContentBriefingObject
|
||||
fields:
|
||||
- name: Icon
|
||||
type: icon
|
||||
- name: Unknown1
|
7
Schemas/AOZReport.yml
Normal file
7
Schemas/AOZReport.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: AOZReport
|
||||
fields:
|
||||
- name: Unknown0
|
||||
- name: Reward
|
||||
type: link
|
||||
targets: [AOZReportReward]
|
||||
- name: Order
|
7
Schemas/AOZScore.yml
Normal file
7
Schemas/AOZScore.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: AOZScore
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
||||
- name: Description
|
||||
- name: Score
|
||||
- name: IsHidden
|
62
Schemas/Achievement.yml
Normal file
62
Schemas/Achievement.yml
Normal file
|
@ -0,0 +1,62 @@
|
|||
name: Achievement
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
||||
- name: Description
|
||||
- name: Item
|
||||
type: link
|
||||
targets: [Item]
|
||||
- name: Key
|
||||
type: link
|
||||
condition:
|
||||
switch: Type
|
||||
cases:
|
||||
2: [Achievement]
|
||||
3: [ClassJob]
|
||||
6: [Quest]
|
||||
7: [ClassJob]
|
||||
8: [Map]
|
||||
9: [Quest]
|
||||
11: [GrandCompany]
|
||||
14: [InstanceContent]
|
||||
15: [BeastTribe]
|
||||
18: [GrandCompany]
|
||||
20: [AetherCurrentCompFlgSet]
|
||||
24: [Quest]
|
||||
- name: Data
|
||||
type: array
|
||||
count: 8
|
||||
fields:
|
||||
- type: link
|
||||
condition:
|
||||
switch: Type
|
||||
cases:
|
||||
2: [Achievement]
|
||||
6: [Quest]
|
||||
9: [Quest]
|
||||
15: [BeastReputationRank]
|
||||
20: [AetherCurrentCompFlgSet]
|
||||
24: [ClassJob, Quest]
|
||||
- name: Title
|
||||
type: link
|
||||
targets: [Title]
|
||||
- name: Icon
|
||||
type: icon
|
||||
- name: Order
|
||||
- name: AchievementCategory
|
||||
type: link
|
||||
targets: [AchievementCategory]
|
||||
- name: AchievementTarget
|
||||
type: link
|
||||
targets: [AchievementTarget]
|
||||
- name: Unknown4
|
||||
- name: Points
|
||||
- name: Unknown8
|
||||
- name: Unknown9
|
||||
- name: Unknown10
|
||||
- name: Unknown12
|
||||
- name: Type
|
||||
- name: Unknown24
|
||||
- name: AchievementHideCondition
|
||||
type: link
|
||||
targets: [AchievementHideCondition]
|
10
Schemas/AchievementCategory.yml
Normal file
10
Schemas/AchievementCategory.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: AchievementCategory
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
||||
- name: AchievementKind
|
||||
type: link
|
||||
targets: [AchievementKind]
|
||||
- name: Order
|
||||
- name: ShowComplete
|
||||
- name: HideCategory
|
5
Schemas/AchievementHideCondition.yml
Normal file
5
Schemas/AchievementHideCondition.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: AchievementHideCondition
|
||||
fields:
|
||||
- name: HideAchievement
|
||||
- name: HideName
|
||||
- name: HideConditions
|
5
Schemas/AchievementKind.yml
Normal file
5
Schemas/AchievementKind.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: AchievementKind
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
||||
- name: Order
|
5
Schemas/AchievementTarget.yml
Normal file
5
Schemas/AchievementTarget.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: AchievementTarget
|
||||
displayField: Value
|
||||
fields:
|
||||
- name: Value
|
||||
- name: Type
|
105
Schemas/Action.yml
Normal file
105
Schemas/Action.yml
Normal file
|
@ -0,0 +1,105 @@
|
|||
name: Action
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
||||
- name: UnlockLink
|
||||
type: link
|
||||
targets: [ChocoboTaxiStand, CraftLeve, CustomTalk, DefaultTalk, FccShop, GCShop, GilShop, GuildleveAssignment, GuildOrderGuide, GuildOrderOfficer, Quest, SpecialShop, Story, SwitchTalk, TopicSelect, TripleTriad, Warp]
|
||||
- name: Icon
|
||||
type: icon
|
||||
- name: VFX
|
||||
type: link
|
||||
targets: [ActionCastVFX]
|
||||
- name: ActionTimelineHit
|
||||
type: link
|
||||
targets: [ActionTimeline]
|
||||
- name: PrimaryCostValue
|
||||
- name: SecondaryCostValue
|
||||
type: link
|
||||
condition:
|
||||
switch: SecondaryCostType
|
||||
cases:
|
||||
32: [Status]
|
||||
35: [Status]
|
||||
46: [Status]
|
||||
- name: ActionCombo
|
||||
type: link
|
||||
targets: [Action]
|
||||
- name: Cast100ms
|
||||
- name: Recast100ms
|
||||
- name: StatusGainSelf
|
||||
type: link
|
||||
targets: [Status]
|
||||
- name: Omen
|
||||
type: link
|
||||
targets: [Omen]
|
||||
- name: Unknown54
|
||||
- name: AnimationEnd
|
||||
type: link
|
||||
targets: [ActionTimeline]
|
||||
- name: ActionCategory
|
||||
type: link
|
||||
targets: [ActionCategory]
|
||||
- name: Unknown4
|
||||
- name: AnimationStart
|
||||
type: link
|
||||
targets: [ActionCastTimeline]
|
||||
- name: Unknown9
|
||||
- name: BehaviourType
|
||||
- name: ClassJobLevel
|
||||
- name: CastType
|
||||
- name: EffectRange
|
||||
- name: XAxisModifier
|
||||
- name: PrimaryCostType
|
||||
- name: SecondaryCostType
|
||||
- name: Unknown38
|
||||
- name: CooldownGroup
|
||||
- name: AdditionalCooldownGroup
|
||||
- name: MaxCharges
|
||||
- name: Aspect
|
||||
- name: ActionProcStatus
|
||||
type: link
|
||||
targets: [ActionProcStatus]
|
||||
- name: Unknown46
|
||||
- name: ClassJobCategory
|
||||
type: link
|
||||
targets: [ClassJobCategory]
|
||||
- name: Unknown50
|
||||
- name: Unknown64
|
||||
- name: ClassJob
|
||||
type: link
|
||||
targets: [ClassJob]
|
||||
- name: Range
|
||||
- name: Unknown24
|
||||
- name: AttackType
|
||||
type: link
|
||||
targets: [AttackType]
|
||||
- name: Unknown1
|
||||
- name: IsRoleAction
|
||||
- name: CanTargetSelf
|
||||
- name: CanTargetParty
|
||||
- name: CanTargetFriendly
|
||||
- name: CanTargetHostile
|
||||
- name: Unknown19
|
||||
- name: Unknown20
|
||||
- name: TargetArea
|
||||
- name: Unknown22
|
||||
- name: Unknown23
|
||||
- name: CanTargetDead
|
||||
- name: Unknown26
|
||||
- name: Unknown30
|
||||
- name: PreservesCombo
|
||||
- name: Unknown51
|
||||
- name: AffectsPosition
|
||||
- name: IsPvP
|
||||
- name: Unknown56
|
||||
- name: Unknown57
|
||||
- name: Unknown58
|
||||
- name: Unknown59
|
||||
- name: Unknown60
|
||||
- name: Unknown61
|
||||
- name: Unknown62
|
||||
- name: Unknown63
|
||||
- name: Unknown65
|
||||
- name: Unknown66
|
||||
- name: IsPlayerAction
|
9
Schemas/ActionCastTimeline.yml
Normal file
9
Schemas/ActionCastTimeline.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
name: ActionCastTimeline
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
||||
type: link
|
||||
targets: [ActionTimeline]
|
||||
- name: VFX
|
||||
type: link
|
||||
targets: [VFX]
|
6
Schemas/ActionCastVFX.yml
Normal file
6
Schemas/ActionCastVFX.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: ActionCastVFX
|
||||
displayField: VFX
|
||||
fields:
|
||||
- name: VFX
|
||||
type: link
|
||||
targets: [VFX]
|
4
Schemas/ActionCategory.yml
Normal file
4
Schemas/ActionCategory.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
name: ActionCategory
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
15
Schemas/ActionComboRoute.yml
Normal file
15
Schemas/ActionComboRoute.yml
Normal file
|
@ -0,0 +1,15 @@
|
|||
name: ActionComboRoute
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
||||
- name: Action
|
||||
type: array
|
||||
count: 4
|
||||
fields:
|
||||
- type: link
|
||||
targets: [Action]
|
||||
- name: Unknown6
|
||||
- name: Unknown7
|
||||
- name: Unknown8
|
||||
- name: Unknown1
|
||||
- name: Unknown9
|
12
Schemas/ActionIndirection.yml
Normal file
12
Schemas/ActionIndirection.yml
Normal file
|
@ -0,0 +1,12 @@
|
|||
name: ActionIndirection
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
||||
type: link
|
||||
targets: [Action]
|
||||
- name: PreviousComboAction
|
||||
type: link
|
||||
targets: [Action]
|
||||
- name: ClassJob
|
||||
type: link
|
||||
targets: [ClassJob]
|
5
Schemas/ActionParam.yml
Normal file
5
Schemas/ActionParam.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: ActionParam
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
||||
- name: Unknown1
|
6
Schemas/ActionProcStatus.yml
Normal file
6
Schemas/ActionProcStatus.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: ActionProcStatus
|
||||
displayField: Status
|
||||
fields:
|
||||
- name: Status
|
||||
type: link
|
||||
targets: [Status]
|
27
Schemas/ActionTimeline.yml
Normal file
27
Schemas/ActionTimeline.yml
Normal file
|
@ -0,0 +1,27 @@
|
|||
name: ActionTimeline
|
||||
displayField: Key
|
||||
fields:
|
||||
- name: Key
|
||||
- name: KillUpper
|
||||
- name: Type
|
||||
- name: Priority
|
||||
- name: Stance
|
||||
- name: Slot
|
||||
- name: LookAtMode
|
||||
- name: ActionTimelineIDMode
|
||||
- name: WeaponTimeline
|
||||
type: link
|
||||
targets: [WeaponTimeline]
|
||||
- name: LoadType
|
||||
- name: StartAttach
|
||||
- name: ResidentPap
|
||||
- name: IsLoop
|
||||
- name: Unknown20
|
||||
- name: Unknown21
|
||||
- name: Pause
|
||||
- name: Resident
|
||||
- name: IsMotionCanceledByMoving
|
||||
- name: Unknown15
|
||||
- name: Unknown17
|
||||
- name: Unknown18
|
||||
- name: Unknown19
|
8
Schemas/ActionTimelineMove.yml
Normal file
8
Schemas/ActionTimelineMove.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
name: ActionTimelineMove
|
||||
fields:
|
||||
- name: Unknown4
|
||||
- name: Unknown0
|
||||
- name: Unknown1
|
||||
- name: Unknown2
|
||||
- name: Unknown3
|
||||
- name: Unknown5
|
8
Schemas/ActionTimelineReplace.yml
Normal file
8
Schemas/ActionTimelineReplace.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
name: ActionTimelineReplace
|
||||
fields:
|
||||
- name: Old
|
||||
type: link
|
||||
targets: [ActionTimeline]
|
||||
- name: New
|
||||
type: link
|
||||
targets: [ActionTimeline]
|
4
Schemas/ActionTransient.yml
Normal file
4
Schemas/ActionTransient.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
name: ActionTransient
|
||||
displayField: Description
|
||||
fields:
|
||||
- name: Description
|
7
Schemas/ActivityFeedButtons.yml
Normal file
7
Schemas/ActivityFeedButtons.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: ActivityFeedButtons
|
||||
fields:
|
||||
- name: BannerURL
|
||||
- name: Description
|
||||
- name: Language
|
||||
- name: PictureURL
|
||||
- name: Unknown0
|
6
Schemas/ActivityFeedCaptions.yml
Normal file
6
Schemas/ActivityFeedCaptions.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: ActivityFeedCaptions
|
||||
fields:
|
||||
- name: JA
|
||||
- name: EN
|
||||
- name: DE
|
||||
- name: FR
|
6
Schemas/ActivityFeedGroupCaptions.yml
Normal file
6
Schemas/ActivityFeedGroupCaptions.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: ActivityFeedGroupCaptions
|
||||
fields:
|
||||
- name: JA
|
||||
- name: EN
|
||||
- name: DE
|
||||
- name: FR
|
7
Schemas/ActivityFeedImages.yml
Normal file
7
Schemas/ActivityFeedImages.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: ActivityFeedImages
|
||||
fields:
|
||||
- name: ExpansionImage
|
||||
- name: ActivityFeedJA
|
||||
- name: ActivityFeedEN
|
||||
- name: ActivityFeedDE
|
||||
- name: ActivityFeedFR
|
4
Schemas/Addon.yml
Normal file
4
Schemas/Addon.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
name: Addon
|
||||
displayField: Text
|
||||
fields:
|
||||
- name: Text
|
26
Schemas/Adventure.yml
Normal file
26
Schemas/Adventure.yml
Normal file
|
@ -0,0 +1,26 @@
|
|||
name: Adventure
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
||||
- name: Impression
|
||||
- name: Description
|
||||
- name: Level
|
||||
type: link
|
||||
targets: [Level]
|
||||
- name: MinLevel
|
||||
- name: PlaceName
|
||||
type: link
|
||||
targets: [PlaceName]
|
||||
- name: IconList
|
||||
type: icon
|
||||
- name: IconDiscovered
|
||||
type: icon
|
||||
- name: IconUndiscovered
|
||||
type: icon
|
||||
- name: Emote
|
||||
type: link
|
||||
targets: [Emote]
|
||||
- name: MinTime
|
||||
- name: MaxTime
|
||||
- name: MaxLevel
|
||||
- name: IsInitial
|
15
Schemas/AdventureExPhase.yml
Normal file
15
Schemas/AdventureExPhase.yml
Normal file
|
@ -0,0 +1,15 @@
|
|||
name: AdventureExPhase
|
||||
fields:
|
||||
- name: Quest
|
||||
type: link
|
||||
targets: [Quest]
|
||||
- name: AdventureBegin
|
||||
type: link
|
||||
targets: [Adventure]
|
||||
- name: AdventureEnd
|
||||
type: link
|
||||
targets: [Adventure]
|
||||
- name: Unknown4
|
||||
- name: Expansion
|
||||
type: link
|
||||
targets: [ExVersion]
|
6
Schemas/AetherCurrent.yml
Normal file
6
Schemas/AetherCurrent.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: AetherCurrent
|
||||
displayField: Quest
|
||||
fields:
|
||||
- name: Quest
|
||||
type: link
|
||||
targets: [Quest]
|
11
Schemas/AetherCurrentCompFlgSet.yml
Normal file
11
Schemas/AetherCurrentCompFlgSet.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
name: AetherCurrentCompFlgSet
|
||||
fields:
|
||||
- name: Territory
|
||||
type: link
|
||||
targets: [TerritoryType]
|
||||
- name: AetherCurrents
|
||||
type: array
|
||||
count: 15
|
||||
fields:
|
||||
- type: link
|
||||
targets: [AetherCurrent]
|
10
Schemas/AetherialWheel.yml
Normal file
10
Schemas/AetherialWheel.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: AetherialWheel
|
||||
fields:
|
||||
- name: ItemUnprimed
|
||||
type: link
|
||||
targets: [Item]
|
||||
- name: ItemPrimed
|
||||
type: link
|
||||
targets: [Item]
|
||||
- name: Grade
|
||||
- name: HoursRequired
|
39
Schemas/Aetheryte.yml
Normal file
39
Schemas/Aetheryte.yml
Normal file
|
@ -0,0 +1,39 @@
|
|||
name: Aetheryte
|
||||
displayField: PlaceName
|
||||
fields:
|
||||
- name: Singular
|
||||
- name: Plural
|
||||
- name: Adjective
|
||||
- name: PossessivePronoun
|
||||
- name: StartsWithVowel
|
||||
- name: Unknown5
|
||||
- name: Pronoun
|
||||
- name: Article
|
||||
- name: Unknown16
|
||||
- name: Level
|
||||
type: array
|
||||
count: 4
|
||||
fields:
|
||||
- type: link
|
||||
targets: [Level]
|
||||
- name: RequiredQuest
|
||||
type: link
|
||||
targets: [Quest]
|
||||
- name: PlaceName
|
||||
type: link
|
||||
targets: [PlaceName]
|
||||
- name: AethernetName
|
||||
type: link
|
||||
targets: [PlaceName]
|
||||
- name: Territory
|
||||
type: link
|
||||
targets: [TerritoryType]
|
||||
- name: Map
|
||||
type: link
|
||||
targets: [Map]
|
||||
- name: AetherstreamX
|
||||
- name: AetherstreamY
|
||||
- name: AethernetGroup
|
||||
- name: Order
|
||||
- name: IsAetheryte
|
||||
- name: Invisible
|
5
Schemas/AetheryteSystemDefine.yml
Normal file
5
Schemas/AetheryteSystemDefine.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: AetheryteSystemDefine
|
||||
displayField: Text
|
||||
fields:
|
||||
- name: Text
|
||||
- name: DefineValue
|
3
Schemas/AetheryteTransient.yml
Normal file
3
Schemas/AetheryteTransient.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
name: AetheryteTransient
|
||||
fields:
|
||||
- name: Unknown0
|
4
Schemas/AirshipExplorationLevel.yml
Normal file
4
Schemas/AirshipExplorationLevel.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
name: AirshipExplorationLevel
|
||||
fields:
|
||||
- name: ExpToNext
|
||||
- name: Capacity
|
4
Schemas/AirshipExplorationLog.yml
Normal file
4
Schemas/AirshipExplorationLog.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
name: AirshipExplorationLog
|
||||
displayField: Text
|
||||
fields:
|
||||
- name: Text
|
4
Schemas/AirshipExplorationParamType.yml
Normal file
4
Schemas/AirshipExplorationParamType.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
name: AirshipExplorationParamType
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
12
Schemas/AirshipExplorationPart.yml
Normal file
12
Schemas/AirshipExplorationPart.yml
Normal file
|
@ -0,0 +1,12 @@
|
|||
name: AirshipExplorationPart
|
||||
fields:
|
||||
- name: Class
|
||||
- name: Surveillance
|
||||
- name: Retrieval
|
||||
- name: Speed
|
||||
- name: Range
|
||||
- name: Favor
|
||||
- name: Slot
|
||||
- name: Rank
|
||||
- name: Components
|
||||
- name: RepairMaterials
|
17
Schemas/AirshipExplorationPoint.yml
Normal file
17
Schemas/AirshipExplorationPoint.yml
Normal file
|
@ -0,0 +1,17 @@
|
|||
name: AirshipExplorationPoint
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
||||
- name: NameShort
|
||||
- name: ExpReward
|
||||
- name: CeruleumTankReq
|
||||
- name: SurveyDurationmin
|
||||
- name: SurveyDistance
|
||||
- name: X
|
||||
- name: Y
|
||||
- name: RankReq
|
||||
- name: Unknown9
|
||||
- name: Unknown11
|
||||
- name: SurveillanceReq
|
||||
- name: Unknown12
|
||||
- name: Passengers
|
11
Schemas/AkatsukiNote.yml
Normal file
11
Schemas/AkatsukiNote.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
name: AkatsukiNote
|
||||
fields:
|
||||
- name: Unknown0
|
||||
- name: Unknown1
|
||||
- name: Unknown2
|
||||
- name: Unknown3
|
||||
- name: Unknown4
|
||||
- name: Unknown5
|
||||
- name: Unknown6
|
||||
- name: Unknown7
|
||||
- name: Unknown8
|
3
Schemas/AkatsukiNoteString.yml
Normal file
3
Schemas/AkatsukiNoteString.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
name: AkatsukiNoteString
|
||||
fields:
|
||||
- name: Unknown0
|
13
Schemas/AnimaWeapon5.yml
Normal file
13
Schemas/AnimaWeapon5.yml
Normal file
|
@ -0,0 +1,13 @@
|
|||
name: AnimaWeapon5
|
||||
fields:
|
||||
- name: Item
|
||||
type: link
|
||||
targets: [Item]
|
||||
- name: Unknown1
|
||||
- name: SecondaryStatTotal
|
||||
- name: Parameter
|
||||
type: array
|
||||
count: 5
|
||||
fields:
|
||||
- type: link
|
||||
targets: [AnimaWeapon5Param]
|
7
Schemas/AnimaWeapon5Param.yml
Normal file
7
Schemas/AnimaWeapon5Param.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: AnimaWeapon5Param
|
||||
displayField: BaseParam
|
||||
fields:
|
||||
- name: Name
|
||||
- name: BaseParam
|
||||
type: link
|
||||
targets: [BaseParam]
|
4
Schemas/AnimaWeapon5PatternGroup.yml
Normal file
4
Schemas/AnimaWeapon5PatternGroup.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
name: AnimaWeapon5PatternGroup
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
6
Schemas/AnimaWeapon5SpiritTalk.yml
Normal file
6
Schemas/AnimaWeapon5SpiritTalk.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: AnimaWeapon5SpiritTalk
|
||||
displayField: Dialogue
|
||||
fields:
|
||||
- name: Dialogue
|
||||
type: link
|
||||
targets: [AnimaWeapon5SpiritTalkParam]
|
5
Schemas/AnimaWeapon5SpiritTalkParam.yml
Normal file
5
Schemas/AnimaWeapon5SpiritTalkParam.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: AnimaWeapon5SpiritTalkParam
|
||||
displayField: Prologue
|
||||
fields:
|
||||
- name: Prologue
|
||||
- name: Epilogue
|
22
Schemas/AnimaWeapon5TradeItem.yml
Normal file
22
Schemas/AnimaWeapon5TradeItem.yml
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: AnimaWeapon5TradeItem
|
||||
fields:
|
||||
- name: CrystalSand
|
||||
type: link
|
||||
targets: [Item]
|
||||
- name: Item
|
||||
type: array
|
||||
count: 8
|
||||
fields:
|
||||
- type: link
|
||||
targets: [ Item ]
|
||||
- name: Order
|
||||
- name: ReceiveQuantity
|
||||
- name: Quantity
|
||||
type: array
|
||||
count: 8
|
||||
- name: Category
|
||||
type: link
|
||||
targets: [AnimaWeapon5PatternGroup]
|
||||
- name: IsHQ
|
||||
type: array
|
||||
count: 8
|
6
Schemas/AnimaWeaponFUITalk.yml
Normal file
6
Schemas/AnimaWeaponFUITalk.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: AnimaWeaponFUITalk
|
||||
displayField: Dialogue
|
||||
fields:
|
||||
- name: Dialogue
|
||||
type: link
|
||||
targets: [AnimaWeaponFUITalkParam]
|
5
Schemas/AnimaWeaponFUITalkParam.yml
Normal file
5
Schemas/AnimaWeaponFUITalkParam.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: AnimaWeaponFUITalkParam
|
||||
displayField: Prologue
|
||||
fields:
|
||||
- name: Prologue
|
||||
- name: Epilogue
|
12
Schemas/AnimaWeaponIcon.yml
Normal file
12
Schemas/AnimaWeaponIcon.yml
Normal file
|
@ -0,0 +1,12 @@
|
|||
name: AnimaWeaponIcon
|
||||
fields:
|
||||
- name: Hyperconductive
|
||||
type: icon
|
||||
- name: Reborn
|
||||
type: icon
|
||||
- name: Sharpened
|
||||
type: icon
|
||||
- name: Zodiac
|
||||
type: icon
|
||||
- name: ZodiacLux
|
||||
type: icon
|
8
Schemas/AnimaWeaponItem.yml
Normal file
8
Schemas/AnimaWeaponItem.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
name: AnimaWeaponItem
|
||||
fields:
|
||||
- name: Item
|
||||
type: array
|
||||
count: 14
|
||||
fields:
|
||||
- type: link
|
||||
targets: [Item]
|
8
Schemas/AnimationLOD.yml
Normal file
8
Schemas/AnimationLOD.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
name: AnimationLOD
|
||||
fields:
|
||||
- name: CameraDistance
|
||||
- name: SampleInterval
|
||||
- name: BoneLOD
|
||||
- name: AnimationEnable
|
||||
type: array
|
||||
count: 8
|
7
Schemas/AozAction.yml
Normal file
7
Schemas/AozAction.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: AozAction
|
||||
displayField: Action
|
||||
fields:
|
||||
- name: Action
|
||||
type: link
|
||||
targets: [Action]
|
||||
- name: Rank
|
34
Schemas/AozActionTransient.yml
Normal file
34
Schemas/AozActionTransient.yml
Normal file
|
@ -0,0 +1,34 @@
|
|||
name: AozActionTransient
|
||||
displayField: Action
|
||||
fields:
|
||||
- name: Stats
|
||||
- name: Description
|
||||
- name: Icon
|
||||
type: icon
|
||||
- name: RequiredForQuest
|
||||
type: link
|
||||
targets: [Quest]
|
||||
- name: PreviousQuest
|
||||
type: link
|
||||
targets: [Quest]
|
||||
- name: Location
|
||||
type: link
|
||||
condition:
|
||||
switch: LocationKey
|
||||
cases:
|
||||
1: [PlaceName]
|
||||
4: [ContentFinderCondition]
|
||||
- name: Number
|
||||
- name: LocationKey
|
||||
- name: TargetsEnemy
|
||||
- name: TargetsSelfOrAlly
|
||||
- name: CauseSlow
|
||||
- name: CausePetrify
|
||||
- name: CauseParalysis
|
||||
- name: CauseInterrupt
|
||||
- name: CauseBlind
|
||||
- name: CauseStun
|
||||
- name: CauseSleep
|
||||
- name: CauseBind
|
||||
- name: CauseHeavy
|
||||
- name: CauseDeath
|
11
Schemas/AquariumFish.yml
Normal file
11
Schemas/AquariumFish.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
name: AquariumFish
|
||||
displayField: Item
|
||||
fields:
|
||||
- name: Item
|
||||
type: link
|
||||
targets: [Item]
|
||||
- name: Unknown3
|
||||
- name: AquariumWater
|
||||
type: link
|
||||
targets: [AquariumWater]
|
||||
- name: Size
|
5
Schemas/AquariumWater.yml
Normal file
5
Schemas/AquariumWater.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: AquariumWater
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
||||
- name: Unknown0
|
5
Schemas/ArchiveItem.yml
Normal file
5
Schemas/ArchiveItem.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: ArchiveItem
|
||||
fields:
|
||||
- name: Unknown0
|
||||
- name: Unknown1
|
||||
- name: Unknown2
|
8
Schemas/ArrayEventHandler.yml
Normal file
8
Schemas/ArrayEventHandler.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
name: ArrayEventHandler
|
||||
fields:
|
||||
- name: Data
|
||||
type: array
|
||||
count: 16
|
||||
fields:
|
||||
- type: link
|
||||
targets: [InstanceContentGuide, Story, Opening, CustomTalk, DefaultTalk, GilShop, Warp, Quest]
|
4
Schemas/AttackType.yml
Normal file
4
Schemas/AttackType.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
name: AttackType
|
||||
displayField: Name
|
||||
fields:
|
||||
- name: Name
|
7
Schemas/Attract.yml
Normal file
7
Schemas/Attract.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: Attract
|
||||
fields:
|
||||
- name: MaxDistance
|
||||
- name: Speed
|
||||
- name: MinRemainingDistance
|
||||
- name: Direction
|
||||
- name: UseDistanceBetweenHitboxes
|
10
Schemas/BGM.yml
Normal file
10
Schemas/BGM.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: BGM
|
||||
displayField: File
|
||||
fields:
|
||||
- name: File
|
||||
- name: DisableRestartResetTime
|
||||
- name: Priority
|
||||
- name: SpecialMode
|
||||
- name: DisableRestartTimeOut
|
||||
- name: DisableRestart
|
||||
- name: PassEnd
|
7
Schemas/BGMFade.yml
Normal file
7
Schemas/BGMFade.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: BGMFade
|
||||
fields:
|
||||
- name: SceneOut
|
||||
- name: SceneIn
|
||||
- name: BGMFadeType
|
||||
type: link
|
||||
targets: [BGMFadeType]
|
6
Schemas/BGMFadeType.yml
Normal file
6
Schemas/BGMFadeType.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: BGMFadeType
|
||||
fields:
|
||||
- name: FadeOutTime
|
||||
- name: FadeInTime
|
||||
- name: FadeInStartTime
|
||||
- name: ResumeFadeInTime
|
7
Schemas/BGMScene.yml
Normal file
7
Schemas/BGMScene.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: BGMScene
|
||||
fields:
|
||||
- name: EnableDisableRestart
|
||||
- name: Resume
|
||||
- name: EnablePassEnd
|
||||
- name: ForceAutoReset
|
||||
- name: IgnoreBattle
|
17
Schemas/BGMSituation.yml
Normal file
17
Schemas/BGMSituation.yml
Normal file
|
@ -0,0 +1,17 @@
|
|||
name: BGMSituation
|
||||
fields:
|
||||
- name: DaytimeID
|
||||
type: link
|
||||
targets: [BGM]
|
||||
- name: NightID
|
||||
type: link
|
||||
targets: [BGM]
|
||||
- name: BattleID
|
||||
type: link
|
||||
targets: [BGM]
|
||||
- name: DaybreakID
|
||||
type: link
|
||||
targets: [BGM]
|
||||
- name: TwilightID
|
||||
type: link
|
||||
targets: [BGM]
|
12
Schemas/BGMSwitch.yml
Normal file
12
Schemas/BGMSwitch.yml
Normal file
|
@ -0,0 +1,12 @@
|
|||
name: BGMSwitch
|
||||
fields:
|
||||
- name: Quest
|
||||
type: link
|
||||
targets: [Quest]
|
||||
- name: BGM
|
||||
type: link
|
||||
targets: [BGM, BGMSituation]
|
||||
- name: BGMSystemDefine
|
||||
type: link
|
||||
targets: [BGMSystemDefine]
|
||||
- name: Unknown2
|
4
Schemas/BGMSystemDefine.yml
Normal file
4
Schemas/BGMSystemDefine.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
name: BGMSystemDefine
|
||||
displayField: Define
|
||||
fields:
|
||||
- name: Define
|
5
Schemas/BNpcAnnounceIcon.yml
Normal file
5
Schemas/BNpcAnnounceIcon.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: BNpcAnnounceIcon
|
||||
displayField: Icon
|
||||
fields:
|
||||
- name: Icon
|
||||
type: icon
|
40
Schemas/BNpcBase.yml
Normal file
40
Schemas/BNpcBase.yml
Normal file
|
@ -0,0 +1,40 @@
|
|||
name: BNpcBase
|
||||
fields:
|
||||
- name: Scale
|
||||
- name: ArrayEventHandler
|
||||
type: link
|
||||
targets: [ArrayEventHandler]
|
||||
- name: Behavior
|
||||
type: link
|
||||
targets: [Behavior]
|
||||
- name: ModelChara
|
||||
type: link
|
||||
targets: [ModelChara]
|
||||
- name: BNpcCustomize
|
||||
type: link
|
||||
targets: [BNpcCustomize]
|
||||
- name: NpcEquip
|
||||
type: link
|
||||
targets: [NpcEquip]
|
||||
- name: Special
|
||||
- name: Battalion
|
||||
type: link
|
||||
targets: [Battalion]
|
||||
- name: LinkRace
|
||||
type: link
|
||||
targets: [LinkRace]
|
||||
- name: Rank
|
||||
- name: SEPack
|
||||
- name: Unknown12
|
||||
- name: BNpcParts
|
||||
type: link
|
||||
targets: [BNpcParts]
|
||||
- name: Unknown14
|
||||
- name: Unknown19
|
||||
- name: Unknown20
|
||||
- name: Unknown21
|
||||
- name: Unknown10
|
||||
- name: Unknown15
|
||||
- name: IsTargetLine
|
||||
- name: IsDisplayLevel
|
||||
- name: Unknown18
|
3
Schemas/BNpcBasePopVfx.yml
Normal file
3
Schemas/BNpcBasePopVfx.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
name: BNpcBasePopVfx
|
||||
fields:
|
||||
- name: Unknown0
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue