diff --git a/Editors/BmdEditor/Services/BmdElementLoader.cs b/Editors/BmdEditor/Services/BmdElementLoader.cs index cce5ede9e..448d0af16 100644 --- a/Editors/BmdEditor/Services/BmdElementLoader.cs +++ b/Editors/BmdEditor/Services/BmdElementLoader.cs @@ -1,9 +1,6 @@ using System; using System.Collections.ObjectModel; -using System.IO; -using System.Linq; using Shared.Core.PackFiles; -using Shared.Core.PackFiles.Models; using Shared.GameFormats.Bmd; using Serilog; using Editors.BmdEditor.ViewModels; @@ -13,17 +10,11 @@ namespace Editors.BmdEditor.Services /// /// Service for recursively loading BMD elements and their child elements /// - public class BmdElementLoader + public class BmdElementLoader(IPackFileService packFileService, BmdSceneCreator bmdSceneCreator) { - private readonly IPackFileService _packFileService; + private readonly IPackFileService _packFileService = packFileService; private readonly ILogger _logger = Serilog.Log.ForContext(); - private readonly BmdSceneCreator _bmdSceneCreator; - - public BmdElementLoader(IPackFileService packFileService, BmdSceneCreator bmdSceneCreator) - { - _packFileService = packFileService; - _bmdSceneCreator = bmdSceneCreator; - } + private readonly BmdSceneCreator _bmdSceneCreator = bmdSceneCreator; /// /// Loads all elements from a BMD file into the provided collections @@ -140,7 +131,7 @@ public void LoadChildElements(BmdInfoViewModel parentViewModel, BmdFile referenc private void LoadBmdInfos(BmdFile bmdFile, ObservableCollection bmdInfos, ObservableCollection allElements, bool loadChildBmds) { - for (int i = 0; i < bmdFile.BmdInfos.Count; i++) + for (var i = 0; i < bmdFile.BmdInfos.Count; i++) { var bmd = bmdFile.BmdInfos[i]; _logger.Information($"Processing BMD reference: {bmd.BmdString}"); @@ -176,10 +167,10 @@ private void LoadBmdInfos(BmdFile bmdFile, ObservableCollection battlefieldBuildings, + private static void LoadBattlefieldBuildings(BmdFile bmdFile, ObservableCollection battlefieldBuildings, ObservableCollection allElements) { - for (int i = 0; i < bmdFile.BattlefieldBuildings.Count; i++) + for (var i = 0; i < bmdFile.BattlefieldBuildings.Count; i++) { var building = bmdFile.BattlefieldBuildings[i]; var vm = new BattlefieldBuildingViewModel(building); @@ -188,7 +179,7 @@ private void LoadBattlefieldBuildings(BmdFile bmdFile, ObservableCollection battlefieldBuildingFars, + private static void LoadBattlefieldBuildingFars(BmdFile bmdFile, ObservableCollection battlefieldBuildingFars, ObservableCollection allElements) { foreach (var buildingFar in bmdFile.BattlefieldBuildingFars) @@ -199,7 +190,7 @@ private void LoadBattlefieldBuildingFars(BmdFile bmdFile, ObservableCollection captureLocations, + private static void LoadCaptureLocations(BmdFile bmdFile, ObservableCollection captureLocations, ObservableCollection allElements) { foreach (var captureLocation in bmdFile.CaptureLocations) @@ -210,7 +201,7 @@ private void LoadCaptureLocations(BmdFile bmdFile, ObservableCollection efLines, + private static void LoadEFLines(BmdFile bmdFile, ObservableCollection efLines, ObservableCollection allElements) { foreach (var efLine in bmdFile.EFLines) @@ -221,7 +212,7 @@ private void LoadEFLines(BmdFile bmdFile, ObservableCollection } } - private void LoadGoOutlines(BmdFile bmdFile, ObservableCollection goOutlines, + private static void LoadGoOutlines(BmdFile bmdFile, ObservableCollection goOutlines, ObservableCollection allElements) { foreach (var goOutline in bmdFile.GoOutlines) @@ -232,10 +223,10 @@ private void LoadGoOutlines(BmdFile bmdFile, ObservableCollection nonTerrainOutlines, + private static void LoadNonTerrainOutlines(BmdFile bmdFile, ObservableCollection nonTerrainOutlines, ObservableCollection allElements) { - for (int i = 0; i < bmdFile.NonTerrainOutlines.Count; i++) + for (var i = 0; i < bmdFile.NonTerrainOutlines.Count; i++) { var nonTerrainOutline = bmdFile.NonTerrainOutlines[i]; var vm = new NonTerrainOutlineViewModel(nonTerrainOutline); @@ -244,10 +235,10 @@ private void LoadNonTerrainOutlines(BmdFile bmdFile, ObservableCollection buildingProjectileEmitters, + private static void LoadBuildingProjectileEmitters(BmdFile bmdFile, ObservableCollection buildingProjectileEmitters, ObservableCollection allElements) { - for (int i = 0; i < bmdFile.BuildingProjectileEmitters.Count; i++) + for (var i = 0; i < bmdFile.BuildingProjectileEmitters.Count; i++) { var buildingProjectileEmitter = bmdFile.BuildingProjectileEmitters[i]; var vm = new BuildingProjectileEmitterViewModel(buildingProjectileEmitter); @@ -256,7 +247,7 @@ private void LoadBuildingProjectileEmitters(BmdFile bmdFile, ObservableCollectio } } - private void LoadZonesTemplates(BmdFile bmdFile, ObservableCollection zonesTemplates, + private static void LoadZonesTemplates(BmdFile bmdFile, ObservableCollection zonesTemplates, ObservableCollection allElements) { foreach (var zonesTemplate in bmdFile.ZonesTemplates) @@ -267,10 +258,10 @@ private void LoadZonesTemplates(BmdFile bmdFile, ObservableCollection props, + private static void LoadProps(BmdFile bmdFile, ObservableCollection props, ObservableCollection allElements) { - for (int i = 0; i < bmdFile.PropInfos.Count; i++) + for (var i = 0; i < bmdFile.PropInfos.Count; i++) { var propInfo = bmdFile.PropInfos[i]; var vm = new PropInfoViewModel(propInfo, propInfo.Rmv2Path); @@ -279,10 +270,10 @@ private void LoadProps(BmdFile bmdFile, ObservableCollection } } - private void LoadVfxInfos(BmdFile bmdFile, ObservableCollection vfxInfos, + private static void LoadVfxInfos(BmdFile bmdFile, ObservableCollection vfxInfos, ObservableCollection allElements) { - for (int i = 0; i < bmdFile.VfxInfos.Count; i++) + for (var i = 0; i < bmdFile.VfxInfos.Count; i++) { var vfx = bmdFile.VfxInfos[i]; var vm = new VfxInfoViewModel(vfx); @@ -291,10 +282,10 @@ private void LoadVfxInfos(BmdFile bmdFile, ObservableCollection pointLights, + private static void LoadPointLights(BmdFile bmdFile, ObservableCollection pointLights, ObservableCollection allElements) { - for (int i = 0; i < bmdFile.PointLights.Count; i++) + for (var i = 0; i < bmdFile.PointLights.Count; i++) { var light = bmdFile.PointLights[i]; var vm = new PointLightInfoViewModel(light); @@ -303,10 +294,10 @@ private void LoadPointLights(BmdFile bmdFile, ObservableCollection spotLights, + private static void LoadSpotLights(BmdFile bmdFile, ObservableCollection spotLights, ObservableCollection allElements) { - for (int i = 0; i < bmdFile.SpotLights.Count; i++) + for (var i = 0; i < bmdFile.SpotLights.Count; i++) { var light = bmdFile.SpotLights[i]; var vm = new SpotLightInfoViewModel(light); @@ -315,7 +306,7 @@ private void LoadSpotLights(BmdFile bmdFile, ObservableCollection sounds, + private static void LoadSounds(BmdFile bmdFile, ObservableCollection sounds, ObservableCollection allElements) { foreach (var sound in bmdFile.Sounds) @@ -326,10 +317,10 @@ private void LoadSounds(BmdFile bmdFile, ObservableCollection polyMeshes, + private static void LoadPolyMeshes(BmdFile bmdFile, ObservableCollection polyMeshes, ObservableCollection allElements) { - for (int i = 0; i < bmdFile.PolyMeshes.Count; i++) + for (var i = 0; i < bmdFile.PolyMeshes.Count; i++) { var mesh = bmdFile.PolyMeshes[i]; var vm = new PolyMeshInfoViewModel(mesh); @@ -338,10 +329,10 @@ private void LoadPolyMeshes(BmdFile bmdFile, ObservableCollection lightProbes, + private static void LoadLightProbes(BmdFile bmdFile, ObservableCollection lightProbes, ObservableCollection allElements) { - for (int i = 0; i < bmdFile.LightProbes.Count; i++) + for (var i = 0; i < bmdFile.LightProbes.Count; i++) { var probe = bmdFile.LightProbes[i]; var vm = new LightProbeInfoViewModel(probe); @@ -350,10 +341,10 @@ private void LoadLightProbes(BmdFile bmdFile, ObservableCollection terrainHoles, + private static void LoadTerrainHoles(BmdFile bmdFile, ObservableCollection terrainHoles, ObservableCollection allElements) { - for (int i = 0; i < bmdFile.TerrainHoles.Count; i++) + for (var i = 0; i < bmdFile.TerrainHoles.Count; i++) { var hole = bmdFile.TerrainHoles[i]; var vm = new TerrainHoleInfoViewModel(hole); @@ -362,7 +353,7 @@ private void LoadTerrainHoles(BmdFile bmdFile, ObservableCollection playableAreas, + private static void LoadPlayableAreas(BmdFile bmdFile, ObservableCollection playableAreas, ObservableCollection allElements) { if (bmdFile.PlayableArea != null) @@ -373,10 +364,10 @@ private void LoadPlayableAreas(BmdFile bmdFile, ObservableCollection cscInfos, + private static void LoadCscInfos(BmdFile bmdFile, ObservableCollection cscInfos, ObservableCollection allElements) { - for (int i = 0; i < bmdFile.CscInfos.Count; i++) + for (var i = 0; i < bmdFile.CscInfos.Count; i++) { var csc = bmdFile.CscInfos[i]; var vm = new CscInfoViewModel(csc); @@ -385,10 +376,10 @@ private void LoadCscInfos(BmdFile bmdFile, ObservableCollection deployments, + private static void LoadDeployments(BmdFile bmdFile, ObservableCollection deployments, ObservableCollection allElements) { - for (int i = 0; i < bmdFile.Deployments.Count; i++) + for (var i = 0; i < bmdFile.Deployments.Count; i++) { var deployment = bmdFile.Deployments[i]; var vm = new DeploymentViewModel(deployment); diff --git a/Editors/BmdEditor/Services/BmdPlaceholderNodes.cs b/Editors/BmdEditor/Services/BmdPlaceholderNodes.cs index fdca8e42f..536af82c2 100644 --- a/Editors/BmdEditor/Services/BmdPlaceholderNodes.cs +++ b/Editors/BmdEditor/Services/BmdPlaceholderNodes.cs @@ -2,23 +2,18 @@ using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using Shared.GameFormats.RigidModel; using Shared.GameFormats.RigidModel.Transforms; using GameWorld.Core.SceneNodes; namespace Editors.BmdEditor.Services { // Custom node class for VFX placeholders that renders a visible cube - public class VfxPlaceholderNode : GroupNode, IDrawableItem + public class VfxPlaceholderNode(string name = "VFX_Placeholder") : GroupNode(name), IDrawableItem { public Color NodeColour { get; set; } = Color.Purple; public Color SelectedNodeColour { get; set; } = Color.Magenta; public float Scale { get; set; } = 0.5f; - public VfxPlaceholderNode(string name = "VFX_Placeholder") : base(name) - { - } - public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { if (IsVisible) @@ -35,7 +30,8 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as VfxPlaceholderNode; + if (target is not VfxPlaceholderNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; typedTarget.Scale = Scale; @@ -44,16 +40,12 @@ public override void CopyInto(ISceneNode target) } // Custom node class for CSC placeholders that renders a visible cube - public class CscPlaceholderNode : GroupNode, IDrawableItem + public class CscPlaceholderNode(string name = "CSC_Placeholder") : GroupNode(name), IDrawableItem { public Color NodeColour { get; set; } = Color.Orange; public Color SelectedNodeColour { get; set; } = Color.DarkOrange; public float Scale { get; set; } = 0.4f; - public CscPlaceholderNode(string name = "CSC_Placeholder") : base(name) - { - } - public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { if (IsVisible) @@ -70,7 +62,8 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as CscPlaceholderNode; + if (target is not CscPlaceholderNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; typedTarget.Scale = Scale; @@ -79,17 +72,13 @@ public override void CopyInto(ISceneNode target) } // Custom node class for Light Probe placeholders that renders two spheres (inner and outer) - public class LightProbePlaceholderNode : GroupNode, IDrawableItem + public class LightProbePlaceholderNode(string name = "LightProbe_Placeholder") : GroupNode(name), IDrawableItem { public Color NodeColour { get; set; } = Color.Cyan; public Color SelectedNodeColour { get; set; } = Color.DarkCyan; public float OuterRadius { get; set; } = 1.0f; public float InnerRadius { get; set; } = 0.5f; - public LightProbePlaceholderNode(string name = "LightProbe_Placeholder") : base(name) - { - } - public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { if (IsVisible) @@ -110,7 +99,8 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as LightProbePlaceholderNode; + if (target is not LightProbePlaceholderNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; typedTarget.OuterRadius = OuterRadius; @@ -120,16 +110,12 @@ public override void CopyInto(ISceneNode target) } // Custom node class for Building Projectile Emitter placeholders that renders a visible cube - public class BuildingProjectileEmitterPlaceholderNode : GroupNode, IDrawableItem + public class BuildingProjectileEmitterPlaceholderNode(string name = "BuildingProjectileEmitter_Placeholder") : GroupNode(name), IDrawableItem { public Color NodeColour { get; set; } = Color.Red; public Color SelectedNodeColour { get; set; } = Color.DarkRed; public float Scale { get; set; } = 0.35f; - public BuildingProjectileEmitterPlaceholderNode(string name = "BuildingProjectileEmitter_Placeholder") : base(name) - { - } - public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { if (IsVisible) @@ -146,7 +132,8 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as BuildingProjectileEmitterPlaceholderNode; + if (target is not BuildingProjectileEmitterPlaceholderNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; typedTarget.Scale = Scale; @@ -155,16 +142,12 @@ public override void CopyInto(ISceneNode target) } // Custom node class for Battlefield Building placeholders that renders a visible cube - public class BattlefieldBuildingPlaceholderNode : GroupNode, IDrawableItem + public class BattlefieldBuildingPlaceholderNode(string name = "BattlefieldBuilding_Placeholder") : GroupNode(name), IDrawableItem { public Color NodeColour { get; set; } = Color.Brown; public Color SelectedNodeColour { get; set; } = Color.SaddleBrown; public float Scale { get; set; } = 0.6f; - public BattlefieldBuildingPlaceholderNode(string name = "BattlefieldBuilding_Placeholder") : base(name) - { - } - public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { if (IsVisible) @@ -181,7 +164,8 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as BattlefieldBuildingPlaceholderNode; + if (target is not BattlefieldBuildingPlaceholderNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; typedTarget.Scale = Scale; @@ -189,46 +173,95 @@ public override void CopyInto(ISceneNode target) } } - // Custom node class for NonTerrainOutline placeholders that renders connected vertices as lines - public class NonTerrainOutlineNode : GroupNode, IDrawableItem + // Custom node class for GoOutline placeholders that renders connected vertices as lines + public class GoOutlineNode(string name = "GoOutline") : GroupNode(name), IDrawableItem { - public Color NodeColour { get; set; } = Color.Cyan; - public Color SelectedNodeColour { get; set; } = Color.DarkCyan; - public List VertexList { get; set; } = new(); + public Color NodeColour { get; set; } = Color.Yellow; + public Color SelectedNodeColour { get; set; } = Color.Gold; + public List VertexList { get; set; } = []; - public NonTerrainOutlineNode(string name = "NonTerrainOutline") : base(name) + public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { + if (IsVisible && VertexList.Count >= 2) + { + var drawColour = NodeColour; + var worldTransform = ModelMatrix * parentWorld; + + // Create lines connecting all vertices in order, including closing the loop + var lineVertices = new List(); + + for (var i = 0; i < VertexList.Count; i++) + { + var currentVertex = VertexList[i]; + var nextVertex = VertexList[(i + 1) % VertexList.Count]; // Wrap around to connect last to first + + var startPos = new Vector3(currentVertex.X, 0, currentVertex.Y); + var endPos = new Vector3(nextVertex.X, 0, nextVertex.Y); + + var worldStart = Vector3.Transform(startPos, worldTransform); + var worldEnd = Vector3.Transform(endPos, worldTransform); + + // Add two vertices for each line segment + lineVertices.Add(new VertexPositionColor(worldStart, drawColour)); + lineVertices.Add(new VertexPositionColor(worldEnd, drawColour)); + } + + if (lineVertices.Count > 0) + { + renderEngine.AddRenderLines([.. lineVertices]); + } + } } + public override ISceneNode CreateCopyInstance() => new GoOutlineNode(); + + public override void CopyInto(ISceneNode target) + { + if (target is not GoOutlineNode typedTarget) + return; + typedTarget.NodeColour = NodeColour; + typedTarget.SelectedNodeColour = SelectedNodeColour; + typedTarget.VertexList = [.. VertexList]; + base.CopyInto(target); + } + } + + // Custom node class for NonTerrainOutline placeholders that renders connected vertices as lines + public class NonTerrainOutlineNode(string name = "NonTerrainOutline") : GroupNode(name), IDrawableItem + { + public Color NodeColour { get; set; } = Color.Cyan; + public Color SelectedNodeColour { get; set; } = Color.DarkCyan; + public List VertexList { get; set; } = []; + public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { if (IsVisible && VertexList.Count >= 2) { var drawColour = NodeColour; var worldTransform = ModelMatrix * parentWorld; - + // Create lines connecting all vertices in order, including closing the loop var lineVertices = new List(); - - for (int i = 0; i < VertexList.Count; i++) + + for (var i = 0; i < VertexList.Count; i++) { var currentVertex = VertexList[i]; var nextVertex = VertexList[(i + 1) % VertexList.Count]; // Wrap around to connect last to first - + var startPos = new Vector3(currentVertex.X, 0, currentVertex.Y); var endPos = new Vector3(nextVertex.X, 0, nextVertex.Y); - + var worldStart = Vector3.Transform(startPos, worldTransform); var worldEnd = Vector3.Transform(endPos, worldTransform); - + // Add two vertices for each line segment lineVertices.Add(new VertexPositionColor(worldStart, drawColour)); lineVertices.Add(new VertexPositionColor(worldEnd, drawColour)); } - + if (lineVertices.Count > 0) { - renderEngine.AddRenderLines(lineVertices.ToArray()); + renderEngine.AddRenderLines([.. lineVertices]); } } } @@ -237,24 +270,21 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as NonTerrainOutlineNode; + if (target is not NonTerrainOutlineNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; - typedTarget.VertexList = new List(VertexList); + typedTarget.VertexList = [.. VertexList]; base.CopyInto(target); } } // Custom node class for Boundary placeholders that renders connected vertices as lines - public class BoundaryNode : GroupNode, IDrawableItem + public class BoundaryNode(string name = "Boundary") : GroupNode(name), IDrawableItem { public Color NodeColour { get; set; } = Color.Magenta; public Color SelectedNodeColour { get; set; } = Color.Purple; - public List PointList { get; set; } = new(); - - public BoundaryNode(string name = "Boundary") : base(name) - { - } + public List PointList { get; set; } = []; public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { @@ -266,7 +296,7 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren // Create lines connecting all points in order, including closing the loop var lineVertices = new List(); - for (int i = 0; i < PointList.Count; i++) + for (var i = 0; i < PointList.Count; i++) { var currentPoint = PointList[i]; var nextPoint = PointList[(i + 1) % PointList.Count]; // Wrap around to connect last to first @@ -284,7 +314,7 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren if (lineVertices.Count > 0) { - renderEngine.AddRenderLines(lineVertices.ToArray()); + renderEngine.AddRenderLines([.. lineVertices]); } } } @@ -293,25 +323,22 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as BoundaryNode; + if (target is not BoundaryNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; - typedTarget.PointList = new List(PointList); + typedTarget.PointList = [.. PointList]; base.CopyInto(target); } } // Custom node class for Point Light spheres that renders a sphere with radius - public class PointLightSphereNode : GroupNode, IDrawableItem + public class PointLightSphereNode(string name = "PointLight_Sphere") : GroupNode(name), IDrawableItem { public Color NodeColour { get; set; } = Color.Yellow; public Color SelectedNodeColour { get; set; } = Color.Orange; public float Radius { get; set; } = 1.0f; - public PointLightSphereNode(string name = "PointLight_Sphere") : base(name) - { - } - public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { if (IsVisible) @@ -329,7 +356,8 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as PointLightSphereNode; + if (target is not PointLightSphereNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; typedTarget.Radius = Radius; @@ -341,7 +369,7 @@ public static VertexPositionColor[] CreateWireframeSphere(Matrix transform, Colo var lines = new List(); // Create latitude lines (horizontal circles) - for (int lat = 0; lat <= segments / 2; lat++) + for (var lat = 0; lat <= segments / 2; lat++) { var theta = MathF.PI * lat / (segments / 2); var sinTheta = MathF.Sin(theta); @@ -349,7 +377,7 @@ public static VertexPositionColor[] CreateWireframeSphere(Matrix transform, Colo Vector3? prevPoint = null; - for (int lon = 0; lon <= segments; lon++) // Use <= to close the circle + for (var lon = 0; lon <= segments; lon++) // Use <= to close the circle { var phi = 2 * MathF.PI * lon / segments; var sinPhi = MathF.Sin(phi); @@ -373,7 +401,7 @@ public static VertexPositionColor[] CreateWireframeSphere(Matrix transform, Colo } // Create longitude lines (vertical lines from pole to pole) - for (int lon = 0; lon < segments; lon++) + for (var lon = 0; lon < segments; lon++) { var phi = 2 * MathF.PI * lon / segments; var sinPhi = MathF.Sin(phi); @@ -381,7 +409,7 @@ public static VertexPositionColor[] CreateWireframeSphere(Matrix transform, Colo Vector3? prevPoint = null; - for (int lat = 0; lat <= segments / 2; lat++) + for (var lat = 0; lat <= segments / 2; lat++) { var theta = MathF.PI * lat / (segments / 2); var sinTheta = MathF.Sin(theta); @@ -404,12 +432,12 @@ public static VertexPositionColor[] CreateWireframeSphere(Matrix transform, Colo } } - return lines.ToArray(); + return [.. lines]; } } // Custom node class for Spot Light cones that renders a cone with direction - public class SpotLightConeNode : GroupNode, IDrawableItem + public class SpotLightConeNode(string name = "SpotLight_Cone") : GroupNode(name), IDrawableItem { public Color NodeColour { get; set; } = Color.LightBlue; public Color SelectedNodeColour { get; set; } = Color.Blue; @@ -419,10 +447,6 @@ public class SpotLightConeNode : GroupNode, IDrawableItem public RmvVector3 Position { get; set; } = new(); public Quaternion Quaternion { get; set; } = Quaternion.Identity; - public SpotLightConeNode(string name = "SpotLight_Cone") : base(name) - { - } - public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { if (IsVisible) @@ -448,7 +472,8 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as SpotLightConeNode; + if (target is not SpotLightConeNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; typedTarget.Length = Length; @@ -471,7 +496,7 @@ private static VertexPositionColor[] CreateWireframeCone(Matrix transform, Color // Create base circle points extending along positive X axis (X as forward) var basePoints = new Vector3[segments]; - for (int i = 0; i < segments; i++) + for (var i = 0; i < segments; i++) { var theta = 2 * MathF.PI * i / segments; basePoints[i] = new Vector3(length, radius * MathF.Cos(theta), radius * MathF.Sin(theta)); @@ -479,26 +504,26 @@ private static VertexPositionColor[] CreateWireframeCone(Matrix transform, Color } // Create lines from tip to base - for (int i = 0; i < segments; i++) + for (var i = 0; i < segments; i++) { lines.Add(new VertexPositionColor(tip, color)); lines.Add(new VertexPositionColor(basePoints[i], color)); } // Create base circle lines - for (int i = 0; i < segments; i++) + for (var i = 0; i < segments; i++) { var next = (i + 1) % segments; lines.Add(new VertexPositionColor(basePoints[i], color)); lines.Add(new VertexPositionColor(basePoints[next], color)); } - return lines.ToArray(); + return [.. lines]; } } // Custom node class for Terrain Hole edges that renders triangle edges only - public class TerrainHoleEdgesNode : GroupNode, IDrawableItem + public class TerrainHoleEdgesNode(string name = "TerrainHole_Edges") : GroupNode(name), IDrawableItem { public Color NodeColour { get; set; } = Color.Red; public Color SelectedNodeColour { get; set; } = Color.DarkRed; @@ -506,10 +531,6 @@ public class TerrainHoleEdgesNode : GroupNode, IDrawableItem public RmvVector3 SecondVert { get; set; } = new(); public RmvVector3 ThirdVert { get; set; } = new(); - public TerrainHoleEdgesNode(string name = "TerrainHole_Edges") : base(name) - { - } - public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { if (IsVisible) @@ -528,14 +549,14 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren v3 = Vector3.Transform(v3, worldTransform); // Draw triangle edges - renderEngine.AddRenderLines(new[] { + renderEngine.AddRenderLines([ new VertexPositionColor(v1, drawColour), new VertexPositionColor(v2, drawColour), new VertexPositionColor(v2, drawColour), new VertexPositionColor(v3, drawColour), new VertexPositionColor(v3, drawColour), new VertexPositionColor(v1, drawColour) - }); + ]); } } @@ -543,7 +564,8 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as TerrainHoleEdgesNode; + if (target is not TerrainHoleEdgesNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; typedTarget.FirstVert = FirstVert; @@ -554,18 +576,14 @@ public override void CopyInto(ISceneNode target) } // Custom node class for PolyMesh triangles that renders filled triangles with materials - public class PolyMeshTrianglesNode : GroupNode, IDrawableItem + public class PolyMeshTrianglesNode(string name = "PolyMesh_Triangles") : GroupNode(name), IDrawableItem { public Color NodeColour { get; set; } = Color.Green; public Color SelectedNodeColour { get; set; } = Color.DarkGreen; - public RmvVector3[] Vertices { get; set; } = Array.Empty(); - public ushort[] Triangles { get; set; } = Array.Empty(); + public RmvVector3[] Vertices { get; set; } = []; + public ushort[] Triangles { get; set; } = []; public string MaterialString { get; set; } = string.Empty; - public PolyMeshTrianglesNode(string name = "PolyMesh_Triangles") : base(name) - { - } - public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { if (IsVisible && Vertices.Length > 0 && Triangles.Length >= 3) @@ -575,7 +593,7 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren // Convert vertices to world space var worldVertices = new Vector3[Vertices.Length]; - for (int i = 0; i < Vertices.Length; i++) + for (var i = 0; i < Vertices.Length; i++) { var vertex = new Vector3(Vertices[i].X, Vertices[i].Y, Vertices[i].Z); worldVertices[i] = Vector3.Transform(vertex, worldTransform); @@ -584,7 +602,7 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren // Create triangle edges (for now, render as wireframe - filled triangles would require proper mesh rendering) var lineVertices = new List(); - for (int i = 0; i < Triangles.Length; i += 3) + for (var i = 0; i < Triangles.Length; i += 3) { if (i + 2 < Triangles.Length) { @@ -607,7 +625,7 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren if (lineVertices.Count > 0) { - renderEngine.AddRenderLines(lineVertices.ToArray()); + renderEngine.AddRenderLines([.. lineVertices]); } } } @@ -616,7 +634,8 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as PolyMeshTrianglesNode; + if (target is not PolyMeshTrianglesNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; typedTarget.Vertices = Vertices; @@ -627,17 +646,13 @@ public override void CopyInto(ISceneNode target) } // Custom node class for BMD Info placeholders that represents recursive BMD file references - public class BmdInfoPlaceholderNode : GroupNode, IDrawableItem + public class BmdInfoPlaceholderNode(string name = "BMD_Info_Placeholder") : GroupNode(name), IDrawableItem { public Color NodeColour { get; set; } = Color.White; public Color SelectedNodeColour { get; set; } = Color.Gray; public float Scale { get; set; } = 0.8f; public string ReferencedBmdPath { get; set; } = string.Empty; - public BmdInfoPlaceholderNode(string name = "BMD_Info_Placeholder") : base(name) - { - } - public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { if (IsVisible) @@ -654,7 +669,8 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as BmdInfoPlaceholderNode; + if (target is not BmdInfoPlaceholderNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; typedTarget.Scale = Scale; @@ -664,17 +680,13 @@ public override void CopyInto(ISceneNode target) } // Custom node class for Prop placeholders that renders a visible cube when RMV2 loading fails - public class PropPlaceholderNode : GroupNode, IDrawableItem + public class PropPlaceholderNode(string name = "Prop_Placeholder") : GroupNode(name), IDrawableItem { public Color NodeColour { get; set; } = Color.Red; public Color SelectedNodeColour { get; set; } = Color.DarkRed; public float Scale { get; set; } = 0.5f; public string FailedModelPath { get; set; } = string.Empty; - public PropPlaceholderNode(string name = "Prop_Placeholder") : base(name) - { - } - public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { if (IsVisible) @@ -691,7 +703,8 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as PropPlaceholderNode; + if (target is not PropPlaceholderNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; typedTarget.Scale = Scale; @@ -701,17 +714,13 @@ public override void CopyInto(ISceneNode target) } // Custom node class for Sound placeholders that renders cubes at coordinates with optional connecting lines - public class SoundPlaceholderNode : GroupNode, IDrawableItem + public class SoundPlaceholderNode(string name = "Sound_Placeholder") : GroupNode(name), IDrawableItem { public Color NodeColour { get; set; } = Color.Lime; public Color SelectedNodeColour { get; set; } = Color.Green; public float Scale { get; set; } = 0.3f; public string SoundType { get; set; } = string.Empty; - public RmvVector3[] CoordList { get; set; } = Array.Empty(); - - public SoundPlaceholderNode(string name = "Sound_Placeholder") : base(name) - { - } + public RmvVector3[] CoordList { get; set; } = []; public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent renderEngine, Matrix parentWorld) { @@ -733,7 +742,7 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren { var lineVertices = new List(); - for (int i = 0; i < CoordList.Length - 1; i++) + for (var i = 0; i < CoordList.Length - 1; i++) { var currentVertex = CoordList[i]; var nextVertex = CoordList[i + 1]; @@ -751,7 +760,7 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren if (lineVertices.Count > 0) { - renderEngine.AddRenderLines(lineVertices.ToArray()); + renderEngine.AddRenderLines([.. lineVertices]); } } } @@ -761,7 +770,8 @@ public void Render(GameWorld.Core.Components.Rendering.RenderEngineComponent ren public override void CopyInto(ISceneNode target) { - var typedTarget = target as SoundPlaceholderNode; + if (target is not SoundPlaceholderNode typedTarget) + return; typedTarget.NodeColour = NodeColour; typedTarget.SelectedNodeColour = SelectedNodeColour; typedTarget.Scale = Scale; diff --git a/Editors/BmdEditor/Services/BmdSceneCreator.cs b/Editors/BmdEditor/Services/BmdSceneCreator.cs index 969a92565..952382716 100644 --- a/Editors/BmdEditor/Services/BmdSceneCreator.cs +++ b/Editors/BmdEditor/Services/BmdSceneCreator.cs @@ -237,6 +237,7 @@ private void LoadOtherComponents(BmdFile bmdFile, GroupNode otherGroup, Observab ("Building_Projectile_Emitters", bmdFile.BuildingProjectileEmitters.Count, group => LoadComponents(bmdFile.BuildingProjectileEmitters, group, "BuildingProjectileEmitter", CreateBuildingProjectileEmitterPlaceholderNode, allElements.OfType().ToList())), ("Terrain_Holes", bmdFile.TerrainHoles.Count, group => LoadComponents(bmdFile.TerrainHoles, group, "TerrainHole", CreateTerrainHoleNode, allElements.OfType().ToList())), ("PolyMeshes", bmdFile.PolyMeshes.Count, group => LoadComponents(bmdFile.PolyMeshes, group, "PolyMesh", CreatePolyMeshNode, allElements.OfType().ToList())), + ("Go_Outlines", bmdFile.GoOutlines.Count, group => LoadComponents(bmdFile.GoOutlines, group, "GoOutline", CreateGoOutlineNode, allElements.OfType().ToList())), ("NonTerrain_Outlines", bmdFile.NonTerrainOutlines.Count, group => LoadComponents(bmdFile.NonTerrainOutlines, group, "NonTerrainOutline", CreateNonTerrainOutlineNode, allElements.OfType().ToList())), ("Battlefield_Buildings", bmdFile.BattlefieldBuildings.Count, group => LoadComponents(bmdFile.BattlefieldBuildings, group, "BattlefieldBuilding", CreateBattlefieldBuildingPlaceholderNode, allElements.OfType().ToList())), ("BMD_References", bmdFile.BmdInfos.Count, group => LoadComponents(bmdFile.BmdInfos, group, "BMD", CreateBmdInfoNode, allElements.OfType().ToList())), @@ -422,6 +423,23 @@ private SceneNode CreateBattlefieldBuildingPlaceholderNode(BattlefieldBuilding b } } + private SceneNode CreateGoOutlineNode(GoOutline outlineInfo, GroupNode goOutlineGroup, int index) + { + var outlineName = $"GoOutline_{index}"; + var outlineNode = goOutlineGroup.AddObject(new GroupNode(outlineName) { IsEditable = false }); + + var placeholderMesh = CreateSpecializedNode(() => new GoOutlineNode("GoOutline_Placeholder") + { + VertexList = outlineInfo.VertexList + }); + if (placeholderMesh != null) + { + outlineNode.AddObject(placeholderMesh); + } + + return outlineNode; + } + private SceneNode CreateNonTerrainOutlineNode(NonTerrainOutline outlineInfo, GroupNode nonTerrainOutlineGroup, int index) { var outlineName = $"NonTerrainOutline_{index}"; @@ -636,6 +654,7 @@ private void LoadReferencedOtherComponents(BmdFile referencedBmd, GroupNode othe ("Referenced_Building_Projectile_Emitters", referencedBmd.BuildingProjectileEmitters.Count, group => LoadReferencedComponents(referencedBmd.BuildingProjectileEmitters, group, "Referenced_BuildingProjectileEmitter", CreateBuildingProjectileEmitterPlaceholderNode, parentIndex)), ("Referenced_Terrain_Holes", referencedBmd.TerrainHoles.Count, group => LoadReferencedComponents(referencedBmd.TerrainHoles, group, "Referenced_TerrainHole", CreateTerrainHoleNode, parentIndex)), ("Referenced_PolyMeshes", referencedBmd.PolyMeshes.Count, group => LoadReferencedComponents(referencedBmd.PolyMeshes, group, "Referenced_PolyMesh", CreatePolyMeshNode, parentIndex)), + ("Referenced_Go_Outlines", referencedBmd.GoOutlines.Count, group => LoadReferencedComponents(referencedBmd.GoOutlines, group, "Referenced_GoOutline", CreateGoOutlineNode, parentIndex)), ("Referenced_NonTerrain_Outlines", referencedBmd.NonTerrainOutlines.Count, group => LoadReferencedComponents(referencedBmd.NonTerrainOutlines, group, "Referenced_NonTerrainOutline", CreateNonTerrainOutlineNode, parentIndex)), ("Referenced_Battlefield_Buildings", referencedBmd.BattlefieldBuildings.Count, group => LoadReferencedComponents(referencedBmd.BattlefieldBuildings, group, "Referenced_BattlefieldBuilding", CreateBattlefieldBuildingPlaceholderNode, parentIndex)) // Note: We intentionally exclude BmdInfos here to prevent infinite recursion diff --git a/Editors/BmdEditor/ViewModels/BmdEditorViewModel.cs b/Editors/BmdEditor/ViewModels/BmdEditorViewModel.cs index d80a0f856..e58b1f88f 100644 --- a/Editors/BmdEditor/ViewModels/BmdEditorViewModel.cs +++ b/Editors/BmdEditor/ViewModels/BmdEditorViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.ObjectModel; using System.IO; -using System.Linq; using System.Windows.Input; using CommunityToolkit.Mvvm.Input; using Shared.Core.Misc; @@ -9,15 +8,13 @@ using Shared.Core.PackFiles.Models; using Shared.Core.ToolCreation; using Shared.Core.Services; -using Shared.Core.ErrorHandling; using Shared.GameFormats.Bmd; using GameWorld.Core.Services; using GameWorld.Core.Rendering.Materials; using GameWorld.Core.Components; using GameWorld.Core.Components.Selection; using GameWorld.Core.SceneNodes; -using Serilog; -using Editors.BmdEditor.Services; // Added missing using directive +using Editors.BmdEditor.Services; namespace Editors.BmdEditor.ViewModels { @@ -43,27 +40,27 @@ public BmdFile? BmdFile } // Collections for different element types - public ObservableCollection AllElements { get; } = new(); - public ObservableCollection BattlefieldBuildings { get; } = new(); - public ObservableCollection BattlefieldBuildingFars { get; } = new(); - public ObservableCollection CaptureLocations { get; } = new(); - public ObservableCollection EFLines { get; } = new(); - public ObservableCollection GoOutlines { get; } = new(); - public ObservableCollection NonTerrainOutlines { get; } = new(); - public ObservableCollection BuildingProjectileEmitters { get; } = new(); - public ObservableCollection ZonesTemplates { get; } = new(); - public ObservableCollection BmdInfos { get; } = new(); - public ObservableCollection Props { get; } = new(); - public ObservableCollection VfxInfos { get; } = new(); - public ObservableCollection PointLights { get; } = new(); - public ObservableCollection SpotLights { get; } = new(); - public ObservableCollection Sounds { get; } = new(); - public ObservableCollection PolyMeshes { get; } = new(); - public ObservableCollection LightProbes { get; } = new(); - public ObservableCollection TerrainHoles { get; } = new(); - public ObservableCollection PlayableAreas { get; } = new(); - public ObservableCollection CscInfos { get; } = new(); - public ObservableCollection Deployments { get; } = new(); + public ObservableCollection AllElements { get; } = []; + public ObservableCollection BattlefieldBuildings { get; } = []; + public ObservableCollection BattlefieldBuildingFars { get; } = []; + public ObservableCollection CaptureLocations { get; } = []; + public ObservableCollection EFLines { get; } = []; + public ObservableCollection GoOutlines { get; } = []; + public ObservableCollection NonTerrainOutlines { get; } = []; + public ObservableCollection BuildingProjectileEmitters { get; } = []; + public ObservableCollection ZonesTemplates { get; } = []; + public ObservableCollection BmdInfos { get; } = []; + public ObservableCollection Props { get; } = []; + public ObservableCollection VfxInfos { get; } = []; + public ObservableCollection PointLights { get; } = []; + public ObservableCollection SpotLights { get; } = []; + public ObservableCollection Sounds { get; } = []; + public ObservableCollection PolyMeshes { get; } = []; + public ObservableCollection LightProbes { get; } = []; + public ObservableCollection TerrainHoles { get; } = []; + public ObservableCollection PlayableAreas { get; } = []; + public ObservableCollection CscInfos { get; } = []; + public ObservableCollection Deployments { get; } = []; // Commands public ICommand RefreshCommand { get; } @@ -87,7 +84,7 @@ public string ComponentDetails } private readonly BmdSceneCreator _bmdSceneCreator; - private readonly SelectionManager _selectionManager; + private readonly SelectionManager? _selectionManager; private readonly BmdElementLoader _bmdElementLoader; public IWpfGame Scene { get; set; } @@ -114,7 +111,7 @@ public BmdEditorViewModel( _eventHub = eventHub; _graphicsResourceCreator = graphicsResourceCreator; _bmdSceneCreator = bmdSceneCreator; - _selectionManager = selectionManager; + _selectionManager = selectionManager!; _bmdElementLoader = bmdElementLoader; Scene = gameWorld; @@ -288,7 +285,7 @@ private void SelectComponentIn3DScene(BmdElementViewModel component) ComponentDetails += $"\n[DEBUG] Found selectable node: {selectableNode.Name}"; // Clear current selection and select the new object - var objectSelection = _selectionManager.GetState(); + var objectSelection = _selectionManager!.GetState(); if (objectSelection != null) { objectSelection.Clear(); @@ -315,7 +312,7 @@ private void SelectComponentIn3DScene(BmdElementViewModel component) } } - private ISelectable? FindFirstSelectableNode(ISceneNode sceneNode) + private static ISelectable? FindFirstSelectableNode(ISceneNode sceneNode) { // Check if the current node is selectable if (sceneNode is ISelectable selectable) @@ -332,7 +329,7 @@ private void SelectComponentIn3DScene(BmdElementViewModel component) return null; } - private string GenerateComponentDetails(BmdElementViewModel component) + private static string GenerateComponentDetails(BmdElementViewModel component) { var details = new System.Text.StringBuilder(); details.AppendLine($"Type: {component.ElementType}"); @@ -383,6 +380,16 @@ private string GenerateComponentDetails(BmdElementViewModel component) details.AppendLine($" Falloff Type: {light.Light.FalloffType}"); details.AppendLine($" Height Mode: {light.Light.HeightMode}"); details.AppendLine($" Light Probe Only: {light.Light.LightProbeOnly}"); + details.AppendLine($" Flags Version: {light.Light.Flags.FlagVersion}"); + details.AppendLine($" Allow In Outfield: {light.Light.Flags.AllowInOutfield}"); + details.AppendLine($" Clamp To Surface: {light.Light.Flags.ClampToSurface}"); + details.AppendLine($" Clamp To Water Surface: {light.Light.Flags.ClampToWaterSurface}"); + details.AppendLine($" Season Spring: {light.Light.Flags.SeasonSpring}"); + details.AppendLine($" Season Summer: {light.Light.Flags.SeasonSummer}"); + details.AppendLine($" Season Autumn: {light.Light.Flags.SeasonAutumn}"); + details.AppendLine($" Season Winter: {light.Light.Flags.SeasonWinter}"); + details.AppendLine($" Visible In Tactical: {light.Light.Flags.VisibleInTactical}"); + details.AppendLine($" Only Visible In Tactical: {light.Light.Flags.OnlyVisibleInTactical}"); break; case SpotLightInfoViewModel spotLight: @@ -393,6 +400,17 @@ private string GenerateComponentDetails(BmdElementViewModel component) details.AppendLine($" Inner Angle: {spotLight.Light.InnerAngleRadians:F2}"); details.AppendLine($" Outer Angle: {spotLight.Light.OuterAngleRadians:F2}"); details.AppendLine($" Color: ({spotLight.Light.IntensityRed:F2}, {spotLight.Light.IntensityGreen:F2}, {spotLight.Light.IntensityBlue:F2})"); + details.AppendLine($" PdlcMask: {spotLight.Light.PdlcMask}"); + details.AppendLine($" Flags Version: {spotLight.Light.Flags.FlagVersion}"); + details.AppendLine($" Allow In Outfield: {spotLight.Light.Flags.AllowInOutfield}"); + details.AppendLine($" Clamp To Surface: {spotLight.Light.Flags.ClampToSurface}"); + details.AppendLine($" Clamp To Water Surface: {spotLight.Light.Flags.ClampToWaterSurface}"); + details.AppendLine($" Season Spring: {spotLight.Light.Flags.SeasonSpring}"); + details.AppendLine($" Season Summer: {spotLight.Light.Flags.SeasonSummer}"); + details.AppendLine($" Season Autumn: {spotLight.Light.Flags.SeasonAutumn}"); + details.AppendLine($" Season Winter: {spotLight.Light.Flags.SeasonWinter}"); + details.AppendLine($" Visible In Tactical: {spotLight.Light.Flags.VisibleInTactical}"); + details.AppendLine($" Only Visible In Tactical: {spotLight.Light.Flags.OnlyVisibleInTactical}"); break; case SoundInfoViewModel sound: @@ -417,7 +435,16 @@ private string GenerateComponentDetails(BmdElementViewModel component) details.AppendLine($" Material: {mesh.Mesh.MaterialString}"); details.AppendLine($" Vertices: {mesh.Mesh.VertexList.Length}"); details.AppendLine($" Triangles: {mesh.Mesh.TriangleList.Length / 3}"); - details.AppendLine($" Visible In Tactical: {mesh.Mesh.VisibleInTactical}"); + details.AppendLine($" Flags Version: {mesh.Mesh.Flags.FlagVersion}"); + details.AppendLine($" Allow In Outfield: {mesh.Mesh.Flags.AllowInOutfield}"); + details.AppendLine($" Clamp To Surface: {mesh.Mesh.Flags.ClampToSurface}"); + details.AppendLine($" Clamp To Water Surface: {mesh.Mesh.Flags.ClampToWaterSurface}"); + details.AppendLine($" Season Spring: {mesh.Mesh.Flags.SeasonSpring}"); + details.AppendLine($" Season Summer: {mesh.Mesh.Flags.SeasonSummer}"); + details.AppendLine($" Season Autumn: {mesh.Mesh.Flags.SeasonAutumn}"); + details.AppendLine($" Season Winter: {mesh.Mesh.Flags.SeasonWinter}"); + details.AppendLine($" Visible In Tactical: {mesh.Mesh.Flags.VisibleInTactical}"); + details.AppendLine($" Only Visible In Tactical: {mesh.Mesh.Flags.OnlyVisibleInTactical}"); break; case LightProbeInfoViewModel probe: @@ -434,6 +461,16 @@ private string GenerateComponentDetails(BmdElementViewModel component) details.AppendLine("Terrain Hole Details:"); details.AppendLine($" Version: {hole.Hole.TerrainHoleVersion}"); details.AppendLine($" Position: ({hole.Hole.FirstVert.X:F2}, {hole.Hole.FirstVert.Y:F2}, {hole.Hole.FirstVert.Z:F2})"); + details.AppendLine($" Flags Version: {hole.Hole.Flags.FlagVersion}"); + details.AppendLine($" Allow In Outfield: {hole.Hole.Flags.AllowInOutfield}"); + details.AppendLine($" Clamp To Surface: {hole.Hole.Flags.ClampToSurface}"); + details.AppendLine($" Clamp To Water Surface: {hole.Hole.Flags.ClampToWaterSurface}"); + details.AppendLine($" Season Spring: {hole.Hole.Flags.SeasonSpring}"); + details.AppendLine($" Season Summer: {hole.Hole.Flags.SeasonSummer}"); + details.AppendLine($" Season Autumn: {hole.Hole.Flags.SeasonAutumn}"); + details.AppendLine($" Season Winter: {hole.Hole.Flags.SeasonWinter}"); + details.AppendLine($" Visible In Tactical: {hole.Hole.Flags.VisibleInTactical}"); + details.AppendLine($" Only Visible In Tactical: {hole.Hole.Flags.OnlyVisibleInTactical}"); break; case CscInfoViewModel csc: @@ -479,7 +516,7 @@ private string GenerateComponentDetails(BmdElementViewModel component) case GoOutlineViewModel goOutline: details.AppendLine("GO Outline Details:"); - details.AppendLine($" Version: {goOutline.GoOutline.Version}"); + details.AppendLine($" Vertices: {goOutline.GoOutline.VertexList.Count}"); break; case NonTerrainOutlineViewModel nonTerrainOutline: @@ -496,7 +533,6 @@ private string GenerateComponentDetails(BmdElementViewModel component) case ZonesTemplateViewModel zonesTemplate: details.AppendLine("Zones Template Details:"); - details.AppendLine($" Version: {zonesTemplate.ZonesTemplate.Version}"); details.AppendLine($" Outline Points: {zonesTemplate.ZonesTemplate.Outline.Count}"); break; @@ -542,7 +578,7 @@ private string GenerateComponentDetails(BmdElementViewModel component) if (boundary.Boundary.PointList.Count > 0) { details.AppendLine(" First few points:"); - for (int i = 0; i < Math.Min(3, boundary.Boundary.PointList.Count); i++) + for (var i = 0; i < Math.Min(3, boundary.Boundary.PointList.Count); i++) { var point = boundary.Boundary.PointList[i]; details.AppendLine($" Point {i + 1}: ({point.X:F2}, {point.Y:F2})"); @@ -660,7 +696,7 @@ private void OnSelectionChanged(SelectionChangedEvent selectionEvent) return null; } - private bool IsNodeOrDescendant(ISceneNode node, ISelectable target) + private static bool IsNodeOrDescendant(ISceneNode node, ISelectable target) { // Check if the node itself is the target if (node == target) @@ -684,246 +720,124 @@ public void Dispose() // Unregister from events _eventHub?.UnRegister(this); + GC.SuppressFinalize(this); } } // Base class for all BMD element view models - public abstract class BmdElementViewModel : NotifyPropertyChangedImpl + public abstract class BmdElementViewModel(string elementType, string displayName, string description = "") : NotifyPropertyChangedImpl { - public string ElementType { get; } - public string DisplayName { get; } - public string Description { get; } - public virtual ObservableCollection Children { get; } = new(); - - protected BmdElementViewModel(string elementType, string displayName, string description = "") - { - ElementType = elementType; - DisplayName = displayName; - Description = description; - } + public string ElementType { get; } = elementType; + public string DisplayName { get; } = displayName; + public string Description { get; } = description; + public virtual ObservableCollection Children { get; } = []; } // View models for specific element types - public class BattlefieldBuildingViewModel : BmdElementViewModel + public class BattlefieldBuildingViewModel(BattlefieldBuilding building) : BmdElementViewModel("Battlefield Building", building.BuildingKey, $"Version: {building.Version}") { - public BattlefieldBuilding Building { get; } - - public BattlefieldBuildingViewModel(BattlefieldBuilding building) - : base("Battlefield Building", building.BuildingKey, $"Version: {building.Version}") - { - Building = building; - } + public BattlefieldBuilding Building { get; } = building; } - public class PropInfoViewModel : BmdElementViewModel + public class PropInfoViewModel(PropInfo prop, string propFilePath) : BmdElementViewModel("Prop", System.IO.Path.GetFileNameWithoutExtension(propFilePath) ?? "Unknown Prop", $"Version: {prop.PropInfoVersion}") { - public PropInfo Prop { get; } - public string PropName { get; } - public string PropFilePath { get; } - - public PropInfoViewModel(PropInfo prop, string propFilePath) - : base("Prop", System.IO.Path.GetFileNameWithoutExtension(propFilePath) ?? "Unknown Prop", $"Version: {prop.PropInfoVersion}") - { - Prop = prop; - PropName = System.IO.Path.GetFileNameWithoutExtension(propFilePath) ?? "Unknown Prop"; - PropFilePath = propFilePath; - } + public PropInfo Prop { get; } = prop; + public string PropName { get; } = System.IO.Path.GetFileNameWithoutExtension(propFilePath) ?? "Unknown Prop"; + public string PropFilePath { get; } = propFilePath; } - public class VfxInfoViewModel : BmdElementViewModel + public class VfxInfoViewModel(VfxInfo vfx) : BmdElementViewModel("VFX", vfx.VfxString, $"Version: {vfx.VfxInfoVersion}") { - public VfxInfo Vfx { get; } - - public VfxInfoViewModel(VfxInfo vfx) - : base("VFX", vfx.VfxString, $"Version: {vfx.VfxInfoVersion}") - { - Vfx = vfx; - } + public VfxInfo Vfx { get; } = vfx; } - public class PointLightInfoViewModel : BmdElementViewModel + public class PointLightInfoViewModel(PointLightInfo light) : BmdElementViewModel("Point Light", $"Point Light at ({light.Position.X:F1}, {light.Position.Y:F1}, {light.Position.Z:F1})", + $"Radius: {light.Radius:F1}, Color: ({light.Red:F1}, {light.Green:F1}, {light.Blue:F1})") { - public PointLightInfo Light { get; } - - public PointLightInfoViewModel(PointLightInfo light) - : base("Point Light", $"Point Light at ({light.Position.X:F1}, {light.Position.Y:F1}, {light.Position.Z:F1})", - $"Radius: {light.Radius:F1}, Color: ({light.Red:F1}, {light.Green:F1}, {light.Blue:F1})") - { - Light = light; - } + public PointLightInfo Light { get; } = light; } - public class SpotLightInfoViewModel : BmdElementViewModel + public class SpotLightInfoViewModel(SpotLightInfo light) : BmdElementViewModel("Spot Light", $"Spot Light at ({light.Position.X:F1}, {light.Position.Y:F1}, {light.Position.Z:F1})", + $"RGB: ({light.IntensityRed:F2},{light.IntensityGreen:F2},{light.IntensityBlue:F2}), Length: {light.Length:F2}") { - public SpotLightInfo Light { get; } - - public SpotLightInfoViewModel(SpotLightInfo light) - : base("Spot Light", $"Spot Light at ({light.Position.X:F1}, {light.Position.Y:F1}, {light.Position.Z:F1})", - $"RGB: ({light.IntensityRed:F2},{light.IntensityGreen:F2},{light.IntensityBlue:F2}), Length: {light.Length:F2}") - { - Light = light; - } + public SpotLightInfo Light { get; } = light; } - public class SoundInfoViewModel : BmdElementViewModel + public class SoundInfoViewModel(SoundInfo sound) : BmdElementViewModel("Sound", sound.SoundString, $"Type: {sound.TypeString}, Version: {sound.Version}") { - public SoundInfo Sound { get; } - - public SoundInfoViewModel(SoundInfo sound) - : base("Sound", sound.SoundString, $"Type: {sound.TypeString}, Version: {sound.Version}") - { - Sound = sound; - } + public SoundInfo Sound { get; } = sound; } - public class PolyMeshInfoViewModel : BmdElementViewModel + public class PolyMeshInfoViewModel(PolyMeshInfo mesh) : BmdElementViewModel("PolyMesh", mesh.MaterialString, $"Vertices: {mesh.VertexList.Length}, Triangles: {mesh.TriangleList.Length / 3}") { - public PolyMeshInfo Mesh { get; } - - public PolyMeshInfoViewModel(PolyMeshInfo mesh) - : base("PolyMesh", mesh.MaterialString, $"Vertices: {mesh.VertexList.Length}, Triangles: {mesh.TriangleList.Length / 3}") - { - Mesh = mesh; - } + public PolyMeshInfo Mesh { get; } = mesh; } - public class LightProbeInfoViewModel : BmdElementViewModel + public class LightProbeInfoViewModel(LightProbeInfo probe) : BmdElementViewModel("Light Probe", $"Probe_{probe.Position.X:F2}_{probe.Position.Y:F2}_{probe.Position.Z:F2}", + $"Inner: {probe.InnerRadius:F2}, Outer: {probe.OuterRadius:F2}, Primary: {probe.Primary}") { - public LightProbeInfo Probe { get; } - - public LightProbeInfoViewModel(LightProbeInfo probe) - : base("Light Probe", $"Probe_{probe.Position.X:F2}_{probe.Position.Y:F2}_{probe.Position.Z:F2}", - $"Inner: {probe.InnerRadius:F2}, Outer: {probe.OuterRadius:F2}, Primary: {probe.Primary}") - { - Probe = probe; - } + public LightProbeInfo Probe { get; } = probe; } - public class TerrainHoleInfoViewModel : BmdElementViewModel + public class TerrainHoleInfoViewModel(TerrainHoleTriangleInfo hole) : BmdElementViewModel("Terrain Hole", $"Hole at ({hole.FirstVert.X:F1}, {hole.FirstVert.Y:F1}, {hole.FirstVert.Z:F1})", + $"Version: {hole.TerrainHoleVersion}") { - public TerrainHoleTriangleInfo Hole { get; } - - public TerrainHoleInfoViewModel(TerrainHoleTriangleInfo hole) - : base("Terrain Hole", $"Hole at ({hole.FirstVert.X:F1}, {hole.FirstVert.Y:F1}, {hole.FirstVert.Z:F1})", - $"Version: {hole.TerrainHoleVersion}") - { - Hole = hole; - } + public TerrainHoleTriangleInfo Hole { get; } = hole; } - public class CscInfoViewModel : BmdElementViewModel + public class CscInfoViewModel(CscInfo csc) : BmdElementViewModel("CSC Info", csc.SceneFile, $"Version: {csc.Version}") { - public CscInfo Csc { get; } - - public CscInfoViewModel(CscInfo csc) - : base("CSC Info", csc.SceneFile, $"Version: {csc.Version}") - { - Csc = csc; - } + public CscInfo Csc { get; } = csc; } - public class BattlefieldBuildingFarViewModel : BmdElementViewModel + public class BattlefieldBuildingFarViewModel(BattlefieldBuildingFar buildingFar) : BmdElementViewModel("Battlefield Building Far", "", $"Version: {buildingFar.Version}") { - public BattlefieldBuildingFar BuildingFar { get; } - - public BattlefieldBuildingFarViewModel(BattlefieldBuildingFar buildingFar) - : base("Battlefield Building Far", "", $"Version: {buildingFar.Version}") - { - BuildingFar = buildingFar; - } + public BattlefieldBuildingFar BuildingFar { get; } = buildingFar; } - public class CaptureLocationViewModel : BmdElementViewModel + public class CaptureLocationViewModel(CaptureLocation captureLocation) : BmdElementViewModel("Capture Location", "", $"Version: {captureLocation.Version}") { - public CaptureLocation CaptureLocation { get; } - - public CaptureLocationViewModel(CaptureLocation captureLocation) - : base("Capture Location", "", $"Version: {captureLocation.Version}") - { - CaptureLocation = captureLocation; - } + public CaptureLocation CaptureLocation { get; } = captureLocation; } - public class EFLineViewModel : BmdElementViewModel + public class EFLineViewModel(EFLine efLine) : BmdElementViewModel("EF Line", "", $"Version: {efLine.Version}") { - public EFLine EFLine { get; } - - public EFLineViewModel(EFLine efLine) - : base("EF Line", "", $"Version: {efLine.Version}") - { - EFLine = efLine; - } + public EFLine EFLine { get; } = efLine; } - public class GoOutlineViewModel : BmdElementViewModel + public class GoOutlineViewModel(GoOutline goOutline) : BmdElementViewModel("Go Outline", "", $"Vertices: {goOutline.VertexList?.Count ?? 0}") { - public GoOutline GoOutline { get; } - - public GoOutlineViewModel(GoOutline goOutline) - : base("GO Outline", "", $"Version: {goOutline.Version}") - { - GoOutline = goOutline; - } + public GoOutline GoOutline { get; } = goOutline; } - public class NonTerrainOutlineViewModel : BmdElementViewModel + public class NonTerrainOutlineViewModel(NonTerrainOutline nonTerrainOutline) : BmdElementViewModel("Non-Terrain Outline", "", $"Vertices: {nonTerrainOutline.VertexList?.Count ?? 0}") { - public NonTerrainOutline NonTerrainOutline { get; } - - public NonTerrainOutlineViewModel(NonTerrainOutline nonTerrainOutline) - : base("Non-Terrain Outline", "", $"Vertices: {nonTerrainOutline.VertexList?.Count ?? 0}") - { - NonTerrainOutline = nonTerrainOutline; - } + public NonTerrainOutline NonTerrainOutline { get; } = nonTerrainOutline; } - public class BuildingProjectileEmitterViewModel : BmdElementViewModel + public class BuildingProjectileEmitterViewModel(BuildingProjectileEmitter buildingProjectileEmitter) : BmdElementViewModel("Building Projectile Emitter", buildingProjectileEmitter.SpecializedBuildingProjectileEmitterKey, $"Version: {buildingProjectileEmitter.BuildingProjectileEmitterVersion}") { - public BuildingProjectileEmitter BuildingProjectileEmitter { get; } - - public BuildingProjectileEmitterViewModel(BuildingProjectileEmitter buildingProjectileEmitter) - : base("Building Projectile Emitter", buildingProjectileEmitter.SpecializedBuildingProjectileEmitterKey, $"Version: {buildingProjectileEmitter.BuildingProjectileEmitterVersion}") - { - BuildingProjectileEmitter = buildingProjectileEmitter; - } + public BuildingProjectileEmitter BuildingProjectileEmitter { get; } = buildingProjectileEmitter; } - public class ZonesTemplateViewModel : BmdElementViewModel + public class ZonesTemplateViewModel(ZonesTemplate zonesTemplate) : BmdElementViewModel("Zones Template", "", "") { - public ZonesTemplate ZonesTemplate { get; } - - public ZonesTemplateViewModel(ZonesTemplate zonesTemplate) - : base("Zones Template", "", $"Version: {zonesTemplate.Version}") - { - ZonesTemplate = zonesTemplate; - } + public ZonesTemplate ZonesTemplate { get; } = zonesTemplate; } - public class PlayableAreaViewModel : BmdElementViewModel + public class PlayableAreaViewModel(PlayableArea playableArea) : BmdElementViewModel("Playable Area", "", $"Version: {playableArea.PlayableAreaVersion}") { - public PlayableArea PlayableArea { get; } - - public PlayableAreaViewModel(PlayableArea playableArea) - : base("Playable Area", "", $"Version: {playableArea.PlayableAreaVersion}") - { - PlayableArea = playableArea; - } + public PlayableArea PlayableArea { get; } = playableArea; } - public class BmdInfoViewModel : BmdElementViewModel + public class BmdInfoViewModel(BmdInfo bmd) : BmdElementViewModel("BMD", bmd.BmdString, $"Version: {bmd.Version}, Region: {bmd.RegionString}") { - public BmdInfo Bmd { get; } - public ObservableCollection ChildElements { get; } = new(); + public BmdInfo Bmd { get; } = bmd; + public ObservableCollection ChildElements { get; } = []; public bool IsExpanded { get; set; } = false; public bool HasChildren { get; set; } = false; public override ObservableCollection Children => ChildElements; - - public BmdInfoViewModel(BmdInfo bmd) - : base("BMD", bmd.BmdString, $"Version: {bmd.Version}, Region: {bmd.RegionString}") - { - Bmd = bmd; - } } public class DeploymentZoneViewModel : BmdElementViewModel @@ -960,15 +874,9 @@ public DeploymentZoneRegionViewModel(DeploymentZoneRegion deploymentZoneRegion) } } - public class BoundaryViewModel : BmdElementViewModel + public class BoundaryViewModel(Boundary boundary) : BmdElementViewModel("Boundary", boundary.BoundaryType, $"Version: {boundary.Version}, Points: {boundary.PointList.Count}") { - public Boundary Boundary { get; } - - public BoundaryViewModel(Boundary boundary) - : base("Boundary", boundary.BoundaryType, $"Version: {boundary.Version}, Points: {boundary.PointList.Count}") - { - Boundary = boundary; - } + public Boundary Boundary { get; } = boundary; } public class DeploymentViewModel : BmdElementViewModel @@ -981,7 +889,7 @@ public DeploymentViewModel(Deployment deployment) Deployment = deployment; // Add child zones - for (int i = 0; i < deployment.DeploymentZones.Count; i++) + for (var i = 0; i < deployment.DeploymentZones.Count; i++) { Children.Add(new DeploymentZoneViewModel(deployment.DeploymentZones[i], i)); } diff --git a/Editors/BmdEditor/ViewModels/BmdSceneViewModel.cs b/Editors/BmdEditor/ViewModels/BmdSceneViewModel.cs index 21a27ce36..ef9f39d78 100644 --- a/Editors/BmdEditor/ViewModels/BmdSceneViewModel.cs +++ b/Editors/BmdEditor/ViewModels/BmdSceneViewModel.cs @@ -1,20 +1,14 @@ using System; -using System.Collections.ObjectModel; -using System.Linq; using System.Windows.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Microsoft.Xna.Framework; using Shared.Core.PackFiles; using Shared.Core.PackFiles.Models; using Shared.Core.ToolCreation; using Shared.Core.Services; using Shared.GameFormats.Bmd; -using Shared.GameFormats.RigidModel; -using GameWorld.Core.Components; using GameWorld.Core.Rendering.Materials; using GameWorld.Core.Services; -using GameWorld.Core.SceneNodes; using Editors.BmdEditor.Services; using Serilog; @@ -150,6 +144,7 @@ public void Dispose() // Cleanup resources Scene3D = null; _bmdFile = null; + GC.SuppressFinalize(this); } } } diff --git a/Editors/Reports/Bmd/BmdReportGenerator.cs b/Editors/Reports/Bmd/BmdReportGenerator.cs index c433d466e..1926c2e44 100644 --- a/Editors/Reports/Bmd/BmdReportGenerator.cs +++ b/Editors/Reports/Bmd/BmdReportGenerator.cs @@ -17,18 +17,12 @@ public class BmdReportCommand(BmdReportGenerator generator) : IUiCommand public void Execute() => generator.Create(); } - public class BmdReportGenerator + public class BmdReportGenerator(IPackFileService pfs, ApplicationSettingsService applicationSettingsService) { - private readonly IPackFileService _pfs; - private readonly ApplicationSettingsService _applicationSettingsService; + private readonly IPackFileService _pfs = pfs; + private readonly ApplicationSettingsService _applicationSettingsService = applicationSettingsService; - public BmdReportGenerator(IPackFileService pfs, ApplicationSettingsService applicationSettingsService) - { - _pfs = pfs; - _applicationSettingsService = applicationSettingsService; - } - - public void Create(string outputDir = null) + public void Create(string? outputDir = null) { var gameName = GameInformationDatabase.GetGameById(_applicationSettingsService.CurrentSettings.CurrentGame).DisplayName; var timeStamp = DateTime.Now.ToString("yyyyMMddHHmmssfff"); @@ -127,10 +121,34 @@ public void Create(string outputDir = null) { dynamic buildingRecord = new ExpandoObject(); buildingRecord.Path = path; + buildingRecord.Version = building.Version; + buildingRecord.BuildingId = building.BuildingId; + buildingRecord.ParentId = building.ParentId; + buildingRecord.BuildingKey = building.BuildingKey; + buildingRecord.PositionType = building.PositionType; buildingRecord.PositionX = building.Transform.M41; buildingRecord.PositionY = building.Transform.M42; buildingRecord.PositionZ = building.Transform.M43; - buildingRecord.BuildingKey = building.BuildingKey; + buildingRecord.PropertiesVersion = building.PropertiesVersion; + buildingRecord.PropertiesBuildingId = building.PropertiesBuildingId; + buildingRecord.StartingDamageUnary = building.StartingDamageUnary; + buildingRecord.OnFire = building.OnFire; + buildingRecord.StartDisabled = building.StartDisabled; + buildingRecord.WeakPoint = building.WeakPoint; + buildingRecord.AiBreachable = building.AiBreachable; + buildingRecord.Indestructible = building.Indestructible; + buildingRecord.Dockable = building.Dockable; + buildingRecord.Toggleable = building.Toggleable; + buildingRecord.Lite = building.Lite; + buildingRecord.CastShadows = building.CastShadows; + buildingRecord.KeyBuilding = building.KeyBuilding; + buildingRecord.KeyBuildingUseFort = building.KeyBuildingUseFort; + buildingRecord.IsPropInOutfield = building.IsPropInOutfield; + buildingRecord.SettlementLevelConfigurable = building.SettlementLevelConfigurable; + buildingRecord.HideTooltip = building.HideTooltip; + buildingRecord.IncludeInFog = building.IncludeInFog; + buildingRecord.HeightMode = building.HeightMode; + buildingRecord.Uid = building.Uid; battlefieldBuildingRecords.Add(buildingRecord); } @@ -168,7 +186,7 @@ public void Create(string outputDir = null) { dynamic goOutlineRecord = new ExpandoObject(); goOutlineRecord.Path = path; - goOutlineRecord.Version = goOutline.Version; + goOutlineRecord.VertexCount = goOutline.VertexList.Count; goOutlineRecords.Add(goOutlineRecord); } @@ -276,17 +294,28 @@ public void Create(string outputDir = null) { dynamic vfxRecord = new ExpandoObject(); vfxRecord.Path = path; + vfxRecord.Version = vfx.VfxInfoVersion; vfxRecord.PositionX = vfx.Transform.M41; vfxRecord.PositionY = vfx.Transform.M42; vfxRecord.PositionZ = vfx.Transform.M43; vfxRecord.VfxString = vfx.VfxString; vfxRecord.EmissionRate = vfx.EmissionRate; vfxRecord.InstanceName = vfx.InstanceName; + vfxRecord.HeightMode = vfx.HeightMode; + vfxRecord.Autoplay = vfx.Autoplay; + vfxRecord.VisibleInShroud = vfx.VisibleInShroud; + vfxRecord.ParentId = vfx.ParentId; + vfxRecord.VisibleInShroudOnly = vfx.VisibleInShroudOnly; + vfxRecord.FlagsVersion = vfx.Flags.FlagVersion; + vfxRecord.AllowInOutfield = vfx.Flags.AllowInOutfield; vfxRecord.ClampToSurface = vfx.Flags.ClampToSurface; + vfxRecord.ClampToWaterSurface = vfx.Flags.ClampToWaterSurface; vfxRecord.SeasonSpring = vfx.Flags.SeasonSpring; vfxRecord.SeasonSummer = vfx.Flags.SeasonSummer; vfxRecord.SeasonAutumn = vfx.Flags.SeasonAutumn; vfxRecord.SeasonWinter = vfx.Flags.SeasonWinter; + vfxRecord.VisibleInTactical = vfx.Flags.VisibleInTactical; + vfxRecord.OnlyVisibleInTactical = vfx.Flags.OnlyVisibleInTactical; vfxRecords.Add(vfxRecord); } @@ -321,6 +350,16 @@ public void Create(string outputDir = null) terrainHoleRecord.PositionY = terrainHole.FirstVert.Y; terrainHoleRecord.PositionZ = terrainHole.FirstVert.Z; terrainHoleRecord.HeightMode = terrainHole.HeightMode; + terrainHoleRecord.FlagsVersion = terrainHole.Flags.FlagVersion; + terrainHoleRecord.AllowInOutfield = terrainHole.Flags.AllowInOutfield; + terrainHoleRecord.ClampToSurface = terrainHole.Flags.ClampToSurface; + terrainHoleRecord.ClampToWaterSurface = terrainHole.Flags.ClampToWaterSurface; + terrainHoleRecord.SeasonSpring = terrainHole.Flags.SeasonSpring; + terrainHoleRecord.SeasonSummer = terrainHole.Flags.SeasonSummer; + terrainHoleRecord.SeasonAutumn = terrainHole.Flags.SeasonAutumn; + terrainHoleRecord.SeasonWinter = terrainHole.Flags.SeasonWinter; + terrainHoleRecord.VisibleInTactical = terrainHole.Flags.VisibleInTactical; + terrainHoleRecord.OnlyVisibleInTactical = terrainHole.Flags.OnlyVisibleInTactical; terrainHoleRecords.Add(terrainHoleRecord); } @@ -337,6 +376,16 @@ public void Create(string outputDir = null) pointLightRecord.LFRelative = pointLight.LFRelative; pointLightRecord.LightProbeOnly = pointLight.LightProbeOnly; pointLightRecord.PdlcMask = pointLight.PdlcMask; + pointLightRecord.FlagsVersion = pointLight.Flags.FlagVersion; + pointLightRecord.AllowInOutfield = pointLight.Flags.AllowInOutfield; + pointLightRecord.ClampToSurface = pointLight.Flags.ClampToSurface; + pointLightRecord.ClampToWaterSurface = pointLight.Flags.ClampToWaterSurface; + pointLightRecord.SeasonSpring = pointLight.Flags.SeasonSpring; + pointLightRecord.SeasonSummer = pointLight.Flags.SeasonSummer; + pointLightRecord.SeasonAutumn = pointLight.Flags.SeasonAutumn; + pointLightRecord.SeasonWinter = pointLight.Flags.SeasonWinter; + pointLightRecord.VisibleInTactical = pointLight.Flags.VisibleInTactical; + pointLightRecord.OnlyVisibleInTactical = pointLight.Flags.OnlyVisibleInTactical; pointLightRecords.Add(pointLightRecord); } @@ -380,6 +429,16 @@ public void Create(string outputDir = null) meshRecord.PositionY = mesh.Transform.M42; meshRecord.PositionZ = mesh.Transform.M43; meshRecord.MaterialString = mesh.MaterialString; + meshRecord.FlagsVersion = mesh.Flags.FlagVersion; + meshRecord.AllowInOutfield = mesh.Flags.AllowInOutfield; + meshRecord.ClampToSurface = mesh.Flags.ClampToSurface; + meshRecord.ClampToWaterSurface = mesh.Flags.ClampToWaterSurface; + meshRecord.SeasonSpring = mesh.Flags.SeasonSpring; + meshRecord.SeasonSummer = mesh.Flags.SeasonSummer; + meshRecord.SeasonAutumn = mesh.Flags.SeasonAutumn; + meshRecord.SeasonWinter = mesh.Flags.SeasonWinter; + meshRecord.VisibleInTactical = mesh.Flags.VisibleInTactical; + meshRecord.OnlyVisibleInTactical = mesh.Flags.OnlyVisibleInTactical; meshRecords.Add(meshRecord); } @@ -401,6 +460,17 @@ public void Create(string outputDir = null) spotLightRecord.PositionY = spotLight.Position.Y; spotLightRecord.PositionZ = spotLight.Position.Z; spotLightRecord.Color = $"{spotLight.IntensityRed},{spotLight.IntensityGreen},{spotLight.IntensityBlue}"; + spotLightRecord.PdlcMask = spotLight.PdlcMask; + spotLightRecord.FlagsVersion = spotLight.Flags.FlagVersion; + spotLightRecord.AllowInOutfield = spotLight.Flags.AllowInOutfield; + spotLightRecord.ClampToSurface = spotLight.Flags.ClampToSurface; + spotLightRecord.ClampToWaterSurface = spotLight.Flags.ClampToWaterSurface; + spotLightRecord.SeasonSpring = spotLight.Flags.SeasonSpring; + spotLightRecord.SeasonSummer = spotLight.Flags.SeasonSummer; + spotLightRecord.SeasonAutumn = spotLight.Flags.SeasonAutumn; + spotLightRecord.SeasonWinter = spotLight.Flags.SeasonWinter; + spotLightRecord.VisibleInTactical = spotLight.Flags.VisibleInTactical; + spotLightRecord.OnlyVisibleInTactical = spotLight.Flags.OnlyVisibleInTactical; spotLightRecords.Add(spotLightRecord); } @@ -428,7 +498,6 @@ public void Create(string outputDir = null) soundRecord.OuterBoundingBoxMaxX = sound.OuterCubeBoundingBox.Max.X; soundRecord.OuterBoundingBoxMaxY = sound.OuterCubeBoundingBox.Max.Y; soundRecord.OuterBoundingBoxMaxZ = sound.OuterCubeBoundingBox.Max.Z; - soundRecord.RiverNodesLength = sound.RiverNodesLength; soundRecord.ClampToSurface = sound.ClampToSurface; soundRecord.HeightMode = sound.HeightMode; soundRecord.CampaignTypeMask = sound.CampaignTypeMask; @@ -612,7 +681,7 @@ public void Create(string outputDir = null) } } - void Write(List dataRecords, string filePath) + static void Write(List dataRecords, string filePath) { using var writer = new StringWriter(); using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); @@ -620,7 +689,7 @@ void Write(List dataRecords, string filePath) File.WriteAllText(filePath, writer.ToString()); } - private ValidationResult ValidateParsedData(BmdFile parsedFile, string filePath) + private static ValidationResult ValidateParsedData(BmdFile parsedFile, string filePath) { var errors = new List(); @@ -719,12 +788,12 @@ private ValidationResult ValidateParsedData(BmdFile parsedFile, string filePath) return new ValidationResult { - IsValid = !errors.Any(), + IsValid = errors.Count == 0, ErrorMessage = string.Join("; ", errors) }; } - private void ValidateStringField(IEnumerable strings, string fieldName, List errors) + private static void ValidateStringField(IEnumerable strings, string fieldName, List errors) { foreach (var str in strings) { @@ -733,7 +802,7 @@ private void ValidateStringField(IEnumerable strings, string fieldName, // Check for Unicode garbage characters if (ContainsUnicodeGarbage(str)) { - errors.Add($"Unicode garbage detected in {fieldName}: '{str.Substring(0, Math.Min(50, str.Length))}...'"); + errors.Add($"Unicode garbage detected in {fieldName}: '{str[..Math.Min(50, str.Length)]}...'"); } // Check for unusually long strings (likely corrupted) @@ -744,7 +813,7 @@ private void ValidateStringField(IEnumerable strings, string fieldName, } } - private bool ContainsUnicodeGarbage(string str) + private static bool ContainsUnicodeGarbage(string str) { // Check for common Unicode garbage patterns var garbagePatterns = new[] diff --git a/Shared/GameFiles/Bmd/BmdFile.cs b/Shared/GameFiles/Bmd/BmdFile.cs index 2f7ebc457..4393a03c9 100644 --- a/Shared/GameFiles/Bmd/BmdFile.cs +++ b/Shared/GameFiles/Bmd/BmdFile.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using Microsoft.Xna.Framework; using Shared.GameFormats.RigidModel.Transforms; @@ -8,41 +6,41 @@ namespace Shared.GameFormats.Bmd public class BmdFile { public FastBinHeader Header { get; set; } = new FastBinHeader(); - public List BattlefieldBuildings { get; set; } = new(); - public List BattlefieldBuildingFars { get; set; } = new(); - public List CaptureLocations { get; set; } = new(); - public List EFLines { get; set; } = new(); - public List GoOutlines { get; set; } = new(); - public List NonTerrainOutlines { get; set; } = new(); - public List ZonesTemplates { get; set; } = new(); - public List BmdInfos { get; set; } = new(); - public List BmdOutlines { get; set; } = new(); - public List TerrainOutlines { get; set; } = new(); - public List LiteBuildingOutlines { get; set; } = new(); - public List CameraZones { get; set; } = new(); - public List CivilianDeployments { get; set; } = new(); - public List CivilianShelters { get; set; } = new(); - public List Props { get; set; } = new(); - public List PropInfos { get; set; } = new(); - public List VfxInfos { get; set; } = new(); + public List BattlefieldBuildings { get; set; } = []; + public List BattlefieldBuildingFars { get; set; } = []; + public List CaptureLocations { get; set; } = []; + public List EFLines { get; set; } = []; + public List GoOutlines { get; set; } = []; + public List NonTerrainOutlines { get; set; } = []; + public List ZonesTemplates { get; set; } = []; + public List BmdInfos { get; set; } = []; + public List BmdOutlines { get; set; } = []; + public List TerrainOutlines { get; set; } = []; + public List LiteBuildingOutlines { get; set; } = []; + public List CameraZones { get; set; } = []; + public List CivilianDeployments { get; set; } = []; + public List CivilianShelters { get; set; } = []; + public List Props { get; set; } = []; + public List PropInfos { get; set; } = []; + public List VfxInfos { get; set; } = []; public AiHints AiHints { get; set; } = new(); - public List LightProbes { get; set; } = new(); - public List TerrainHoles { get; set; } = new(); - public List PointLights { get; set; } = new(); - public List BuildingProjectileEmitters { get; set; } = new(); + public List LightProbes { get; set; } = []; + public List TerrainHoles { get; set; } = []; + public List PointLights { get; set; } = []; + public List BuildingProjectileEmitters { get; set; } = []; public PlayableArea PlayableArea { get; set; } = new(); - public List PolyMeshes { get; set; } = new(); - public List TerrainStencilBlendTriangles { get; set; } = new(); - public List SpotLights { get; set; } = new(); - public List Sounds { get; set; } = new(); - public List CscInfos { get; set; } = new(); - public List Deployments { get; set; } = new(); - public List BmdCachedAreas { get; set; } = new(); - public List ToggleableBuildingSlots { get; set; } = new(); - public List TerraindDecals { get; set; } = new(); - public List TreeListReferences { get; set; } = new(); - public List GrassListReferences { get; set; } = new(); - public List WaterOutlines { get; set; } = new(); + public List PolyMeshes { get; set; } = []; + public List TerrainStencilBlendTriangles { get; set; } = []; + public List SpotLights { get; set; } = []; + public List Sounds { get; set; } = []; + public List CscInfos { get; set; } = []; + public List Deployments { get; set; } = []; + public List BmdCachedAreas { get; set; } = []; + public List ToggleableBuildingSlots { get; set; } = []; + public List TerraindDecals { get; set; } = []; + public List TreeListReferences { get; set; } = []; + public List GrassListReferences { get; set; } = []; + public List WaterOutlines { get; set; } = []; //Pharaoh Exclusive classes (for Pharaoh's version of the version 25 BMD format) public CameraZoneNew CameraZoneNew { get; set; } = new(); @@ -161,7 +159,7 @@ public class CaptureLocation public int Something5 { get; set; } public string Str { get; set; } = string.Empty; public string Str2 { get; set; } = string.Empty; - public float[] Coords { get; set; } = Array.Empty(); + public float[] Coords { get; set; } = []; public string Str3 { get; set; } = string.Empty; public float Something6 { get; set; } public float Something7 { get; set; } @@ -179,32 +177,29 @@ public class EFLine public class GoOutline { - //TODO: Not properly implemented - public ushort Version { get; set; } + public List VertexList { get; set; } = []; } public class NonTerrainOutline { - public List VertexList { get; set; } = new(); + public List VertexList { get; set; } = []; } public class ZonesTemplate { - public ushort Version { get; set; } - public List Outline { get; set; } = new(); + public List Outline { get; set; } = []; public string ZoneName { get; set; } = string.Empty; public string EntityFormationTemplateName { get; set; } = string.Empty; public uint LinesLength { get; set; } - public byte[] LinesData { get; set; } = new byte[0]; // Raw data since Lines structure is unknown - public float[] TransformMatrix { get; set; } = new float[16]; // 4x4 transform matrix - } + public byte[] LinesData { get; set; } = []; // Raw data since Lines structure is unknown + public Matrix Transform { get; set; } = Matrix.Identity;} //4x4 public class BmdInfo { public ushort Version { get; set; } public string BmdString { get; set; } = string.Empty; public Matrix Transform { get; set; } = Matrix.Identity; - public byte[] SeasonsMaybe { get; set; } = new byte[4]; //this has to correspond to + public uint PropertyOverrides { get; set; } //this has to correspond to public CultureMask CultureMask { get; set; } //"campaign_type_mask"? public string RegionString { get; set; } = string.Empty; public string HeightMode { get; set; } = string.Empty; @@ -304,14 +299,17 @@ public class VfxInfo public bool VisibleInShroud { get; set; } public int ParentId { get; set; } public bool VisibleInShroudOnly { get; set; } + + // Early version 4 specific fields + public byte[] EarlyVersionUnknownBytes { get; set; } = new byte[12]; } public class AiHints { - public List Separators { get; set; } = new(); - public List DirectedPoints { get; set; } = new(); - public List PolyLines { get; set; } = new(); - public List PolyLinesList { get; set; } = new(); + public List Separators { get; set; } = []; + public List DirectedPoints { get; set; } = []; + public List PolyLines { get; set; } = []; + public List PolyLinesList { get; set; } = []; } public class Separator @@ -334,7 +332,7 @@ public class HintPolyLine public bool OnlyVanguard { get; set; } public bool OnlyDeployWhenClear { get; set; } public bool SpawnVfx { get; set; } - public List Points { get; set; } = new(); + public List Points { get; set; } = []; } public class HintPolyLineList @@ -342,12 +340,12 @@ public class HintPolyLineList public ushort Version { get; set; } public string Type { get; set; } = string.Empty; public uint District { get; set; } - public List PolygonList { get; set; } = new(); + public List PolygonList { get; set; } = []; } public class Polygon { - public List Points { get; set; } = new(); + public List Points { get; set; } = []; } public class LightProbeInfo @@ -368,7 +366,7 @@ public class TerrainHoleTriangleInfo public RmvVector3 SecondVert { get; set; } public RmvVector3 ThirdVert { get; set; } public string HeightMode { get; set; } = string.Empty; - public byte[] Booleans { get; set; } = new byte[10]; + public BmdComponentFlags Flags { get; set; } = new(); } public class PointLightInfo @@ -390,14 +388,14 @@ public class PointLightInfo public string HeightMode { get; set; } = string.Empty; public bool LightProbeOnly { get; set; } public ulong PdlcMask { get; set; } - public byte[] MoreData2 { get; set; } = new byte[10]; + public BmdComponentFlags Flags { get; set; } = new(); } public class BuildingProjectileEmitter { public ushort BuildingProjectileEmitterVersion { get; set; } public RmvVector3 Location { get; set; } - public float[] Rotation { get; set; } = new float[3]; + public float[] Rotation { get; set; } = [0, 0, 0]; public uint BuildingIndex { get; set; } public string HeightMode { get; set; } = string.Empty; public string SpecializedBuildingProjectileEmitterKey { get; set; } = string.Empty; @@ -407,7 +405,7 @@ public class PlayableArea { public ushort PlayableAreaVersion { get; set; } public bool HasBeenSet { get; set; } - public float[] BoundingBox { get; set; } = new float[4]; + public float[] BoundingBox { get; set; } = [0, 0, 0, 0]; public ushort FlagVersion { get; set; } public bool Flag1 { get; set; } public bool Flag2 { get; set; } @@ -418,13 +416,11 @@ public class PlayableArea public class PolyMeshInfo { public ushort PolyMeshVersion { get; set; } - public RmvVector3[] VertexList { get; set; } = Array.Empty(); - public ushort[] TriangleList { get; set; } = Array.Empty(); + public RmvVector3[] VertexList { get; set; } = []; + public ushort[] TriangleList { get; set; } = []; public string MaterialString { get; set; } = string.Empty; public string HeightMode { get; set; } = string.Empty; - public byte[] MoreData { get; set; } = new byte[8]; - public bool VisibleInTactical { get; set; } - public bool OnlyVisibleInTactical { get; set; } + public BmdComponentFlags Flags { get; set; } = new(); public Matrix Transform { get; set; } = Matrix.Identity; public byte[] Booleans { get; set; } = new byte[4]; public bool VisibleInShroud { get; set; } @@ -455,7 +451,16 @@ public class SpotLightInfo public string Gobo { get; set; } = string.Empty; public bool Volumetric { get; set; } public string HeightMode { get; set; } = string.Empty; - public byte[] MoreData { get; set; } = new byte[18]; + public ulong PdlcMask { get; set; } + public BmdComponentFlags Flags { get; set; } = new(); + } + + public class RiverNode + { + public ushort Version { get; set; } + public RmvVector3 Position { get; set; } + public float Something1 { get; set; } + public float Something2 { get; set; } } public class SoundInfo @@ -463,12 +468,12 @@ public class SoundInfo public ushort Version { get; set; } public string SoundString { get; set; } = string.Empty; public string TypeString { get; set; } = string.Empty; - public RmvVector3[] CoordList { get; set; } = Array.Empty(); + public RmvVector3[] CoordList { get; set; } = []; public float InnerRadius { get; set; } public float OuterRadius { get; set; } public (RmvVector3 Min, RmvVector3 Max) InnerCubeBoundingBox { get; set; } public (RmvVector3 Min, RmvVector3 Max) OuterCubeBoundingBox { get; set; } - public uint RiverNodesLength { get; set; } + public RiverNode[] RiverNodeList { get; set; } = []; public byte ClampToSurface { get; set; } public string HeightMode { get; set; } = string.Empty; public ulong CampaignTypeMask { get; set; } @@ -501,19 +506,19 @@ public class Deployment { public ushort Version { get; set; } public string Category { get; set; } = string.Empty; - public List DeploymentZones { get; set; } = new(); + public List DeploymentZones { get; set; } = []; } public class DeploymentZone { public ushort Version { get; set; } - public List DeploymentZoneRegions { get; set; } = new(); + public List DeploymentZoneRegions { get; set; } = []; } public class DeploymentZoneRegion { public ushort Version { get; set; } - public List Boundaries { get; set; } = new(); + public List Boundaries { get; set; } = []; public float Orientation { get; set; } public byte SnapFacing { get; set; } public uint Id { get; set; } @@ -523,7 +528,7 @@ public class Boundary { public ushort Version { get; set; } public string BoundaryType { get; set; } = string.Empty; - public List PointList { get; set; } = new(); + public List PointList { get; set; } = []; } public class BmdCachedArea diff --git a/Shared/GameFiles/Bmd/BmdParser.cs b/Shared/GameFiles/Bmd/BmdParser.cs index 7b0eb7b91..30bc69e29 100644 --- a/Shared/GameFiles/Bmd/BmdParser.cs +++ b/Shared/GameFiles/Bmd/BmdParser.cs @@ -1,13 +1,8 @@ -using System; -using System.IO; using System.Text; using Serilog; -using Shared.ByteParsing; -using Shared.Core.Misc; using Shared.Core.ErrorHandling; using Microsoft.Xna.Framework; using Shared.GameFormats.RigidModel.Transforms; -using Shared.GameFormats.Bmd; namespace Shared.GameFormats.Bmd { @@ -58,7 +53,7 @@ public BmdFile Parse() try { - if (_fastBinVersion < 8) + if (_fastBinVersion < 11) { //In a super early version of FastBin, there was stuff up here //(and it was eventually moved down?) @@ -69,7 +64,7 @@ public BmdFile Parse() _logger.Here().Information($"BMD Parser - Early props length: {early_props_length}"); // Read early props entries - for (int i = 0; i < early_props_length; i++) + for (var i = 0; i < early_props_length; i++) { if (_stream.Position >= _stream.Length) break; @@ -95,8 +90,30 @@ public BmdFile Parse() bmdFile.PropInfos.Add(earlyProp); } - //One of these is likely the early version of vfx, and the others... who knows - var otherthing1_length = _reader.ReadUInt32(); + //VFX (the modern version of VFX is way down the page) + //no version + var early_vfxs_length = _reader.ReadUInt32(); + _logger.Here().Information($"BMD Parser - Early vfxs length: {early_vfxs_length}"); + + // Read early vfx entries + for (var i = 0; i < early_vfxs_length; i++) + { + if (_stream.Position >= _stream.Length) break; + + var earlyVfx = new VfxInfo + { + VfxString = ReadString(), + VfxInfoVersion = _reader.ReadUInt16(), //seems to be the version + Transform = ReadRowMajorMatrix(true), + + //12 bytes of bytes I can't make heads or tails of, especially last 2 + EarlyVersionUnknownBytes = _reader.ReadBytes(12) + }; + + bmdFile.VfxInfos.Add(earlyVfx); + } + + //Probably more early version of stuff var otherthing2_length = _reader.ReadUInt32(); var otherthing3_length = _reader.ReadUInt32(); var otherthing4_length = _reader.ReadUInt32(); @@ -173,7 +190,9 @@ public BmdFile Parse() // Prop var propVersion = _reader.ReadUInt16(); ReadPropInfos(propVersion, bmdFile); - + } + if (_fastBinVersion > 8) + { // Vfx var vfxVersion = _reader.ReadUInt16(); ReadCollection("VfxInfo", bmdFile.VfxInfos, ReadVfxInfo, vfxVersion); @@ -182,7 +201,9 @@ public BmdFile Parse() var aiHintsVersion = _reader.ReadUInt16(); _logger.Here().Information($"BMD Parser - AiHints version: {aiHintsVersion}"); bmdFile.AiHints = ReadAiHints(); - + } + if (_fastBinVersion > 10) + { // LightProbe var lightProbeVersion = _reader.ReadUInt16(); ReadCollection("LightProbe", bmdFile.LightProbes, ReadLightProbeInfo, lightProbeVersion); @@ -198,27 +219,39 @@ public BmdFile Parse() // BuildingProjectileEmitters var buildingProjectileEmitterVersion = _reader.ReadUInt16(); ReadCollection("BuildingProjectileEmitter", bmdFile.BuildingProjectileEmitters, ReadBuildingProjectileEmitter, buildingProjectileEmitterVersion); - + } + if (_fastBinVersion > 15) + { // PlayableArea _logger.Here().Information($"BMD Parser - PlayableArea"); bmdFile.PlayableArea = ReadPlayableArea(); - + } + if (_fastBinVersion > 16) + { // PolyMesh var polyMeshVersion = _reader.ReadUInt16(); ReadCollection("PolyMesh", bmdFile.PolyMeshes, ReadPolyMeshInfo, polyMeshVersion); - + } + if (_fastBinVersion > 17) //guess + { // TerrainStencilBlendTriangles var terrainStencilBlendTriangleVersion = _reader.ReadUInt16(); ReadCollection("TerrainStencilBlendTriangle", bmdFile.TerrainStencilBlendTriangles, ReadTerrainStencilBlendTriangle, terrainStencilBlendTriangleVersion); - + } + if (_fastBinVersion > 18) //guess + { // SpotLight var spotLightVersion = _reader.ReadUInt16(); ReadCollection("SpotLight", bmdFile.SpotLights, ReadSpotLightInfo, spotLightVersion); - + } + if (_fastBinVersion > 19) //guess + { // Sound var soundVersion = _reader.ReadUInt16(); ReadCollection("Sound", bmdFile.Sounds, ReadSoundInfo, soundVersion); - + } + if (_fastBinVersion > 20) + { // CSC (Composite Scene Container) var cscVersion = _reader.ReadUInt16(); ReadCollection("CSC", bmdFile.CscInfos, ReadCscInfo, cscVersion); @@ -244,7 +277,9 @@ public BmdFile Parse() // TerraindDecals var terraindDecalVersion = _reader.ReadUInt16(); ReadCollection("TerraindDecal", bmdFile.TerraindDecals, ReadTerraindDecal, terraindDecalVersion); - + } + if (_fastBinVersion > 25) + { // TreeListReferences var treeListReferenceVersion = _reader.ReadUInt16(); ReadCollection("TreeListReference", bmdFile.TreeListReferences, ReadTreeListReference, treeListReferenceVersion); @@ -262,16 +297,10 @@ public BmdFile Parse() return bmdFile; } - catch (EndOfStreamException ex) - { - _logger.Here().Error($"BMD Parser - EndOfStreamException caught: {ex.Message}"); - _logger.Here().Error($"BMD Parser - Stream position: {_stream.Position}, Length: {_stream.Length}"); - _logger.Here().Warning($"BMD Parser - Returning partially parsed BMD file with {bmdFile.BattlefieldBuildings.Count} battlefield buildings"); - return bmdFile; - } catch (Exception ex) { - _logger.Here().Error($"BMD Parser - Unexpected exception: {ex.Message}"); + _logger.Here().Error($"BMD Parser - Exception caught: {ex.Message}"); + _logger.Here().Error($"BMD Parser - Stream position: {_stream.Position}, Length: {_stream.Length}"); _logger.Here().Error($"BMD Parser - Stack trace: {ex.StackTrace}"); throw; } @@ -290,7 +319,7 @@ private void ReadCollection(string collectionName, List collection, Func= _stream.Length) break; collection.Add(readFunc()); @@ -300,44 +329,95 @@ private void ReadCollection(string collectionName, List collection, Func 8) building.ParentId = _reader.ReadInt32(); - else + else if (building.Version > 6) building.ParentId = _reader.ReadInt16(); - building.BuildingKey = ReadString(); + + if (building.Version > 4) + building.BuildingKey = ReadString(); + building.PositionType = ReadString(); - // Transform matrix (3x4 matrix stored in row-major order) - building.Transform = ReadRowMajorMatrix(false); + if (building.Version < 6) + { + // Raw position / rotation / scale + var position = ReadRmvVector3(); + var translationMatrix = Matrix.CreateTranslation(position.ToVector3()); + + var rotation = new Quaternion( + _reader.ReadSingle(), + _reader.ReadSingle(), + _reader.ReadSingle(), + _reader.ReadSingle()); + var rotationMatrix = Matrix.CreateFromQuaternion(rotation); + + var scale = ReadRmvVector3(); + var scaleMatrix = Matrix.CreateScale(scale.ToVector3()); + + building.Transform = scaleMatrix * rotationMatrix * translationMatrix; + } + else + { + //Transformation matrix + building.Transform = ReadRowMajorMatrix(false); + } // Properties - building.PropertiesVersion = _reader.ReadUInt16(); - building.PropertiesBuildingId = ReadString(); - building.StartingDamageUnary = _reader.ReadSingle(); - building.OnFire = _reader.ReadByte() != 0; - building.StartDisabled = _reader.ReadByte() != 0; - building.WeakPoint = _reader.ReadByte() != 0; - building.AiBreachable = _reader.ReadByte() != 0; - building.Indestructible = _reader.ReadByte() != 0; - building.Dockable = _reader.ReadByte() != 0; - building.Toggleable = _reader.ReadByte() != 0; - building.Lite = _reader.ReadByte() != 0; - building.CastShadows = _reader.ReadByte() != 0; - building.KeyBuilding = _reader.ReadByte() != 0; - if (building.Version > 8) + if (building.Version < 4) + { + //No properties version + building.StartingDamageUnary = _reader.ReadSingle(); + building.OnFire = _reader.ReadByte() != 0; + building.StartDisabled = _reader.ReadByte() != 0; + building.WeakPoint = _reader.ReadByte() != 0; + building.AiBreachable = _reader.ReadByte() != 0; + building.Indestructible = _reader.ReadByte() != 0; + building.Dockable = _reader.ReadByte() != 0; + building.Toggleable = _reader.ReadByte() != 0; + building.Lite = _reader.ReadByte() != 0; + } + else { - building.KeyBuildingUseFort = _reader.ReadByte() != 0; - building.IsPropInOutfield = _reader.ReadByte() != 0; - building.SettlementLevelConfigurable = _reader.ReadByte() != 0; - building.HideTooltip = _reader.ReadByte() != 0; - building.IncludeInFog = _reader.ReadByte() != 0; + building.PropertiesVersion = _reader.ReadUInt16(); + building.PropertiesBuildingId = ReadString(); + building.StartingDamageUnary = _reader.ReadSingle(); + if (building.PropertiesVersion > 1) + { + building.OnFire = _reader.ReadByte() != 0; + building.StartDisabled = _reader.ReadByte() != 0; + building.WeakPoint = _reader.ReadByte() != 0; + building.AiBreachable = _reader.ReadByte() != 0; + building.Indestructible = _reader.ReadByte() != 0; + building.Dockable = _reader.ReadByte() != 0; + building.Toggleable = _reader.ReadByte() != 0; + building.Lite = _reader.ReadByte() != 0; + } + if (building.PropertiesVersion > 2) + building.CastShadows = _reader.ReadByte() != 0; + if (building.PropertiesVersion > 3) + building.KeyBuilding = _reader.ReadByte() != 0; + if (building.PropertiesVersion > 5) + { + building.KeyBuildingUseFort = _reader.ReadByte() != 0; + building.IsPropInOutfield = _reader.ReadByte() != 0; + } + if (building.PropertiesVersion > 8) + { + building.SettlementLevelConfigurable = _reader.ReadByte() != 0; + building.HideTooltip = _reader.ReadByte() != 0; + building.IncludeInFog = _reader.ReadByte() != 0; + } } - building.HeightMode = ReadString(); - + // Back to normal stuff + if (building.Version > 7) + building.HeightMode = ReadString(); if (building.Version > 8) building.Uid = _reader.ReadInt64(); @@ -360,11 +440,13 @@ private CaptureLocation ReadCaptureLocation() location.Something4 = _reader.ReadInt32(); location.Something5 = _reader.ReadInt32(); location.Str = ReadString(); - location.Str2 = ReadString(); + + if (location.Version > 2) + location.Str2 = ReadString(); var coordCount = _reader.ReadUInt32(); location.Coords = new float[coordCount * 2]; - for (int i = 0; i < location.Coords.Length; i++) + for (var i = 0; i < location.Coords.Length; i++) { location.Coords[i] = _reader.ReadSingle(); } @@ -372,10 +454,14 @@ private CaptureLocation ReadCaptureLocation() location.Str3 = ReadString(); location.Something6 = _reader.ReadSingle(); location.Something7 = _reader.ReadSingle(); - location.Bools = _reader.ReadBytes(7); - location.Something8 = _reader.ReadUInt16(); - location.Something9 = _reader.ReadSingle(); - location.Something10 = _reader.ReadSingle(); + location.Bools = _reader.ReadBytes(4); //redo this + if (location.Version > 2) + { + location.Bools = _reader.ReadBytes(3); //redo this + location.Something8 = _reader.ReadUInt16(); + location.Something9 = _reader.ReadSingle(); + location.Something10 = _reader.ReadSingle(); + } return location; } @@ -386,32 +472,45 @@ private EFLine ReadEFLine() private GoOutline ReadGoOutline() { - throw new NotImplementedException("BmdOutline parsing not implemented yet"); + var goOutline = new GoOutline(); + + //no version + + var vertexListLength = _reader.ReadUInt32(); + goOutline.VertexList = []; + for (var i = 0; i < vertexListLength; i++) + { + var x = _reader.ReadSingle(); + var y = _reader.ReadSingle(); + goOutline.VertexList.Add(new RmvVector2(x, y)); + } + + return goOutline; } private NonTerrainOutline ReadNonTerrainOutline() { - var outline = new NonTerrainOutline(); + var ntOutline = new NonTerrainOutline(); + + //no version - // Read VertexListLength (hidden=true, so we just read it but don't store it) var vertexListLength = _reader.ReadUInt32(); - - // Read VertexList array - outline.VertexList = new List(); - for (int i = 0; i < vertexListLength; i++) + ntOutline.VertexList = []; + for (var i = 0; i < vertexListLength; i++) { var x = _reader.ReadSingle(); var y = _reader.ReadSingle(); - outline.VertexList.Add(new RmvVector2(x, y)); + ntOutline.VertexList.Add(new RmvVector2(x, y)); } - return outline; + return ntOutline; } private ZonesTemplate ReadZonesTemplate() { var template = new ZonesTemplate(); - template.Version = _reader.ReadUInt16(); + + //no version var outlineLength = _reader.ReadUInt32(); for (uint i = 0; i < outlineLength; i++) @@ -430,8 +529,7 @@ private ZonesTemplate ReadZonesTemplate() template.LinesLength = _reader.ReadUInt32(); //template.LinesData = blah blah; //skip the actual Lines data since structure is unknown - for (int i = 0; i < 16; i++) - template.TransformMatrix[i] = _reader.ReadSingle(); + template.Transform = ReadRowMajorMatrix(true); return template; } @@ -439,16 +537,43 @@ private ZonesTemplate ReadZonesTemplate() private BmdInfo ReadBmdInfo() { var bmd = new BmdInfo(); + bmd.Version = _reader.ReadUInt16(); bmd.BmdString = ReadString(); bmd.Transform = ReadRowMajorMatrix(true); - bmd.SeasonsMaybe = _reader.ReadBytes(4); - bmd.CultureMask = ReadCultureMask(); - bmd.RegionString = ReadString(); - bmd.HeightMode = ReadString(); + + //not sure how this works, it's 0 (empty) 99.99% of the time + bmd.PropertyOverrides = _reader.ReadUInt32(); + if (bmd.PropertyOverrides == 1) //can there be multple? is this a length? I've only seen it as 1 + { + var propertyOverridesVersion = _reader.ReadUInt16(); + ReadString(); + _reader.ReadSingle(); + _reader.ReadBytes(8); + if (propertyOverridesVersion > 2) + _reader.ReadByte(); + if (propertyOverridesVersion > 3) + _reader.ReadByte(); + } + + if (bmd.Version == 4) + _reader.ReadBytes(6); + else if (bmd.Version == 5 || bmd.Version == 6) + _reader.ReadBytes(7); + else if (bmd.Version == 7) + _reader.ReadBytes(11); //weird because this is larger than version 8 + else if (bmd.Version > 7) + { + bmd.CultureMask = ReadCultureMask(); + bmd.RegionString = ReadString(); + } + + if (bmd.Version > 5) + bmd.HeightMode = ReadString(); if (bmd.Version > 8) bmd.Uid = _reader.ReadBytes(8); + return bmd; } @@ -493,7 +618,7 @@ private void ReadPropInfos(ushort propVersion, BmdFile bmdFile) var propCount = _reader.ReadUInt32(); _logger.Here().Information($"BMD Parser - PropString count: {propCount}"); - for (int i = 0; i < propCount; i++) + for (var i = 0; i < propCount; i++) { if (_stream.Position >= _stream.Length) break; propsList.Add(ReadString()); @@ -504,7 +629,7 @@ private void ReadPropInfos(ushort propVersion, BmdFile bmdFile) var propInfoCount = _reader.ReadUInt32(); _logger.Here().Information($"BMD Parser - PropInfo count: {propInfoCount}"); - for (int i = 0; i < propInfoCount; i++) + for (var i = 0; i < propInfoCount; i++) { if (_stream.Position >= _stream.Length) break; bmdFile.PropInfos.Add(ReadPropInfo(propsList)); @@ -517,15 +642,41 @@ private PropInfo ReadPropInfo(List propsList) prop.PropInfoVersion = _reader.ReadUInt16(); if (prop.PropInfoVersion <= 12) - prop.Rmv2Path = ReadString(); //Read string directly for old versions + { + //Read string directly for old versions + prop.Rmv2Path = ReadString(); + } else { + // Map the PropIndex to the actual RMV2 path from the props list var propIndex = _reader.ReadUInt32(); - prop.Rmv2Path = propsList[(int)propIndex]; // Map the PropIndex to the actual RMV2 path from the props list + prop.Rmv2Path = propsList[(int)propIndex]; } - // Read transform matrix (3x4 matrix stored in row-major order) prop.Transform = ReadRowMajorMatrix(false); + + if (prop.PropInfoVersion < 4) + { + //There's less mystery bytes for the later version (version 3 seen) + if (prop.PropInfoVersion == 1) + _reader.ReadBytes(30); //proto-props version 3 (last seen) only had 28 mystery bytes + else if (prop.PropInfoVersion == 2) + { + _reader.ReadBytes(7); + + //3 floats, some position thing + _reader.ReadSingle(); + _reader.ReadSingle(); + _reader.ReadSingle(); + + _reader.ReadBytes(11); + _reader.ReadByte(); //one extra compared to version 1 + } + else if (prop.PropInfoVersion == 3) + _reader.ReadBytes(23); //proto-props version 1 had 23 mystery bytes, clue? + + return prop; + } prop.IsDecal = _reader.ReadByte() != 0; prop.LogicalDecal = _reader.ReadByte() != 0; prop.IsFauna = _reader.ReadByte() != 0; @@ -540,15 +691,18 @@ private PropInfo ReadPropInfo(List propsList) prop.Flags = ReadBmdComponentFlags(); - prop.VisibleInShroud = _reader.ReadByte() != 0; - prop.ApplyToTerrain = _reader.ReadByte() != 0; - prop.ApplyToPropsOrReceiveDecal = _reader.ReadByte() != 0; - prop.RenderAboveSnow = _reader.ReadByte() != 0; + if (prop.PropInfoVersion > 4) + { + prop.VisibleInShroud = _reader.ReadByte() != 0; + prop.ApplyToTerrain = _reader.ReadByte() != 0; + prop.ApplyToPropsOrReceiveDecal = _reader.ReadByte() != 0; + prop.RenderAboveSnow = _reader.ReadByte() != 0; + } if (prop.PropInfoVersion > 7) prop.HeightMode = ReadString(); - if (prop.PropInfoVersion > 14) + if (prop.PropInfoVersion > 15) prop.CultureMask = _reader.ReadBytes(8); else if (prop.PropInfoVersion > 10) { @@ -566,7 +720,7 @@ private PropInfo ReadPropInfo(List propsList) prop.CastsShadow = _reader.ReadByte() != 0; if (prop.PropInfoVersion > 13) prop.NoCulling = _reader.ReadByte() != 0; - if (prop.PropInfoVersion > 15) + if (prop.PropInfoVersion > 14) prop.HasHeightPatch = _reader.ReadByte() != 0; if (prop.PropInfoVersion > 16) prop.ApplyHeightPatch = _reader.ReadByte() != 0; @@ -578,7 +732,7 @@ private PropInfo ReadPropInfo(List propsList) //Not quite sure what's happening with version 21/22 if (prop.PropInfoVersion == 21) prop.SomeWeirdThing = _reader.ReadByte() != 0; - if (prop.PropInfoVersion == 22) + if (prop.PropInfoVersion == 22) { prop.SomeWeirdThing = _reader.ReadByte() != 0; prop.SomeWeirdThing2 = _reader.ReadByte() != 0; @@ -603,7 +757,14 @@ private VfxInfo ReadVfxInfo() vfx.EmissionRate = _reader.ReadSingle(); vfx.InstanceName = ReadString(); - vfx.Flags = ReadBmdComponentFlags(); + + if (vfx.VfxInfoVersion == 1) + { + _reader.ReadUInt16(); //one + _reader.ReadUInt32(); //zero + } + else + vfx.Flags = ReadBmdComponentFlags(); if (vfx.VfxInfoVersion > 2) vfx.HeightMode = ReadString(); @@ -623,14 +784,14 @@ private VfxInfo ReadVfxInfo() vfx.Autoplay = _reader.ReadByte() != 0; vfx.VisibleInShroud = _reader.ReadByte() != 0; } - if (vfx.VfxInfoVersion > 7) - { + + if (vfx.VfxInfoVersion == 8) + vfx.ParentId = _reader.ReadInt16(); + else if (vfx.VfxInfoVersion > 8) vfx.ParentId = _reader.ReadInt32(); - } + if (vfx.VfxInfoVersion > 9) - { vfx.VisibleInShroudOnly = _reader.ReadByte() != 0; - } return vfx; } @@ -640,32 +801,28 @@ private AiHints ReadAiHints() var aiHints = new AiHints(); // Read Separators - var separatorsVersion = _reader.ReadUInt16(); + _ = _reader.ReadUInt16(); // separatorsVersion - unused var separatorsCount = _reader.ReadUInt32(); - for (int i = 0; i < separatorsCount; i++) - { + if (separatorsCount > 0) throw new NotImplementedException("AiHints-Separators parsing not implemented yet"); - } // Read DirectedPoints - var directedPointsVersion = _reader.ReadUInt16(); + _ = _reader.ReadUInt16(); // directedPointsVersion - unused var directedPointsCount = _reader.ReadUInt32(); - for (int i = 0; i < directedPointsCount; i++) - { + if (directedPointsCount > 0) throw new NotImplementedException("AiHints-DirectedPoints parsing not implemented yet"); - } // Read PolyLines - var polyLinesVersion = _reader.ReadUInt16(); + _ = _reader.ReadUInt16(); // polyLinesVersion - unused var polyLinesCount = _reader.ReadUInt32(); - for (int i = 0; i < polyLinesCount; i++) + for (var i = 0; i < polyLinesCount; i++) { var polyLine = new HintPolyLine(); polyLine.Version = _reader.ReadUInt16(); polyLine.Type = ReadString(); var pointsCount = _reader.ReadUInt32(); - for (int j = 0; j < pointsCount; j++) + for (var j = 0; j < pointsCount; j++) { var point = new RmvVector2(); point.X = _reader.ReadSingle(); @@ -673,7 +830,7 @@ private AiHints ReadAiHints() polyLine.Points.Add(point); } - if (polyLine.Version > 3) + if (polyLine.Version > 1) polyLine.ScriptId = ReadString(); if (polyLine.Version > 2) //version is a guess polyLine.OnlyVanguard = _reader.ReadByte() != 0; @@ -687,9 +844,9 @@ private AiHints ReadAiHints() } // Read PolyLinesList - var polyLinesListVersion = _reader.ReadUInt16(); + _ = _reader.ReadUInt16(); // polyLinesListVersion - unused var polyLinesListCount = _reader.ReadUInt32(); - for (int i = 0; i < polyLinesListCount; i++) + for (var i = 0; i < polyLinesListCount; i++) { var polyLineList = new HintPolyLineList(); polyLineList.Version = _reader.ReadUInt16(); @@ -697,22 +854,20 @@ private AiHints ReadAiHints() // Read type string var typeLength = _reader.ReadUInt16(); if (typeLength > 0) - { polyLineList.Type = Encoding.UTF8.GetString(_reader.ReadBytes(typeLength)); - } // Read district polyLineList.District = _reader.ReadUInt32(); // Read polygon list var polygonListLength = _reader.ReadUInt32(); - for (int j = 0; j < polygonListLength; j++) + for (var j = 0; j < polygonListLength; j++) { var polygon = new Polygon(); // Read points for this polygon var pointsLength = _reader.ReadUInt32(); - for (int k = 0; k < pointsLength; k++) + for (var k = 0; k < pointsLength; k++) { var point = new RmvVector2(); point.X = _reader.ReadSingle(); @@ -733,35 +888,47 @@ private AiHints ReadAiHints() private LightProbeInfo ReadLightProbeInfo() { var probe = new LightProbeInfo(); + probe.Version = _reader.ReadUInt16(); + probe.Position = ReadRmvVector3(); probe.OuterRadius = _reader.ReadSingle(); - probe.InnerRadius = _reader.ReadSingle(); - probe.SomeZero = _reader.ReadByte(); + + if (probe.Version > 2) + { + probe.InnerRadius = _reader.ReadSingle(); + probe.SomeZero = _reader.ReadByte(); + } + probe.Primary = _reader.ReadByte() != 0; probe.HeightMode = ReadString(); + return probe; } private TerrainHoleTriangleInfo ReadTerrainHoleTriangleInfo() { var hole = new TerrainHoleTriangleInfo(); + hole.TerrainHoleVersion = _reader.ReadUInt16(); + hole.FirstVert = ReadRmvVector3(); hole.SecondVert = ReadRmvVector3(); hole.ThirdVert = ReadRmvVector3(); - hole.HeightMode = ReadString(); + if (hole.TerrainHoleVersion > 1) + hole.HeightMode = ReadString(); if (hole.TerrainHoleVersion > 2) - { - hole.Booleans = _reader.ReadBytes(10); - } + hole.Flags = ReadBmdComponentFlags(); + return hole; } private PointLightInfo ReadPointLightInfo() { var light = new PointLightInfo(); + light.PointLightInfoVersion = _reader.ReadUInt16(); + light.Position = ReadRmvVector3(); light.Radius = _reader.ReadSingle(); light.Red = _reader.ReadSingle(); @@ -778,18 +945,17 @@ private PointLightInfo ReadPointLightInfo() if (light.PointLightInfoVersion > 1) light.HeightMode = ReadString(); if (light.PointLightInfoVersion > 2) - { light.LightProbeOnly = _reader.ReadByte() != 0; - + if (light.PointLightInfoVersion > 3) + { if (light.PointLightInfoVersion > 5) light.PdlcMask = _reader.ReadUInt64(); else light.PdlcMask = _reader.ReadUInt32(); } if (light.PointLightInfoVersion > 6) - { - light.MoreData2 = _reader.ReadBytes(10); - } + light.Flags = ReadBmdComponentFlags(); + return light; } @@ -819,7 +985,7 @@ private PlayableArea ReadPlayableArea() area.PlayableAreaVersion = _reader.ReadUInt16(); area.BoundingBox = new float[4]; - for (int i = 0; i < 4; i++) + for (var i = 0; i < 4; i++) area.BoundingBox[i] = _reader.ReadSingle(); area.HasBeenSet = _reader.ReadByte() != 0; @@ -844,30 +1010,21 @@ private PolyMeshInfo ReadPolyMeshInfo() var vertexCount = _reader.ReadUInt32(); mesh.VertexList = new RmvVector3[vertexCount]; - for (int i = 0; i < vertexCount; i++) - { + for (var i = 0; i < vertexCount; i++) mesh.VertexList[i] = ReadRmvVector3(); - } var triangleCount = _reader.ReadUInt32(); mesh.TriangleList = new ushort[triangleCount]; - for (int i = 0; i < triangleCount; i++) - { + for (var i = 0; i < triangleCount; i++) mesh.TriangleList[i] = _reader.ReadUInt16(); - } mesh.MaterialString = ReadString(); mesh.HeightMode = ReadString(); if (mesh.PolyMeshVersion > 2) - { - mesh.MoreData = _reader.ReadBytes(8); - mesh.VisibleInTactical = _reader.ReadByte() != 0; - mesh.OnlyVisibleInTactical = _reader.ReadByte() != 0; - } + mesh.Flags = ReadBmdComponentFlags(); if (mesh.PolyMeshVersion > 3) { - // Read transform matrix (3x4 matrix stored in row-major order) mesh.Transform = ReadRowMajorMatrix(false); mesh.Booleans = _reader.ReadBytes(4); mesh.VisibleInShroud = _reader.ReadByte() != 0; @@ -901,7 +1058,15 @@ private SpotLightInfo ReadSpotLightInfo() light.Gobo = ReadString(); light.Volumetric = _reader.ReadByte() != 0; light.HeightMode = ReadString(); - light.MoreData = _reader.ReadBytes(18); + + if (light.Version > 3) + light.PdlcMask = _reader.ReadUInt32(); + else if (light.Version > 4) + light.PdlcMask = _reader.ReadUInt64(); + + if (light.Version > 7) + light.Flags = ReadBmdComponentFlags(); + return light; } @@ -916,7 +1081,7 @@ private TerrainHoleTriangleInfo ReadTerrainHoleInfo() if (hole.TerrainHoleVersion > 2) { - hole.Booleans = _reader.ReadBytes(10); + hole.Flags = ReadBmdComponentFlags(); } return hole; @@ -931,7 +1096,7 @@ private SoundInfo ReadSoundInfo() var coordCount = _reader.ReadUInt32(); sound.CoordList = new RmvVector3[coordCount]; - for (int i = 0; i < coordCount; i++) + for (var i = 0; i < coordCount; i++) sound.CoordList[i] = ReadRmvVector3(); sound.InnerRadius = _reader.ReadSingle(); @@ -940,21 +1105,34 @@ private SoundInfo ReadSoundInfo() sound.InnerCubeBoundingBox = (ReadRmvVector3(), ReadRmvVector3()); sound.OuterCubeBoundingBox = (ReadRmvVector3(), ReadRmvVector3()); - sound.RiverNodesLength = _reader.ReadUInt32(); - // River nodes are always empty according to comments, so we skip reading them + var riverNodeCount = _reader.ReadUInt32(); + sound.RiverNodeList = new RiverNode[riverNodeCount]; + for (var i = 0; i < riverNodeCount; i++) + { + sound.RiverNodeList[i] = new RiverNode + { + Version = _reader.ReadUInt16(), + Position = ReadRmvVector3(), + Something1 = _reader.ReadSingle(), + Something2 = _reader.ReadSingle() + }; + } sound.ClampToSurface = _reader.ReadByte(); sound.HeightMode = ReadString(); - // Campaign type mask - conditional based on version if (sound.Version > 9) sound.CampaignTypeMask = _reader.ReadUInt64(); else sound.CampaignTypeMask = _reader.ReadUInt32(); - - sound.CultureMask = ReadCultureMask(); - sound.DirectionVector = ReadRmvVector3(); - sound.UpVector = ReadRmvVector3(); + + if (sound.Version > 5) + sound.CultureMask = ReadCultureMask(); + if (sound.Version > 7) + { + sound.DirectionVector = ReadRmvVector3(); + sound.UpVector = ReadRmvVector3(); + } if (sound.Version > 8) sound.Scope = ReadString(); @@ -966,14 +1144,16 @@ private CscInfo ReadCscInfo() var csc = new CscInfo(); csc.Version = _reader.ReadUInt16(); - // Read transform matrix (3x4 matrix stored in row-major order) csc.Transform = ReadRowMajorMatrix(false); csc.SceneFile = ReadString(); csc.HeightMode = ReadString(); - csc.PdlcMask = _reader.ReadUInt64(); - csc.Autoplay = _reader.ReadByte() != 0; - csc.VisibleInShroud = _reader.ReadByte() != 0; - csc.NoCulling = _reader.ReadByte() != 0; + if (csc.Version > 2) + { + csc.PdlcMask = _reader.ReadUInt64(); + csc.Autoplay = _reader.ReadByte() != 0; + csc.VisibleInShroud = _reader.ReadByte() != 0; + csc.NoCulling = _reader.ReadByte() != 0; + } if (csc.Version > 7) { csc.ScriptId = ReadString(); @@ -1000,12 +1180,10 @@ private Deployment ReadDeployment() var deployment = new Deployment(); deployment.Version = _reader.ReadUInt16(); - // Read category deployment.Category = ReadString(); - // Read DeploymentZone list var deploymentZoneListLength = _reader.ReadUInt32(); - for (int i = 0; i < deploymentZoneListLength; i++) + for (var i = 0; i < deploymentZoneListLength; i++) { deployment.DeploymentZones.Add(ReadDeploymentZone()); } @@ -1018,9 +1196,8 @@ private DeploymentZone ReadDeploymentZone() var deploymentZone = new DeploymentZone(); deploymentZone.Version = _reader.ReadUInt16(); - // Read DeploymentZoneRegion list var deploymentZoneRegionListLength = _reader.ReadUInt32(); - for (int i = 0; i < deploymentZoneRegionListLength; i++) + for (var i = 0; i < deploymentZoneRegionListLength; i++) { deploymentZone.DeploymentZoneRegions.Add(ReadDeploymentZoneRegion()); } @@ -1033,9 +1210,8 @@ private DeploymentZoneRegion ReadDeploymentZoneRegion() var region = new DeploymentZoneRegion(); region.Version = _reader.ReadUInt16(); - // Read Boundary list var boundaryListLength = _reader.ReadUInt32(); - for (int i = 0; i < boundaryListLength; i++) + for (var i = 0; i < boundaryListLength; i++) { region.Boundaries.Add(ReadBoundary()); } @@ -1052,12 +1228,10 @@ private Boundary ReadBoundary() var boundary = new Boundary(); boundary.Version = _reader.ReadUInt16(); - // Read boundary type boundary.BoundaryType = ReadString(); - // Read point list var pointListLength = _reader.ReadUInt32(); - for (int i = 0; i < pointListLength; i++) + for (var i = 0; i < pointListLength; i++) { var coord = new RmvVector2 { @@ -1106,20 +1280,28 @@ private WaterOutline ReadWaterOutline() private BmdComponentFlags ReadBmdComponentFlags() { var flags = new BmdComponentFlags(); + flags.FlagVersion = _reader.ReadUInt16(); + flags.AllowInOutfield = _reader.ReadByte() != 0; - if (flags.FlagVersion == 2) + + //Clamp to surface goes away after version 2, merged with clamp to water? + if (flags.FlagVersion < 3) flags.ClampToSurface = _reader.ReadByte() != 0; flags.ClampToWaterSurface = _reader.ReadByte() != 0; + + //Seasons flags.SeasonSpring = _reader.ReadByte() != 0; flags.SeasonSummer = _reader.ReadByte() != 0; flags.SeasonAutumn = _reader.ReadByte() != 0; flags.SeasonWinter = _reader.ReadByte() != 0; + if (flags.FlagVersion > 3) { flags.VisibleInTactical = _reader.ReadByte() != 0; flags.OnlyVisibleInTactical = _reader.ReadByte() != 0; } + return flags; }