1
Fork 0
mirror of https://github.com/xivdev/EXDSchema.git synced 2025-06-06 16:17:46 +00:00

Update schema, code, and docs to reflect the list type and new list declaration method.

This commit is contained in:
Liam 2023-09-25 20:04:35 -04:00
parent b4e19ad8fb
commit a448444c39
5 changed files with 99 additions and 53 deletions

View file

@ -32,7 +32,7 @@ The schema includes the following:
- Maps out-of-the-box to a very simple object mapping - Maps out-of-the-box to a very simple object mapping
- JSON schema for the schema itself, providing IDE completion and error-checking - JSON schema for the schema itself, providing IDE completion and error-checking
- Example code - Example code
- A C# project that perform the conversion from the - A C# project that perform a partial conversion from the
common JSON schemas ("SC Schema") to the EXDSchema format common JSON schemas ("SC Schema") to the EXDSchema format
- A C# project that will validate EXDSchemas against a given game installation - A C# project that will validate EXDSchemas against a given game installation
- A tool for editing schemas and viewing the results of parsing on-the-fly - A tool for editing schemas and viewing the results of parsing on-the-fly

View file

@ -1,4 +1,5 @@
using Lumina.Data.Structs.Excel; using Lumina.Data.Structs.Excel;
using SchemaConverter.New;
namespace SchemaConverter; namespace SchemaConverter;
@ -11,11 +12,12 @@ public class ColumnInfo
public ExcelColumnDataType DataType { get; set; } public ExcelColumnDataType DataType { get; set; }
public bool IsArrayMember { get; set; } public bool IsArrayMember { get; set; }
public int? ArrayIndex { get; set; } public int? ArrayIndex { get; set; }
public New.Link? Link { get; set; } public Condition? Condition { get; set; }
public List<string>? Targets { get; set; }
public ColumnInfo() { } public ColumnInfo() { }
public ColumnInfo(Old.Definition def, int index, bool isArrayMember, int? arrayIndex, New.Link link) public ColumnInfo(Old.Definition def, int index, bool isArrayMember, int? arrayIndex, Condition? condition, List<string>? targets)
{ {
var converterType = def.Converter?.Type; var converterType = def.Converter?.Type;
var nameSuffix = isArrayMember ? $"[{arrayIndex}]" : ""; var nameSuffix = isArrayMember ? $"[{arrayIndex}]" : "";
@ -24,8 +26,11 @@ public class ColumnInfo
Type = converterType; Type = converterType;
IsArrayMember = isArrayMember; IsArrayMember = isArrayMember;
ArrayIndex = arrayIndex; ArrayIndex = arrayIndex;
Link = link; Condition = condition;
Targets = targets;
} }
public override string ToString() => $"{Name} ({Index}@{BitOffset / 8}&{BitOffset % 8}) {Type} {IsArrayMember} {ArrayIndex}"; public override string ToString() => $"{Name} ({Index}@{BitOffset / 8}&{BitOffset % 8}) {Type} {IsArrayMember} {ArrayIndex}";
} }

View file

@ -14,6 +14,7 @@ public enum FieldType
Icon, Icon,
ModelId, ModelId,
Color, Color,
Link,
} }
public class Sheet public class Sheet
@ -47,24 +48,18 @@ public class Field
public List<Field>? Fields { get; set; } public List<Field>? Fields { get; set; }
[YamlMember(5)] [YamlMember(5)]
public Link? Link { get; set; }
}
public class Link
{
[YamlMember(0)]
public Condition? Condition { get; set; } public Condition? Condition { get; set; }
[YamlMember(1)] [YamlMember(6)]
[YamlStyle(YamlStyle.Flow)] [YamlStyle(YamlStyle.Flow)]
public List<string> Target { get; set; } public List<string>? Targets { get; set; }
} }
public class Condition public class Condition
{ {
[YamlMember(0)] [YamlMember(0)]
public string Switch { get; set; } public string? Switch { get; set; }
[YamlMember(1)] [YamlMember(1)]
public Dictionary<int, List<string>> Cases { get; set; } public Dictionary<int, List<string>>? Cases { get; set; }
} }

