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;
}