using DatReaderWriter; using DatReaderWriter.DBObjs; namespace AcDream.Core.World; public static class LandblockLoader { private const uint GfxObjMask = 0x01000000u; private const uint SetupMask = 0x02000000u; private const uint TypeMask = 0xFF000000u; /// /// Load a single landblock (heightmap + static objects) from the dats. /// /// Null if the landblock is missing from the cell dat. public static LoadedLandblock? Load(DatCollection dats, uint landblockId) { var block = dats.Get(landblockId); if (block is null) return null; var info = dats.Get((landblockId & 0xFFFF0000u) | 0xFFFEu); var entities = info is null ? Array.Empty() : BuildEntitiesFromInfo(info); return new LoadedLandblock(landblockId, block, entities); } /// /// Pure mapping from a parsed LandBlockInfo to a list of WorldEntity. /// Each Stab and BuildingInfo becomes one entity. Unsupported id types /// (neither GfxObj 0x01xxxxxx nor Setup 0x02xxxxxx) are silently skipped. /// MeshRefs is left empty at this stage — Task 5 populates it. /// public static IReadOnlyList BuildEntitiesFromInfo(LandBlockInfo info) { var result = new List(info.Objects.Count + info.Buildings.Count); uint nextId = 1; foreach (var stab in info.Objects) { if (!IsSupported(stab.Id)) continue; result.Add(new WorldEntity { Id = nextId++, SourceGfxObjOrSetupId = stab.Id, Position = stab.Frame.Origin, Rotation = stab.Frame.Orientation, MeshRefs = Array.Empty(), }); } foreach (var building in info.Buildings) { if (!IsSupported(building.ModelId)) continue; result.Add(new WorldEntity { Id = nextId++, SourceGfxObjOrSetupId = building.ModelId, Position = building.Frame.Origin, Rotation = building.Frame.Orientation, MeshRefs = Array.Empty(), }); } return result; } private static bool IsSupported(uint id) { var type = id & TypeMask; return type == GfxObjMask || type == SetupMask; } }