View file

@ -1,4 +1,4 @@
using Lumina; using Lumina;
using Lumina.Data.Files.Excel; using Lumina.Data.Files.Excel;
using Lumina.Data.Structs.Excel; using Lumina.Data.Structs.Excel;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -9,7 +9,7 @@ namespace SchemaConverter;
public class SchemaConverter public class SchemaConverter
{ {
private static readonly New.Link _genericReferenceLink = new() {Target = new List<string>()}; private static readonly List<string> _genericReferenceLink = new();
private static void EnumerateGenericReferenceTargets(string oldSchemaDir) private static void EnumerateGenericReferenceTargets(string oldSchemaDir)
{ {
@ -22,7 +22,7 @@ public class SchemaConverter
targets.Add(oldSchema.SheetName); targets.Add(oldSchema.SheetName);
} }
} }
_genericReferenceLink.Target.AddRange(targets); _genericReferenceLink.AddRange(targets);
} }
public static void Main(string[] args) public static void Main(string[] args)
@ -128,8 +128,11 @@ public class SchemaConverter
"color" => FieldType.Color, "color" => FieldType.Color,
_ => FieldType.Scalar, _ => FieldType.Scalar,
}, },
Link = col.Link, Condition = col.Condition,
Targets = col.Targets,
}; };
if (field.Condition != null || field.Targets != null)
field.Type = FieldType.Link;
newSchema.Fields.Add(field); newSchema.Fields.Add(field);
} }
@ -166,7 +169,8 @@ public class SchemaConverter
private static void EmitSingle(List<ColumnInfo> infos, Old.Definition definition, bool isArray, int? arrayIndex, ref int index) private static void EmitSingle(List<ColumnInfo> infos, Old.Definition definition, bool isArray, int? arrayIndex, ref int index)
{ {
infos.Add(new ColumnInfo(definition, index++, isArray, arrayIndex, ConvertLink(definition.Converter))); var link = ConvertLink(definition.Converter);
infos.Add(new ColumnInfo(definition, index++, isArray, arrayIndex, link.condition, link.targets));
} }
private static void EmitRepeat(List<ColumnInfo> infos, Old.Definition definition, ref int index) private static void EmitRepeat(List<ColumnInfo> infos, Old.Definition definition, ref int index)
@ -199,40 +203,38 @@ public class SchemaConverter
} }
} }
private static New.Link ConvertLink(Old.Converter oldLink) private static (Condition? condition, List<string>? targets) ConvertLink(Old.Converter oldLink)
{ {
if (oldLink == null) return null; if (oldLink == null) return (null, null);
var newLink = new New.Link();
if (oldLink.Type == "generic") if (oldLink.Type == "generic")
{ {
return _genericReferenceLink; return (null, _genericReferenceLink);
} }
else if (oldLink.Type == "link") else if (oldLink.Type == "link")
{ {
newLink.Target = new List<string>() {oldLink.Target}; return (null, new List<string> {oldLink.Target});
} }
else if (oldLink.Type == "multiref") else if (oldLink.Type == "multiref")
{ {
newLink.Target = oldLink.Targets; return (null, oldLink.Targets);
} }
else if (oldLink.Type == "complexlink") else if (oldLink.Type == "complexlink")
{ {
if (oldLink.Links[0].Project != null) if (oldLink.Links[0].Project != null)
{ {
return null; return (null, null);
} }
newLink.Condition = new Condition(); var condition = new Condition();
newLink.Condition.Switch = oldLink.Links[0].When.Key; condition.Switch = oldLink.Links[0].When.Key;
newLink.Condition.Cases = new Dictionary<int, List<string>>(); condition.Cases = new Dictionary<int, List<string>>();
foreach (var oldLinkLink in oldLink.Links) foreach (var oldLinkLink in oldLink.Links)
newLink.Condition.Cases.Add(oldLinkLink.When.Value, oldLinkLink.Sheets); condition.Cases.Add(oldLinkLink.When.Value, oldLinkLink.LinkedSheet == null ? oldLinkLink.Sheets : new List<string> { oldLinkLink.LinkedSheet });
return (condition, null);
} }
else else
{ {
return null; return (null, null);
} }
return newLink;
} }
} }

