From b16dcb8a6694ec3b093bf7022155b09ac796774a Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Sat, 7 Oct 2023 19:46:23 +1100 Subject: [PATCH] object manager (#10) * first pass * working * cleanup * add some logging around this --- OpenLocoTool/DatFileParsing/LocoObject.cs | 6 +++ .../DatFileParsing/SawyerStreamReader.cs | 24 +++++----- OpenLocoTool/ObjectManager.cs | 44 +++++++++++++++++++ OpenLocoTool/Objects/CargoObject.cs | 11 ++++- OpenLocoTool/Objects/Vehicle/VehicleObject.cs | 20 ++++++++- OpenLocoToolCommon/Logger.cs | 4 +- OpenLocoToolGui/MainForm.cs | 2 + OpenLocoToolGui/MainFormModel.cs | 17 +++++++ 8 files changed, 111 insertions(+), 17 deletions(-) create mode 100644 OpenLocoTool/ObjectManager.cs diff --git a/OpenLocoTool/DatFileParsing/LocoObject.cs b/OpenLocoTool/DatFileParsing/LocoObject.cs index 92d06790..c21e6a97 100644 --- a/OpenLocoTool/DatFileParsing/LocoObject.cs +++ b/OpenLocoTool/DatFileParsing/LocoObject.cs @@ -62,6 +62,12 @@ public interface ILocoStructVariableData ReadOnlySpan Save(); } + [TypeConverter(typeof(ExpandableObjectConverter))] + public interface ILocoStructStringTablePostLoad + { + void LoadPostStringTable(StringTable stringTable); + } + [TypeConverter(typeof(ExpandableObjectConverter))] public interface ILocoObject { diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs index 1f11028f..2e25fd54 100644 --- a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs +++ b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs @@ -82,24 +82,26 @@ public ILocoObject LoadFull(string filename, bool loadExtra = true) var (stringTable, stringTableBytesRead) = LoadStringTable(remainingData, locoStruct); remainingData = remainingData[stringTableBytesRead..]; + if (locoStruct is ILocoStructStringTablePostLoad locoStructString) + { + locoStructString.LoadPostStringTable(stringTable); + } + // special handling per object type if (loadExtra && locoStruct is ILocoStructVariableData locoStructExtra) { remainingData = locoStructExtra.Load(remainingData); } - //try - { - var (g1Header, imageTable, imageTableBytesRead) = LoadImageTable(remainingData); - Logger.Log(LogLevel.Info, $"FileLength={new FileInfo(filename).Length} HeaderLength={S5Header.StructLength} DataLength={objectHeader.DataLength} StringTableLength={stringTableBytesRead} ImageTableLength={imageTableBytesRead}"); + var (g1Header, imageTable, imageTableBytesRead) = LoadImageTable(remainingData); + Logger.Log(LogLevel.Info, $"FileLength={new FileInfo(filename).Length} HeaderLength={S5Header.StructLength} DataLength={objectHeader.DataLength} StringTableLength={stringTableBytesRead} ImageTableLength={imageTableBytesRead}"); - return new LocoObject(s5Header, objectHeader, locoStruct, stringTable, g1Header, imageTable); - } - //catch (Exception ex) - //{ - // Logger.Error(ex.ToString()); - // return new LocoObject(objectHeader, locoStruct, stringTable, new G1Header(0, 0), new List()); - //} + var newObj = new LocoObject(s5Header, objectHeader, locoStruct, stringTable, g1Header, imageTable); + + // add to object manager + SObjectManager.Add(newObj); + + return newObj; } (StringTable table, int bytesRead) LoadStringTable(ReadOnlySpan data, ILocoStruct locoStruct) diff --git a/OpenLocoTool/ObjectManager.cs b/OpenLocoTool/ObjectManager.cs new file mode 100644 index 00000000..e1baf984 --- /dev/null +++ b/OpenLocoTool/ObjectManager.cs @@ -0,0 +1,44 @@ +using OpenLocoTool.DatFileParsing; +using OpenLocoTool.Headers; + +namespace OpenLocoTool +{ + public static class SObjectManager + { + static readonly Dictionary> Objects = new(); + + static SObjectManager() + { + foreach (var v in Enum.GetValues(typeof(ObjectType))) + { + Objects.Add((ObjectType)v, new List()); + } + } + + public static List Get(ObjectType type) + where T : ILocoStruct => Objects[type].Select(a => a.Object).Cast().ToList(); + + public static void Add(T obj) where T : ILocoObject + => Objects[obj.S5Header.ObjectType].Add(obj); + } + + // unused for now + //public class ObjectManager + //{ + // readonly Dictionary> Objects = new(); + + // public ObjectManager() + // { + // foreach (var v in Enum.GetValues(typeof(ObjectType))) + // { + // Objects.Add((ObjectType)v, new List()); + // } + // } + + // public List Get(ObjectType type) + // where T : ILocoStruct => Objects[type].Select(a => a.Object).Cast().ToList(); + + // public void Add(T obj) where T : ILocoObject + // => Objects[obj.S5Header.ObjectType].Add(obj); + //} +} diff --git a/OpenLocoTool/Objects/CargoObject.cs b/OpenLocoTool/Objects/CargoObject.cs index 49fdb5ec..84ef898a 100644 --- a/OpenLocoTool/Objects/CargoObject.cs +++ b/OpenLocoTool/Objects/CargoObject.cs @@ -16,7 +16,7 @@ public enum CargoObjectFlags : uint8_t [LocoStructSize(0x1F)] [LocoStringCount(4)] public record CargoObject( - [property: LocoStructOffset(0x00)] string_id Name, + //[property: LocoStructOffset(0x00)] string_id Name, [property: LocoStructOffset(0x02)] uint16_t var_02, [property: LocoStructOffset(0x04)] uint16_t var_04, [property: LocoStructOffset(0x06)] string_id UnitsAndCargoName, @@ -34,9 +34,16 @@ public record CargoObject( [property: LocoStructOffset(0x1B)] uint16_t PaymentFactor, [property: LocoStructOffset(0x1D)] uint8_t PaymentIndex, [property: LocoStructOffset(0x1E)] uint8_t UnitSize - ) : ILocoStruct + ) : ILocoStruct, ILocoStructStringTablePostLoad { public static ObjectType ObjectType => ObjectType.Cargo; public static int StructSize => 0x1F; + + public string Name { get; set; } + + public void LoadPostStringTable(StringTable stringTable) + { + Name = stringTable[(0, (LanguageId)0)]; + } } } \ No newline at end of file diff --git a/OpenLocoTool/Objects/Vehicle/VehicleObject.cs b/OpenLocoTool/Objects/Vehicle/VehicleObject.cs index 031ab9db..97fd6b8e 100644 --- a/OpenLocoTool/Objects/Vehicle/VehicleObject.cs +++ b/OpenLocoTool/Objects/Vehicle/VehicleObject.cs @@ -8,11 +8,13 @@ namespace OpenLocoTool.Objects [LocoStructSize(0x15E)] public class VehicleObject : ILocoStruct, ILocoStructVariableData { - public const ObjectType ObjType = ObjectType.Vehicle; + public static ObjectType ObjectType => ObjectType.Vehicle; public const int StructSize = 0x15E; public const int MaxBodySprites = 4; public List CargoMatchFlags { get; set; } = new(); + public List CompatibleCargo { get; set; } = new(); + public VehicleObject(ushort name, TransportMode mode, VehicleType type, byte var_04, byte trackType, byte numMods, byte costIndex, short costFactor, byte reliability, byte runCostIndex, short runCostFactor, byte colourType, byte numCompat, ushort[] compatibleVehicles, byte[] requiredTrackExtras, VehicleObjectUnk[] var_24, BodySprite[] bodySprites, BogieSprite[] bogieSprites, ushort power, short speed, short rackSpeed, ushort weight, VehicleObjectFlags flags, byte[] maxCargo, uint[] cargoTypes, byte[] cargoTypeSpriteOffsets, byte numSimultaneousCargoTypes, SimpleAnimation[] animation, byte var_113, ushort designed, ushort obsolete, byte rackRailType, DrivingSoundType drivingSoundType, byte[] pad_135, byte numStartSounds, byte[] startSounds) { Name = name; @@ -77,7 +79,7 @@ public VehicleObject(ushort name, TransportMode mode, VehicleType type, byte var [LocoStructOffset(0xDE)] public uint16_t Weight { get; set; } [LocoStructOffset(0xE0)] public VehicleObjectFlags Flags { get; set; } [LocoStructOffset(0xE2), LocoArrayLength(2)] public uint8_t[] MaxCargo { get; set; } // size is relative to the first cargoTypes - [LocoStructOffset(0xE4), LocoArrayLength(2)] public uint32_t[] CargoTypes { get; set; } + [LocoStructOffset(0xE4), LocoArrayLength(2), Browsable(false)] public uint32_t[] CargoTypes { get; set; } [LocoStructOffset(0xEC), LocoArrayLength(32)] public uint8_t[] CargoTypeSpriteOffsets { get; set; } [LocoStructOffset(0x10C)] public uint8_t NumSimultaneousCargoTypes { get; set; } [LocoStructOffset(0x10D), LocoArrayLength(2)] public SimpleAnimation[] Animation { get; set; } @@ -118,6 +120,9 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) remainingData = remainingData[(S5Header.StructLength * NumMods)..]; // cargo types + // this whole bullshit is mostly copied and pasted from openloco + // but we need to do it to a) load the cargo match flags and b) to move the stream to the right offset to load the next variable data + // afterwards, we'll do nice c# load of the cargo based on the match flags for (var i = 0; i < CargoTypes.Length; ++i) { var index = NumSimultaneousCargoTypes; @@ -139,11 +144,14 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) var unk = remainingData[0]; remainingData = remainingData[1..]; // uint8_t + var cargoObjs = SObjectManager.Get(ObjectType.Cargo); + for (var cargoType = 0; cargoType < 32; ++cargoType) // 32 is ObjectType::MaxObjects[cargo] { // until the rest of this is implemented, these values will be wrong // but as long as they're non-zero to pass the == 0 check below, it'll work CargoTypes[index] |= 1U << cargoType; + CargoTypeSpriteOffsets[cargoType] = unk; } ptr = BitConverter.ToUInt16(remainingData[0..2]); @@ -161,6 +169,14 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) } } + foreach (var cargo in SObjectManager.Get(ObjectType.Cargo)) + { + if (CargoMatchFlags.Contains(cargo.MatchFlags)) + { + CompatibleCargo.Add(cargo); + } + } + // animation foreach (var anim in Animation) { diff --git a/OpenLocoToolCommon/Logger.cs b/OpenLocoToolCommon/Logger.cs index 5c87bb72..d9f3f2aa 100644 --- a/OpenLocoToolCommon/Logger.cs +++ b/OpenLocoToolCommon/Logger.cs @@ -20,7 +20,7 @@ public override string ToString() public class Logger : ILogger { - readonly List loglines = new(); + public readonly List Logs = new(); public LogLevel Level = LogLevel.Info; public event EventHandler LogAdded; @@ -28,7 +28,7 @@ public class Logger : ILogger public void Log(LogLevel level, string message) { var log = new LogLine { Time = DateTime.Now, Level = level, Message = message }; - loglines.Add(log); + Logs.Add(log); if (Level <= level) { diff --git a/OpenLocoToolGui/MainForm.cs b/OpenLocoToolGui/MainForm.cs index 3eb9e9b7..262c81e3 100644 --- a/OpenLocoToolGui/MainForm.cs +++ b/OpenLocoToolGui/MainForm.cs @@ -90,6 +90,8 @@ public MainForm() private void MainForm_Load(object sender, EventArgs e) { + // pre-add any existing log lines + lbLogs.Items.AddRange(((Logger)logger).Logs.ToArray()); // can only do this after window handle has been created (so can't do in constructor) ((Logger)logger).LogAdded += (s, e) => lbLogs.Invoke(() => lbLogs.Items.Insert(0, e.Log.ToString())); diff --git a/OpenLocoToolGui/MainFormModel.cs b/OpenLocoToolGui/MainFormModel.cs index 61ab21e9..adb4dcd3 100644 --- a/OpenLocoToolGui/MainFormModel.cs +++ b/OpenLocoToolGui/MainFormModel.cs @@ -7,6 +7,7 @@ using OpenLocoTool.DatFileParsing; using OpenLocoTool.Objects; using OpenLocoToolCommon; +using OpenLocoTool.Headers; namespace OpenLocoToolGui { @@ -20,6 +21,8 @@ class MainFormModel public ObjectCache ObjectCache { get; private set; } = new(); + //public OpenLocoTool.ObjectManager ObjectManager { get; private set; } = new(); + public string PaletteFile { get => Settings.PaletteFile; @@ -56,6 +59,20 @@ public MainFormModel(ILogger logger, string settingsFile) writer = new SawyerStreamWriter(logger); LoadSettings(settingsFile); + + // Load all cargo objects on startup + // Until a better solution is found (dynamic load-on-demand) we'll just do this + // for now. We'll have to do this for every dependent object type + + var dependentObjectTypes = new HashSet() { ObjectType.Cargo }; + foreach (var depObjectType in dependentObjectTypes) + { + logger.Debug($"Preloading dependent {depObjectType} objects"); + } + foreach (var dep in HeaderIndex.Where(kvp => dependentObjectTypes.Contains(kvp.Value.ObjectType))) + { + reader.LoadFull(dep.Key); + } } public GuiSettings Settings { get; private set; }