diff --git a/Assets/Scripts/Game/StaticNPC.cs b/Assets/Scripts/Game/StaticNPC.cs index 91ab6cd806..40d00fe7db 100644 --- a/Assets/Scripts/Game/StaticNPC.cs +++ b/Assets/Scripts/Game/StaticNPC.cs @@ -182,7 +182,11 @@ public void SetLayoutData(DFBlock.RmbBlockFlatObjectRecord obj, int mapId, int l int flatID = FlatsFile.GetFlatID(obj.TextureArchive, obj.TextureRecord); if (DaggerfallUnity.Instance.ContentReader.FlatsFileReader.GetFlatData(flatID, out FlatsFile.FlatData flatCFG)) { - if (flatCFG.gender.Contains("2")) + // We've had null reference exceptions in this function, + // which could only possibly be this gender field + if (flatCFG.gender == null) + Debug.LogError($"Flat '{obj.TextureArchive}_{obj.TextureRecord}' has invalid gender in FLATS.CFG"); + else if (flatCFG.gender.Contains("2")) obj.Flags |= 32; else obj.Flags &= 223; diff --git a/Assets/Scripts/Utility/RMBLayout.cs b/Assets/Scripts/Utility/RMBLayout.cs index 55520903c7..dc1bbc3411 100644 --- a/Assets/Scripts/Utility/RMBLayout.cs +++ b/Assets/Scripts/Utility/RMBLayout.cs @@ -339,42 +339,51 @@ public static void AddMiscBlockFlats( // Add block flats foreach (DFBlock.RmbBlockFlatObjectRecord obj in blockData.RmbBlock.MiscFlatObjectRecords) { - // Ignore lights as they are handled by AddLights() - if (obj.TextureArchive == TextureReader.LightsTextureArchive) - continue; - - // Calculate position - Vector3 billboardPosition = new Vector3( - obj.XPos, - -obj.YPos + blockFlatsOffsetY, - obj.ZPos + BlocksFile.RMBDimension) * MeshReader.GlobalScale; - - GameObject go = MeshReplacement.ImportCustomFlatGameobject(obj.TextureArchive, obj.TextureRecord, billboardPosition, flatsParent); - if (go == null) + // If any error happens creating this object, try to avoid destroying the entire city + try { - // Add standalone billboard gameobject - go = GameObjectHelper.CreateDaggerfallBillboardGameObject(obj.TextureArchive, obj.TextureRecord, flatsParent); - go.transform.position = billboardPosition; - AlignBillboardToBase(go); - } + // Ignore lights as they are handled by AddLights() + if (obj.TextureArchive == TextureReader.LightsTextureArchive) + continue; - // Add animal sound - if (obj.TextureArchive == TextureReader.AnimalsTextureArchive) - GameObjectHelper.AddAnimalAudioSource(go, obj.TextureRecord); + // Calculate position + Vector3 billboardPosition = new Vector3( + obj.XPos, + -obj.YPos + blockFlatsOffsetY, + obj.ZPos + BlocksFile.RMBDimension) * MeshReader.GlobalScale; - // If flat record has a non-zero faction id, then it's an exterior NPC - if (obj.FactionID != 0) - { - // Add RMB data to billboard - Billboard dfBillboard = go.GetComponent(); - if (dfBillboard != null) - dfBillboard.SetRMBPeopleData(obj.FactionID, obj.Flags, obj.Position); + GameObject go = MeshReplacement.ImportCustomFlatGameobject(obj.TextureArchive, obj.TextureRecord, billboardPosition, flatsParent); + if (go == null) + { + // Add standalone billboard gameobject + go = GameObjectHelper.CreateDaggerfallBillboardGameObject(obj.TextureArchive, obj.TextureRecord, flatsParent); + go.transform.position = billboardPosition; + AlignBillboardToBase(go); + } - // Add StaticNPC behaviour - StaticNPC npc = go.AddComponent(); - npc.SetLayoutData(obj, mapId, locationIndex); + // Add animal sound + if (obj.TextureArchive == TextureReader.AnimalsTextureArchive) + GameObjectHelper.AddAnimalAudioSource(go, obj.TextureRecord); - QuestMachine.Instance.SetupIndividualStaticNPC(go, obj.FactionID); + // If flat record has a non-zero faction id, then it's an exterior NPC + if (obj.FactionID != 0) + { + // Add RMB data to billboard + Billboard dfBillboard = go.GetComponent(); + if (dfBillboard != null) + dfBillboard.SetRMBPeopleData(obj.FactionID, obj.Flags, obj.Position); + + // Add StaticNPC behaviour + StaticNPC npc = go.AddComponent(); + npc.SetLayoutData(obj, mapId, locationIndex); + + QuestMachine.Instance.SetupIndividualStaticNPC(go, obj.FactionID); + } + } + catch(Exception ex) + { + Debug.LogError($"Error creating misc flat obj '{obj.Position}' of block '{blockData.Name}'"); + Debug.LogException(ex); } } } @@ -404,69 +413,78 @@ public static void AddExteriorBlockFlats( foreach (DFBlock.RmbBlockFlatObjectRecord obj in subRecord.Exterior.BlockFlatObjectRecords) { - // Don't add building exterior editor flats since they can't be used by any DFU systems - int archive = obj.TextureArchive; - if (archive == TextureReader.EditorFlatsTextureArchive) - continue; + // If any error happens creating this object, try to avoid destroying the entire city + try + { + // Don't add building exterior editor flats since they can't be used by any DFU systems + int archive = obj.TextureArchive; + if (archive == TextureReader.EditorFlatsTextureArchive) + continue; - // Calculate position - Vector3 billboardPosition = new Vector3( - obj.XPos, - -obj.YPos + blockFlatsOffsetY, - obj.ZPos + BlocksFile.RMBDimension) * MeshReader.GlobalScale; + // Calculate position + Vector3 billboardPosition = new Vector3( + obj.XPos, + -obj.YPos + blockFlatsOffsetY, + obj.ZPos + BlocksFile.RMBDimension) * MeshReader.GlobalScale; - billboardPosition += subRecordPosition; + billboardPosition += subRecordPosition; - // Add natures using correct climate set archive - if (archive >= (int)DFLocation.ClimateTextureSet.Nature_RainForest && archive <= (int)DFLocation.ClimateTextureSet.Nature_Mountains_Snow) - { - archive = natureArchive; - billboardPosition.z = natureFlatsOffsetY; - } + // Add natures using correct climate set archive + if (archive >= (int)DFLocation.ClimateTextureSet.Nature_RainForest && archive <= (int)DFLocation.ClimateTextureSet.Nature_Mountains_Snow) + { + archive = natureArchive; + billboardPosition.z = natureFlatsOffsetY; + } - GameObject go = MeshReplacement.ImportCustomFlatGameobject(archive, obj.TextureRecord, billboardPosition, flatsParent); - bool isImported = go != null; - if (!isImported) - { - // Add standalone billboard gameobject - go = GameObjectHelper.CreateDaggerfallBillboardGameObject(archive, obj.TextureRecord, flatsParent); - go.transform.position = billboardPosition; - AlignBillboardToBase(go); - } + GameObject go = MeshReplacement.ImportCustomFlatGameobject(archive, obj.TextureRecord, billboardPosition, flatsParent); + bool isImported = go != null; + if (!isImported) + { + // Add standalone billboard gameobject + go = GameObjectHelper.CreateDaggerfallBillboardGameObject(archive, obj.TextureRecord, flatsParent); + go.transform.position = billboardPosition; + AlignBillboardToBase(go); + } - // Add animal sound - if (archive == TextureReader.AnimalsTextureArchive) - GameObjectHelper.AddAnimalAudioSource(go, obj.TextureRecord); + // Add animal sound + if (archive == TextureReader.AnimalsTextureArchive) + GameObjectHelper.AddAnimalAudioSource(go, obj.TextureRecord); - // If flat record has a non-zero faction id, then it's an exterior NPC - if (obj.FactionID != 0) - { - // Add RMB data to billboard - Billboard dfBillboard = go.GetComponent(); - if (dfBillboard != null) - dfBillboard.SetRMBPeopleData(obj.FactionID, obj.Flags, obj.Position); + // If flat record has a non-zero faction id, then it's an exterior NPC + if (obj.FactionID != 0) + { + // Add RMB data to billboard + Billboard dfBillboard = go.GetComponent(); + if (dfBillboard != null) + dfBillboard.SetRMBPeopleData(obj.FactionID, obj.Flags, obj.Position); - // Add StaticNPC behaviour - StaticNPC npc = go.AddComponent(); - npc.SetLayoutData(obj, mapId, locationIndex); + // Add StaticNPC behaviour + StaticNPC npc = go.AddComponent(); + npc.SetLayoutData(obj, mapId, locationIndex); - QuestMachine.Instance.SetupIndividualStaticNPC(go, obj.FactionID); - } + QuestMachine.Instance.SetupIndividualStaticNPC(go, obj.FactionID); + } - // If this is a light flat, import light prefab - if (archive == TextureReader.LightsTextureArchive && !isImported) - { - if (dfUnity.Option_CityLightPrefab == null) - return; + // If this is a light flat, import light prefab + if (archive == TextureReader.LightsTextureArchive && !isImported) + { + if (dfUnity.Option_CityLightPrefab == null) + return; - Vector2 size = dfUnity.MeshReader.GetScaledBillboardSize(210, obj.TextureRecord); - Vector3 position = new Vector3( - obj.XPos, - -obj.YPos + size.y, - obj.ZPos + BlocksFile.RMBDimension) * MeshReader.GlobalScale; - position += subRecordPosition; + Vector2 size = dfUnity.MeshReader.GetScaledBillboardSize(210, obj.TextureRecord); + Vector3 position = new Vector3( + obj.XPos, + -obj.YPos + size.y, + obj.ZPos + BlocksFile.RMBDimension) * MeshReader.GlobalScale; + position += subRecordPosition; - GameObjectHelper.InstantiatePrefab(dfUnity.Option_CityLightPrefab.gameObject, string.Empty, lightsParent, position); + GameObjectHelper.InstantiatePrefab(dfUnity.Option_CityLightPrefab.gameObject, string.Empty, lightsParent, position); + } + } + catch (Exception ex) + { + Debug.LogError($"Error creating exterior block flat obj '{obj.Position}' of block '{blockData.Name}'"); + Debug.LogException(ex); } } } @@ -840,7 +858,7 @@ private static void AddModels( // Get model data ModelData modelData; - dfUnity.MeshReader.GetModelData(obj.ModelIdNum, out modelData); + bool hasModelData = dfUnity.MeshReader.GetModelData(obj.ModelIdNum, out modelData); // Does this model have doors? StaticDoor[] staticDoors = null; @@ -875,10 +893,19 @@ private static void AddModels( if (staticDoors != null && staticDoors.Length > 0) CustomDoor.InitDoors(go, staticDoors, buildingKey, out dontCreateStaticDoors); } - else if (combiner == null || IsCityGate(obj.ModelIdNum) || IsBulletinBoard(obj.ModelIdNum) || PlayerActivate.HasCustomActivation(obj.ModelIdNum)) - AddStandaloneModel(dfUnity, ref modelData, modelMatrix, parent); else - combiner.Add(ref modelData, modelMatrix); + { + if(!hasModelData) + { + Debug.LogError($"Could not load model '{obj.ModelIdNum}' in block '{blockData.Name}'"); + continue; + } + + if (combiner == null || IsCityGate(obj.ModelIdNum) || IsBulletinBoard(obj.ModelIdNum) || PlayerActivate.HasCustomActivation(obj.ModelIdNum)) + AddStandaloneModel(dfUnity, ref modelData, modelMatrix, parent); + else + combiner.Add(ref modelData, modelMatrix); + } if (modelData.Doors != null && !dontCreateStaticDoors) doorsOut.AddRange(staticDoors);