diff --git a/src/Lumina.Tests/ExcelRows.cs b/src/Lumina.Tests/ExcelRows.cs index 4f57693a..9f4d57a5 100644 --- a/src/Lumina.Tests/ExcelRows.cs +++ b/src/Lumina.Tests/ExcelRows.cs @@ -1,4 +1,6 @@ using Lumina.Excel; +using Lumina.Text.ReadOnly; +using System.CodeDom.Compiler; namespace Lumina.Tests; @@ -209,4 +211,28 @@ public readonly struct Description( ExcelPage page, uint offset, uint row ) : IE static Description IExcelRow.Create( ExcelPage page, uint offset, uint row ) => new( page, offset, row ); +} + +[Sheet( "GatheringPointBase" )] +public readonly unsafe struct GatheringPointBase( ExcelPage page, uint offset, uint row ) : IExcelRow +{ + public uint RowId => row; + + public readonly RowRef GatheringType => new( page.Module, (uint)page.ReadInt32( offset ), page.Language ); + + static GatheringPointBase IExcelRow.Create( ExcelPage page, uint offset, uint row ) => + new( page, offset, row ); +} + +[Sheet( "GatheringType" )] +public readonly struct GatheringType( ExcelPage page, uint offset, uint row ) : IExcelRow +{ + public uint RowId => row; + + public readonly ReadOnlySeString Name => page.ReadString( offset, offset ); + public readonly int IconMain => page.ReadInt32( offset + 4 ); + public readonly int IconOff => page.ReadInt32( offset + 8 ); + + static GatheringType IExcelRow.Create( ExcelPage page, uint offset, uint row ) => + new( page, offset, row ); } \ No newline at end of file diff --git a/src/Lumina.Tests/ExcelTests.cs b/src/Lumina.Tests/ExcelTests.cs index ba036b98..93574f36 100644 --- a/src/Lumina.Tests/ExcelTests.cs +++ b/src/Lumina.Tests/ExcelTests.cs @@ -90,4 +90,14 @@ public void RowRefIntervalTree() Assert.Equal( brute, interval ); } + + [RequiresGameInstallationFact] + public void IndirectStringSheet() + { + var gameData = RequiresGameInstallationFact.CreateGameData(); + + var row = gameData.GetExcelSheet()!.GetRow( 30 ); + var name = row.GatheringType.ValueNullable?.Name; + Assert.Equal( "Harvesting", name ); + } } diff --git a/src/Lumina/Excel/ExcelPage.cs b/src/Lumina/Excel/ExcelPage.cs index e31e977e..c1294c68 100644 --- a/src/Lumina/Excel/ExcelPage.cs +++ b/src/Lumina/Excel/ExcelPage.cs @@ -15,28 +15,31 @@ namespace Lumina.Excel; /// /// If you only want to read excel sheets, please refrain from touching this class. This class exists mostly as an implementation detail for parsing excel rows. /// -[EditorBrowsable( EditorBrowsableState.Advanced )] public sealed class ExcelPage { + /// + /// The raw sheet that this page belongs to. + /// + public RawExcelSheet Sheet { get; } + /// /// The module that this page belongs to. /// - public ExcelModule Module { get; } + public ExcelModule Module => Sheet.Module; /// /// The associated language of the page. /// - public Language Language { get; } + public Language Language => Sheet.Language; private readonly byte[] data; private ReadOnlyMemory< byte > Data => data; private readonly ushort dataOffset; - internal ExcelPage( ExcelModule module, Language language, byte[] pageData, ushort headerDataOffset ) + internal ExcelPage( RawExcelSheet sheet, byte[] pageData, ushort headerDataOffset ) { - Module = module; - Language = language; + Sheet = sheet; data = pageData; dataOffset = headerDataOffset; } diff --git a/src/Lumina/Excel/RawExcelSheet.cs b/src/Lumina/Excel/RawExcelSheet.cs index 301f48ca..8255080b 100644 --- a/src/Lumina/Excel/RawExcelSheet.cs +++ b/src/Lumina/Excel/RawExcelSheet.cs @@ -110,7 +110,7 @@ internal RawExcelSheet( ExcelModule module, ExcelHeaderFile headerFile, Language if( fileData == null ) continue; - var newPage = _pages[ pageIdx ] = new( Module, Language, fileData.Data, headerFile.Header.DataOffset ); + var newPage = _pages[ pageIdx ] = new( this, fileData.Data, headerFile.Header.DataOffset ); // If row count information from exh file is incorrect, cope with it. if( i + fileData.RowData.Count > _rowOffsetLookupTable.Length ) diff --git a/src/Lumina/Excel/RawRow.cs b/src/Lumina/Excel/RawRow.cs new file mode 100644 index 00000000..783677a2 --- /dev/null +++ b/src/Lumina/Excel/RawRow.cs @@ -0,0 +1,275 @@ +using Lumina.Data.Structs.Excel; +using Lumina.Text.ReadOnly; +using System; +using System.Collections.Generic; + +namespace Lumina.Excel; + +/// +/// An type for explicitly reading data from an . +/// +/// +/// This type is designed to be used to read from arbitrary columns and offsets. +/// +public readonly struct RawRow( ExcelPage page, uint offset, uint row ) : IExcelRow +{ + /// + /// The associated of the row. + /// + public ExcelPage Page => page; + + /// + /// Offset to the referenced row in the . + /// + public uint Offset => offset; + + /// + public uint RowId => row; + + /// + public IReadOnlyList< ExcelColumnDefinition > Columns => + page.Sheet.Columns; + + /// + public ushort GetColumnOffset( int columnIdx ) => + page.Sheet.GetColumnOffset( columnIdx ); + + /// + /// Reads the value of the specified column. + /// + /// + /// Returns the value as a boxed . + /// Thrown when the column is of an unknown type. + public object ReadColumn( int columnIdx ) + { + var column = page.Sheet.Columns[columnIdx]; + return column.Type switch + { + ExcelColumnDataType.String => ReadString( column.Offset ), + ExcelColumnDataType.Bool => ReadBool( column.Offset ), + ExcelColumnDataType.Int8 => ReadInt8( column.Offset ), + ExcelColumnDataType.UInt8 => ReadUInt8( column.Offset ), + ExcelColumnDataType.Int16 => ReadInt16( column.Offset ), + ExcelColumnDataType.UInt16 => ReadUInt16( column.Offset ), + ExcelColumnDataType.Int32 => ReadInt32( column.Offset ), + ExcelColumnDataType.UInt32 => ReadUInt32( column.Offset ), + ExcelColumnDataType.Float32 => ReadFloat32( column.Offset ), + ExcelColumnDataType.Int64 => ReadInt64( column.Offset ), + ExcelColumnDataType.UInt64 => ReadUInt64( column.Offset ), + >= ExcelColumnDataType.PackedBool0 and <= ExcelColumnDataType.PackedBool7 => + page.ReadPackedBool( column.Offset, (byte)( column.Type - ExcelColumnDataType.PackedBool0 ) ), + _ => throw new InvalidOperationException( $"Unknown column type {column.Type}" ) + }; + } + + /// + /// Reads a at . + /// + /// Offset of the field inside the row. + /// + public ReadOnlySeString ReadString( nuint offset ) => + page.ReadString( Offset + offset, Offset ); + + /// + /// Reads a at . + /// + /// Offset of the field inside the row. + /// + public bool ReadBool( nuint offset ) => + page.ReadBool( Offset + offset ); + + /// + /// Reads a at . + /// + /// Offset of the field inside the row. + /// + public sbyte ReadInt8( nuint offset ) => + page.ReadInt8( Offset + offset ); + + /// + /// Reads a at . + /// + /// Offset of the field inside the row. + /// + public byte ReadUInt8( nuint offset ) => + page.ReadUInt8( Offset + offset ); + + /// + /// Reads a at . + /// + /// Offset of the field inside the row. + /// + public short ReadInt16( nuint offset ) => + page.ReadInt16( Offset + offset ); + + + /// + /// Reads a at . + /// + /// Offset of the field inside the row. + /// + public ushort ReadUInt16( nuint offset ) => + page.ReadUInt16( Offset + offset ); + + /// + /// Reads a at . + /// + /// Offset of the field inside the row. + /// + public int ReadInt32( nuint offset ) => + page.ReadInt32( Offset + offset ); + + /// + /// Reads a at . + /// + /// Offset of the field inside the row. + /// + public uint ReadUInt32( nuint offset ) => + page.ReadUInt32( Offset + offset ); + + /// + /// Reads a at . + /// + /// Offset of the field inside the row. + /// + public float ReadFloat32( nuint offset ) => + page.ReadFloat32( Offset + offset ); + + /// + /// Reads a at . + /// + /// Offset of the field inside the row. + /// + public long ReadInt64( nuint offset ) => + page.ReadInt64( Offset + offset ); + + /// + /// Reads a at . + /// + /// Offset of the field inside the row. + /// + public ulong ReadUInt64( nuint offset ) => + page.ReadUInt64( Offset + offset ); + + /// + /// Reads a at at bit offset . + /// + /// Offset of the field inside the row. + /// Bit offset of the field inside the byte. (0 - 7) + /// + public bool ReadPackedBool( nuint offset, byte bit ) => + page.ReadPackedBool( Offset + offset, bit ); + + /// + /// Reads a at . + /// + /// The index of the column. + /// + public ReadOnlySeString ReadStringColumn( int columnIdx ) => + ReadString( GetColumnOffset( columnIdx ) ); + + /// + /// Reads a at . + /// + /// The index of the column. + /// + public bool ReadBoolColumn( int columnIdx ) => + ReadBool( GetColumnOffset( columnIdx ) ); + + /// + /// Reads a at . + /// + /// The index of the column. + /// + public sbyte ReadInt8Column( int columnIdx ) => + ReadInt8( GetColumnOffset( columnIdx ) ); + + /// + /// Reads a at . + /// + /// The index of the column. + /// + public byte ReadUInt8Column( int columnIdx ) => + ReadUInt8( GetColumnOffset( columnIdx ) ); + + /// + /// Reads a at . + /// + /// The index of the column. + /// + public short ReadInt16Column( int columnIdx ) => + ReadInt16( GetColumnOffset( columnIdx ) ); + + /// + /// Reads a at . + /// + /// The index of the column. + /// + public ushort ReadUInt16Column( int columnIdx ) => + ReadUInt16( GetColumnOffset( columnIdx ) ); + + /// + /// Reads a at . + /// + /// The index of the column. + /// + public int ReadInt32Column( int columnIdx ) => + ReadInt32( GetColumnOffset( columnIdx ) ); + + /// + /// Reads a at . + /// + /// The index of the column. + /// + public uint ReadUInt32Column( int columnIdx ) => + ReadUInt32( GetColumnOffset( columnIdx ) ); + + /// + /// Reads a at . + /// + /// The index of the column. + /// + public float ReadFloat32Column( int columnIdx ) => + ReadFloat32( GetColumnOffset( columnIdx ) ); + + /// + /// Reads a at . + /// + /// The index of the column. + /// + public long ReadInt64Column( int columnIdx ) => + ReadInt64( GetColumnOffset( columnIdx ) ); + + /// + /// Reads a at . + /// + /// The index of the column. + /// + public ulong ReadUInt64Column( int columnIdx ) => + ReadUInt64( GetColumnOffset( columnIdx ) ); + + /// + /// Reads a at at an arbitrary bit offset . + /// + /// The index of the column. + /// Bit offset of the field inside the byte. (0 - 7) + /// + public bool ReadPackedBoolColumn( int columnIdx, byte bit ) => + ReadPackedBool( GetColumnOffset( columnIdx ), bit ); + + /// + /// Reads a at . + /// + /// Thrown when the column not of a packed bool type. + /// + public bool ReadPackedBoolColumn( int columnIdx ) => + Columns[columnIdx].Type switch + { + var t when t < ExcelColumnDataType.PackedBool0 || t > ExcelColumnDataType.PackedBool7 => + throw new InvalidOperationException( $"Unknown column type {t}" ), + var t => ReadPackedBoolColumn( columnIdx, (byte)( t - ExcelColumnDataType.PackedBool0 ) ) + }; + + static RawRow IExcelRow.Create( ExcelPage page, uint offset, uint row ) => + new( page, offset, row ); +} diff --git a/src/Lumina/Excel/RawSubrow.cs b/src/Lumina/Excel/RawSubrow.cs new file mode 100644 index 00000000..7686b20b --- /dev/null +++ b/src/Lumina/Excel/RawSubrow.cs @@ -0,0 +1,143 @@ +using Lumina.Data.Structs.Excel; +using Lumina.Text.ReadOnly; +using System; +using System.Collections.Generic; + +namespace Lumina.Excel; + +/// +/// An type for explicitly reading data from an . +/// +/// +public readonly struct RawSubrow( ExcelPage page, uint offset, uint row, ushort subrow ) : IExcelSubrow +{ + /// + public ExcelPage Page => page; + + /// + /// Offset to the referenced row in the . + /// + public uint Offset => offset; + + /// + public uint RowId => row; + + /// + public ushort SubrowId => subrow; + + /// + public IReadOnlyList Columns => + page.Sheet.Columns; + + /// + public ushort GetColumnOffset( int columnIdx ) => + page.Sheet.GetColumnOffset( columnIdx ); + + /// + public ReadOnlySeString ReadString( nuint offset ) => + page.ReadString( Offset + offset, Offset ); + + /// + public bool ReadBool( nuint offset ) => + page.ReadBool( Offset + offset ); + + /// + public sbyte ReadInt8( nuint offset ) => + page.ReadInt8( Offset + offset ); + + /// + public byte ReadUInt8( nuint offset ) => + page.ReadUInt8( Offset + offset ); + + /// + public short ReadInt16( nuint offset ) => + page.ReadInt16( Offset + offset ); + + /// + public ushort ReadUInt16( nuint offset ) => + page.ReadUInt16( Offset + offset ); + + /// + public int ReadInt32( nuint offset ) => + page.ReadInt32( Offset + offset ); + + /// + public uint ReadUInt32( nuint offset ) => + page.ReadUInt32( Offset + offset ); + + /// + public float ReadFloat32( nuint offset ) => + page.ReadFloat32( Offset + offset ); + + /// + public long ReadInt64( nuint offset ) => + page.ReadInt64( Offset + offset ); + + /// + public ulong ReadUInt64( nuint offset ) => + page.ReadUInt64( Offset + offset ); + + /// + public bool ReadPackedBool( nuint offset, byte bit ) => + page.ReadPackedBool( Offset + offset, bit ); + + /// + public ReadOnlySeString ReadStringColumn( int columnIdx ) => + ReadString( GetColumnOffset( columnIdx ) ); + + /// + public bool ReadBoolColumn( int columnIdx ) => + ReadBool( GetColumnOffset( columnIdx ) ); + + /// + public sbyte ReadInt8Column( int columnIdx ) => + ReadInt8( GetColumnOffset( columnIdx ) ); + + /// + public byte ReadUInt8Column( int columnIdx ) => + ReadUInt8( GetColumnOffset( columnIdx ) ); + + /// + public short ReadInt16Column( int columnIdx ) => + ReadInt16( GetColumnOffset( columnIdx ) ); + + /// + public ushort ReadUInt16Column( int columnIdx ) => + ReadUInt16( GetColumnOffset( columnIdx ) ); + + /// + public int ReadInt32Column( int columnIdx ) => + ReadInt32( GetColumnOffset( columnIdx ) ); + + /// + public uint ReadUInt32Column( int columnIdx ) => + ReadUInt32( GetColumnOffset( columnIdx ) ); + + /// + public float ReadFloat32Column( int columnIdx ) => + ReadFloat32( GetColumnOffset( columnIdx ) ); + + /// + public long ReadInt64Column( int columnIdx ) => + ReadInt64( GetColumnOffset( columnIdx ) ); + + /// + public ulong ReadUInt64Column( int columnIdx ) => + ReadUInt64( GetColumnOffset( columnIdx ) ); + + /// + public bool ReadPackedBoolColumn( int columnIdx, byte bit ) => + ReadPackedBool( GetColumnOffset( columnIdx ), bit ); + + /// + public bool ReadPackedBoolColumn( int columnIdx ) => + Columns[columnIdx].Type switch + { + var t when t < ExcelColumnDataType.PackedBool0 || t > ExcelColumnDataType.PackedBool7 => + throw new InvalidOperationException( $"Unknown column type {t}" ), + var t => ReadPackedBoolColumn( columnIdx, (byte)( t - ExcelColumnDataType.PackedBool0 ) ) + }; + + static RawSubrow IExcelSubrow.Create( ExcelPage page, uint offset, uint row, ushort subrow ) => + new( page, offset, row, subrow ); +} diff --git a/src/Lumina/Excel/RowRef{T}.cs b/src/Lumina/Excel/RowRef{T}.cs index 523e3217..119542d9 100644 --- a/src/Lumina/Excel/RowRef{T}.cs +++ b/src/Lumina/Excel/RowRef{T}.cs @@ -17,7 +17,11 @@ private ExcelSheet< T >? Sheet { get { if( module == null ) return null; - return _sheet ??= module.GetSheet< T >( language ); + return _sheet ??= module.GetSheet< T >( + language == Data.Language.None ? + null : // Use default language if null (or fall back to None) + language + ); } } diff --git a/src/Lumina/Excel/SubrowRef{T}.cs b/src/Lumina/Excel/SubrowRef{T}.cs index 856641c5..489d100d 100644 --- a/src/Lumina/Excel/SubrowRef{T}.cs +++ b/src/Lumina/Excel/SubrowRef{T}.cs @@ -17,7 +17,11 @@ private SubrowExcelSheet< T >? Sheet { get { if( module == null ) return null; - return _sheet ??= module.GetSubrowSheet< T >( language ); + return _sheet ??= module.GetSubrowSheet< T >( + language == Data.Language.None ? + null : // Use default language if null (or fall back to None) + language + ); } }