View file

@ -50,7 +50,7 @@ fields:
This schema is valid because it is accurate in structure. It defines a field for each column in the EXH file as of 6.48. This schema is valid because it is accurate in structure. It defines a field for each column in the EXH file as of 6.48.
### Types ### Types
Valid types for fields in a schema are `scalar`, `array`, `icon`, `modelId`, and `color`. Valid types for fields in a schema are `scalar`, `link`, `array`, `icon`, `modelId`, and `color`.
#### scalar #### scalar
The default type. If the `type` is omitted from a field, it will be assumed to be a `scalar`. Effectively does nothing except tell consumers that The default type. If the `type` is omitted from a field, it will be assumed to be a `scalar`. Effectively does nothing except tell consumers that
@ -96,7 +96,37 @@ Some fields contain an RGB value for color in the ARGB format with no alpha. Thi
columns' fields as actual colors rather than the raw value. columns' fields as actual colors rather than the raw value.
#### array #### array
Array fields provide the ability to group and repeat nested structures. For example, the notorious SpecialShop sheet: Array fields provide the ability to group and repeat nested structures. These are the methods of declaring an array:
```yml
name: ExampleSheet
fields:
- name: Array of scalars
comment: This array is just an array of scalars
type: array
count: 2
- name: Erroneous array
comment: This array fails schema validation because it contains the fields key with no fields
type: array
count: 2
fields: []
- name: Array of single explicit column
comment: Schema consumers should consider this an array of scalars that are also a link
type: array
count: 2
fields:
- type: link
targets: [Item]
- name: Array of structs
comment: This array is a list of structs
type: array
count: 2
fields:
- type: scalar
- type: scalar
```
The comment on each array declaration describes what the array is declaring.
For a more concrete example, let's look at `SpecialShop`:
```yml ```yml
name: SpecialShop name: SpecialShop
fields: fields:
@ -114,17 +144,29 @@ fields:
- name: Item - name: Item
type: array type: array
count: 2 count: 2
fields:
- type: link
targets: [Item]
- name: Category - name: Category
type: array type: array
count: 2 count: 2
fields:
- type: link
targets: [SpecialShopItemCategory]
- name: ItemCost - name: ItemCost
type: array type: array
count: 3 count: 3
- name: Quest - name: Quest
type: array type: array
count: 2 count: 2
fields:
- type: link
targets: [Quest]
- name: Unknown - name: Unknown
- name: AchievementUnlock - name: AchievementUnlock
fields:
- type: link
targets: [Achievement]
- name: CollectabilityCost - name: CollectabilityCost
type: array type: array
count: 3 count: 3
@ -138,6 +180,8 @@ fields:
type: array type: array
count: 3 count: 3
- name: Quest - name: Quest
type: link
targets: [Quest]
- type: scalar - type: scalar
- type: scalar - type: scalar
- name: CompleteText - name: CompleteText
@ -179,29 +223,29 @@ struct SpecialShop
}; };
``` ```
As you can see, the overall schema is similar to defining structures in YML but omitting the actual data type. As you can see, the overall schema is similar to defining structures in YML but omitting the actual data type.
This nested capability allows you to define complex structures. However, to cut down on overall parsing complexity, This nested capability allows you to define complex structures. From experience, we have seen that
based on existing knowledge of the EXH data, **you may only nest twice.** you should not need to nest more than 2 levels deep, but schema consumers should still support this.
### Linking ### Linking
The sheets that power the game are relational in nature, so the schema supports a few different kinds of linking. The sheets that power the game are relational in nature, so the schema supports a few different kinds of linking.
#### Single Link #### Single Link
To define a link, simply add a link object: To define a single link, set the `type` to `link` and define the `targets` array:
```yml ```yml
- name: Quest - name: Quest
link: type: link
target: [Quest] targets: [Quest]
``` ```
Note that the link target is an array of strings. They must be sheet names, and there must be at least one sheet. To link to one sheet, leave a single sheet in the array. Note that the link targets is an array of strings. They must be sheet names, and there must be at least one sheet. To link to one sheet, leave a single sheet in the array.
#### Multi Link #### Multi Link
A sheet's single column can link to multiple columns: A sheet's single column can link to multiple columns:
```yml ```yml
- name: Requirement - name: Requirement
link: type: link
target: [Quest, GrandCompany] targets: [Quest, GrandCompany]
``` ```
In this case, disparate sheet key ranges will provide the ability to determine which sheet a link should resolve to. In this case, disparate sheet key ranges will provide the ability for consumers to determine which sheet a link should resolve to.
For example, if a row's `Requirement` is `2`, it will resolve to `GrandCompany`, because row `2` exists in `GrandCompany` and not in `Quest.` For example, if a row's `Requirement` is `2`, it will resolve to `GrandCompany`, because row `2` exists in `GrandCompany` and not in `Quest.`
The same thing happens in the other direction: if `Requirement` is `69208`, it will link to `Quest` and not `GrandCompany` for the same reason. The same thing happens in the other direction: if `Requirement` is `69208`, it will link to `Quest` and not `GrandCompany` for the same reason.
@ -210,14 +254,14 @@ A sheet's single column can link to multiple columns depending on another field
```yml ```yml
- name: Location - name: Location
comment: PlaceName when LocationKey is 1, ContentFinderCondition when LocationKey is 4 comment: PlaceName when LocationKey is 1, ContentFinderCondition when LocationKey is 4
link: type: link
condition: condition:
switch: LocationKey switch: LocationKey
cases: cases:
1: [PlaceName] 1: [PlaceName]
4: [ContentFinderCondition] 4: [ContentFinderCondition]
``` ```
The targets array is not required for conditional links. The targets array is not required for conditional links, and if both are specified, the file will fail schema validation.
When defining the link, add a `condition` object with a `switch` key that defines the field to switch on the value of. When defining the link, add a `condition` object with a `switch` key that defines the field to switch on the value of.
The `cases` dictionary contains arrays of the sheet to reference when the case matches. The `cases` dictionary contains arrays of the sheet to reference when the case matches.
@ -242,7 +286,7 @@ Yes, the `case` dictionary may contain an *array*. This means that each case can
32: [Orchestrion] 32: [Orchestrion]
36: [SubmarinePart] 36: [SubmarinePart]
``` ```
The `AdditionalData` column does a lot of heavy lifting. We can assume during game execution that the use of the field is heavily based on context, The `AdditionalData` column in `Item` does a lot of heavy lifting. We can assume during game execution that the use of the field is heavily based on context,
but for research and data exploration, having the ability to define the exact sheet is very useful. Here, we can see that when `FilterGroup` is `14`, but for research and data exploration, having the ability to define the exact sheet is useful. Here, we can see that when `FilterGroup` is `14`,
we can link to any of `HousingExterior`, `HousingInterior`, `HousingYardObject`, `HousingFurniture`, `HousingPreset`, or finally `HousingUnitedExterior`. we can link to any of `HousingExterior`, `HousingInterior`, `HousingYardObject`, `HousingFurniture`, `HousingPreset`, or finally `HousingUnitedExterior`.
This works because the value for `AdditionalData` are distinct ranges, even when `FilterGroup` is `14`, thus allowing the definition here to behave like a multi link. This works because the value for `AdditionalData` are distinct ranges, even when `FilterGroup` is `14`, thus allowing the definition here to behave like a multi link.