Skip to content
Open
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 57 additions & 2 deletions Assets/Scripts/Utility/AssetInjection/WorldDataReplacement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ private static bool AssignBlockIndices(ref DFLocation dfLocation)
// RMB blocks
foreach (string blockName in dfLocation.Exterior.ExteriorData.BlockNames)
if (blocksFile.GetBlockIndex(blockName) == -1)
AssignNextIndex(blockName);
AssignNewIndex(blockName);

// RDB blocks
if (dfLocation.Dungeon.Blocks != null)
Expand All @@ -495,7 +495,7 @@ private static bool AssignBlockIndices(ref DFLocation dfLocation)
{
string blockName = dungeonBlock.BlockName;
if (blocksFile.GetBlockIndex(blockName) == -1)
AssignNextIndex(blockName);
AssignNewIndex(blockName);
}
}
return true;
Expand All @@ -504,6 +504,61 @@ private static bool AssignBlockIndices(ref DFLocation dfLocation)
return false;
}

public static void AssignNewIndex(string blockName)
{
string fileName = GetDFBlockReplacementFilename(blockName, WorldDataVariants.NoVariant);

int jsonBlockIndex;
DFBlock? dfBlock = null; // Make blockData nullable
TextAsset blockReplacementJsonAsset;

// Seek from loose files
if (File.Exists(Path.Combine(worldDataPath, fileName)))
{
string blockReplacementJson = File.ReadAllText(Path.Combine(worldDataPath, fileName));
dfBlock = (DFBlock)SaveLoadManager.Deserialize(typeof(DFBlock), blockReplacementJson);
}
// Seek from mods
else if (ModManager.Instance != null && ModManager.Instance.TryGetAsset(fileName, false, out blockReplacementJsonAsset))
{
dfBlock = (DFBlock)SaveLoadManager.Deserialize(typeof(DFBlock), blockReplacementJsonAsset.text);
}

// Ensure blockData was successfully assigned
if (!dfBlock.HasValue)
{
Debug.LogError($"Failed to load block data for blockName: {blockName}");
throw new System.Exception($"Block {blockName} does not have a valid Index in its JSON file.");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is gonna throw it should probably have its own exception so it can be caught by the caller. Lines 519 and 524 could also possibly throw invalid cast exceptions, whether or not that can actually happen in the FS library idk.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added WorldDataReplacementException and wrapped the (DFBlock)SaveLoadManager.Deserialize(...) calls in a try / catch (InvalidCastException) block.

}

// Grab the variant key
string blockKey = blockName + WorldDataVariants.NoVariant;

// Check for the "Index" field and assign its value
jsonBlockIndex = dfBlock.Value.Index;

// If jsonBlockIndex is invalid (less than or equal to nextBlockIndex), use fallback method
if (jsonBlockIndex <= nextBlockIndex)
{
AssignNextIndex(blockName);

// Cache the full DFBlock
if (!blocks.ContainsKey(blockKey))
blocks[blockKey] = dfBlock.Value;
Copy link
Contributor

@TwelveEyes TwelveEyes Jun 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cached DFBlock for the AssignNextIndex fallback also needs to have it's internal Index value updated. What I would recommend:

diff --git a/Assets/Scripts/Utility/AssetInjection/WorldDataReplacement.cs b/Assets/Scripts/Utility/AssetInjection/WorldDataReplacement.cs
index 92ddee749..73875e6e4 100644
--- a/Assets/Scripts/Utility/AssetInjection/WorldDataReplacement.cs
+++ b/Assets/Scripts/Utility/AssetInjection/WorldDataReplacement.cs
@@ -627,11 +627,19 @@ namespace DaggerfallWorkshop.Utility.AssetInjection
             // If jsonBlockIndex is invalid (less than or equal to nextBlockIndex), use fallback method
             if (jsonBlockIndex <= nextBlockIndex)
             {
+                int newIndex = nextBlockIndex;
+
                 AssignNextIndex(blockName);
 
                 // Cache the full DFBlock
                 if (!blocks.ContainsKey(blockKey))
-                    blocks[blockKey] = dfBlock.Value;
+                {
+                    // Update DFBlock index
+                    DFBlock block = dfBlock.Value;
+                    block.Index = newIndex;
+
+                    blocks[blockKey] = block;
+                }
 
                 return;
             }

Otherwise AssignBlockData() will potentially pull the wrong data for RMBs assigned an index through the AssignNextIndex() fallback, causing the player to enter the wrong interior, or throwing an exception because the door.resourceIndex is outside the bounds of the RMB's SubRecords.

This bug is clearly visible here with Cliffworm's release version of Lively Cities.

For more insight I'll use the mod Village of Fishguard by dotWayton to demonstrate the bug step by step.
Here you can see Fishguard's RMBs are both set to index 308 within their JSON files.
2025-06-11_03-13
2025-06-11_03-13_1

And here we can see Fishguard's RMBs have been assigned new indexes by AssignNextIndex():
2025-06-11_03-19

Attempting to enter a house in FISHER03.RMB results in this:
2025-06-11_03-24

We can see AssignBlockData() grabbed two RMBs for our location, FISHER02.RMB and FISHER03.RMB (lines 4531 and 4532). And we can immediately see three things are wrong upon it trying to load the record data (on line 4533): it is pulling from FISHER02.RMB instead of FISHER03.RMB, the Block Index and the Door BlockIndex are both wrong (308 instead of 1296), and the Door RecordIndex far exceeds the Block SubRecord length (which causes the exception on line 4534). This is because the internal index value for the block wasn't updated, so the door is still using the old block index of 308, and since FISHER02.RMB is the first block that matches the door's block index of 308 it gets selected.

After the change in the diff above was applied:
2025-06-11_03-51

It's now pulling from the correct block FISHER03.RMB instead of FISHER02.RMB, with the correct Block Index and Door BlockIndex of 1297 instead of 308, and with the Door RecordIndex correctly within the bounds of the Block SubRecords length.


return;
}

// Add to cache
newBlockNames[jsonBlockIndex] = blockName;
newBlockIndices[blockName] = jsonBlockIndex;
Copy link
Contributor

@TwelveEyes TwelveEyes Jun 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably want to add a fallback to AssignNextIndex here too in case there are already entries for the keys jsonBlockIndex and blockName. This way two different modded RMBs with the same custom index (by accident) can still get along.

Debug.LogFormat("Found a new DFBlock: {0}, (assigned index: {1})", blockName, jsonBlockIndex);

// Cache the full DFBlock
if (!blocks.ContainsKey(blockKey))
blocks[blockKey] = dfBlock.Value;
}

private static void AssignNextIndex(string blockName)
{
newBlockNames[nextBlockIndex] = blockName;
Expand Down