diff --git a/CodeWalker.Core/World/Space.cs b/CodeWalker.Core/World/Space.cs index 2c788837d..20257ddd5 100644 --- a/CodeWalker.Core/World/Space.cs +++ b/CodeWalker.Core/World/Space.cs @@ -40,6 +40,9 @@ public class Space public SpaceNodeGrid NodeGrid; private Dictionary AllYnds = new Dictionary(); + private Dictionary DefaultYndEntries = new Dictionary(); + private Dictionary> YndDependentsByArea = new Dictionary>(); + private Dictionary> YndDependencyAreas = new Dictionary>(); public SpaceNavGrid NavGrid; @@ -348,6 +351,9 @@ private void InitNodeGrid() NodeGrid = new SpaceNodeGrid(); AllYnds.Clear(); + DefaultYndEntries.Clear(); + YndDependentsByArea.Clear(); + YndDependencyAreas.Clear(); var rpfman = GameFileCache.RpfMan; Dictionary yndentries = new Dictionary(); @@ -375,6 +381,11 @@ private void InitNodeGrid() } } + foreach (var entry in yndentries) + { + DefaultYndEntries[entry.Key] = entry.Value; + } + Vector3 corner = new Vector3(-8192, -8192, -2048); Vector3 cellsize = new Vector3(512, 512, 4096); @@ -390,11 +401,7 @@ private void InitNodeGrid() if (yndentries.TryGetValue(fnhash, out fentry)) { cell.Ynd = rpfman.GetFile(fentry); - cell.Ynd.BBMin = corner + (cellsize * new Vector3(x, y, 0)); - cell.Ynd.BBMax = cell.Ynd.BBMin + cellsize; - cell.Ynd.CellX = x; - cell.Ynd.CellY = y; - cell.Ynd.Loaded = true; + ConfigureYndForCell(cell.Ynd, x, y); AllYnds[fnhash] = cell.Ynd; @@ -494,11 +501,106 @@ private void InitNodeGrid() //string str = sb.ToString(); } + private void ConfigureYndForCell(YndFile ynd, int x, int y) + { + if (ynd == null) return; + + Vector3 corner = new Vector3(-8192, -8192, -2048); + Vector3 cellsize = new Vector3(512, 512, 4096); + ynd.BBMin = corner + (cellsize * new Vector3(x, y, 0)); + ynd.BBMax = ynd.BBMin + cellsize; + ynd.CellX = x; + ynd.CellY = y; + ynd.Loaded = true; + } + public void PatchYndFile(YndFile ynd) { //ideally we should be able to revert to the vanilla ynd's after closing the project window, //but codewalker can always just be restarted, so who cares really NodeGrid.UpdateYnd(ynd); + UpdateAllYndReference(ynd); + } + + private void UpdateAllYndReference(YndFile ynd, int? previousAreaId = null) + { + if (ynd == null) return; + + if (previousAreaId.HasValue) + { + uint previousHash = JenkHash.GenHash("nodes" + previousAreaId.Value + ".ynd"); + if (AllYnds.TryGetValue(previousHash, out var previousYnd) && (previousYnd == ynd)) + { + AllYnds.Remove(previousHash); + } + } + + uint currentHash = JenkHash.GenHash("nodes" + ynd.AreaID + ".ynd"); + if (AllYnds.TryGetValue(currentHash, out var existingYnd) && (existingYnd != ynd)) + { + UnregisterYndDependencies(existingYnd); + } + AllYnds[currentHash] = ynd; + } + + private void UnregisterYndDependencies(YndFile ynd) + { + if ((ynd == null) || !YndDependencyAreas.TryGetValue(ynd, out var areas)) + { + return; + } + + foreach (var area in areas) + { + if (YndDependentsByArea.TryGetValue(area, out var dependents)) + { + dependents.Remove(ynd); + if (dependents.Count == 0) + { + YndDependentsByArea.Remove(area); + } + } + } + + YndDependencyAreas.Remove(ynd); + } + + private void RegisterYndDependencies(YndFile ynd) + { + UnregisterYndDependencies(ynd); + + if (ynd?.Links == null) + { + return; + } + + var dependencyAreas = new HashSet(); + foreach (var link in ynd.Links) + { + var node2 = link?.Node2; + if (node2 == null) + { + continue; + } + + dependencyAreas.Add(node2.AreaID); + } + + if (dependencyAreas.Count == 0) + { + return; + } + + YndDependencyAreas[ynd] = dependencyAreas; + foreach (var area in dependencyAreas) + { + if (!YndDependentsByArea.TryGetValue(area, out var dependents)) + { + dependents = new HashSet(); + YndDependentsByArea[area] = dependents; + } + dependents.Add(ynd); + } } private void AddRpfYnds(RpfFile rpffile, Dictionary yndentries) @@ -524,7 +626,11 @@ public void BuildYndLinks(YndFile ynd, List tlinks = null, List tlinks = null, List tverts = null) @@ -697,24 +804,60 @@ public void BuildYndData(YndFile ynd, List tverts = null, List GetYndFilesThatDependOnArea(int areaId) + { + if (YndDependentsByArea.TryGetValue(areaId, out var dependents)) + { + return new HashSet(dependents); + } + + return new HashSet(); + } + public HashSet GetYndFilesThatDependOnYndFile(YndFile file) { - HashSet result = new HashSet(); - int targetAreaID = file.AreaID; // Cache to avoid repeated property access + if (file == null) + { + return new HashSet(); + } - foreach (var ynd in AllYnds.Values) + return GetYndFilesThatDependOnArea(file.AreaID); + } + + public YndFile RestoreYndArea(int areaId) + { + var cell = NodeGrid?.GetCell(areaId); + if (cell == null) { - foreach (var link in ynd.Links) - { - if (link.Node2.AreaID == targetAreaID) - { - result.Add(ynd); - break; // No need to check more links for this YndFile - } - } + return null; + } + + uint areaHash = JenkHash.GenHash("nodes" + areaId + ".ynd"); + if (AllYnds.TryGetValue(areaHash, out var existingYnd)) + { + UnregisterYndDependencies(existingYnd); + } + + if (!DefaultYndEntries.TryGetValue(areaHash, out var defaultEntry)) + { + cell.Ynd = null; + AllYnds.Remove(areaHash); + return null; + } + + var defaultYnd = GameFileCache?.RpfMan?.GetFile(defaultEntry); + if (defaultYnd == null) + { + cell.Ynd = null; + AllYnds.Remove(areaHash); + return null; } - return result; + ConfigureYndForCell(defaultYnd, cell.X, cell.Y); + NodeGrid.UpdateYnd(defaultYnd); + AllYnds[areaHash] = defaultYnd; + RegisterYndDependencies(defaultYnd); + return defaultYnd; } public void MoveYndArea(YndFile ynd, int desiredX, int desiredY) @@ -799,6 +942,7 @@ public void MoveYndArea(YndFile ynd, int desiredX, int desiredY) ynd.BuildStructs(); } NodeGrid.UpdateYnd(ynd); + UpdateAllYndReference(ynd, areaIdorig); } public void RecalculateAllYndIndices() diff --git a/CodeWalker/Project/Panels/ProjectExplorerPanel.cs b/CodeWalker/Project/Panels/ProjectExplorerPanel.cs index 2f7cba18b..ce75b4611 100644 --- a/CodeWalker/Project/Panels/ProjectExplorerPanel.cs +++ b/CodeWalker/Project/Panels/ProjectExplorerPanel.cs @@ -2216,6 +2216,31 @@ public void UpdateYbnTreeNode(YbnFile ybn) tn.Text = ybn.RpfFileEntry?.Name ?? ybn.Name; } } + public void AddYndTreeNode(YndFile ynd) + { + if (ynd == null) return; + if (FindYndTreeNode(ynd) != null) return; + if (ProjectTreeView.Nodes.Count <= 0) + { + LoadProjectTree(CurrentProjectFile); + return; + } + + var projnode = ProjectTreeView.Nodes[0]; + var yndsnode = GetChildTreeNode(projnode, "Ynd"); + if (yndsnode == null) + { + yndsnode = projnode.Nodes.Add("Ynd Files"); + yndsnode.Name = "Ynd"; + } + + var changestr = ynd.HasChanged ? "*" : ""; + var name = ynd.RpfFileEntry?.Name ?? ynd.Name; + var yndnode = yndsnode.Nodes.Add(changestr + name); + yndnode.Tag = ynd; + LoadYndTreeNodes(ynd, yndnode); + yndsnode.Expand(); + } public void UpdateYndTreeNode(YndFile ynd) { var tn = FindYndTreeNode(ynd); @@ -2224,6 +2249,31 @@ public void UpdateYndTreeNode(YndFile ynd) tn.Text = ynd.RpfFileEntry?.Name ?? ynd.Name; } } + public void RefreshYndTreeNode(YndFile ynd) + { + var tn = FindYndTreeNode(ynd); + if (tn == null) + { + return; + } + + var wasExpanded = tn.IsExpanded; + var nodesExpanded = GetChildTreeNode(tn, "Nodes")?.IsExpanded ?? false; + var changestr = ynd.HasChanged ? "*" : ""; + var name = ynd.RpfFileEntry?.Name ?? ynd.Name; + + tn.Text = changestr + name; + LoadYndTreeNodes(ynd, tn); + + if (wasExpanded) + { + tn.Expand(); + } + if (nodesExpanded) + { + GetChildTreeNode(tn, "Nodes")?.Expand(); + } + } public void UpdateYnvTreeNode(YnvFile ynv) { var tn = FindYnvTreeNode(ynv); diff --git a/CodeWalker/Project/ProjectForm.cs b/CodeWalker/Project/ProjectForm.cs index 8654d616f..6f4946ede 100644 --- a/CodeWalker/Project/ProjectForm.cs +++ b/CodeWalker/Project/ProjectForm.cs @@ -1383,6 +1383,12 @@ public void CloseProject() { if (CurrentProjectFile == null) return; + var yndAreasToRestore = CurrentProjectFile.YndFiles + .Where(ynd => ynd != null) + .Select(ynd => ynd.AreaID) + .Distinct() + .ToArray(); + foreach (var ymap in CurrentProjectFile.YmapFiles) { if ((ymap != null) && (ymap.HasChanged)) @@ -1522,6 +1528,32 @@ public void CloseProject() if (WorldForm != null) { + if (yndAreasToRestore.Length > 0) + { + lock (WorldForm.RenderSyncRoot) + { + var yndsToRefresh = new HashSet(); + foreach (var areaId in yndAreasToRestore) + { + foreach (var dependent in WorldForm.Space.GetYndFilesThatDependOnArea(areaId)) + { + yndsToRefresh.Add(dependent); + } + + var restoredYnd = WorldForm.Space.RestoreYndArea(areaId); + if (restoredYnd != null) + { + yndsToRefresh.Add(restoredYnd); + } + } + + foreach (var ynd in yndsToRefresh) + { + WorldForm.UpdatePathYndGraphics(ynd, true); + } + } + } + WorldForm.SelectItem(null);//make sure current selected item isn't still selected... } @@ -4718,7 +4750,14 @@ public void AddYndToProject(YndFile ynd) { ynd.HasChanged = true; CurrentProjectFile.HasChanged = true; - LoadProjectTree(); + if ((ProjectExplorer != null) && (ProjectExplorer.CurrentProjectFile == CurrentProjectFile)) + { + ProjectExplorer.AddYndTreeNode(ynd); + } + else + { + LoadProjectTree(); + } } CurrentYndFile = ynd; RefreshUI(); @@ -4807,7 +4846,7 @@ public YndNode NewPathNode(YndNode copy = null, bool copyPosition = false, bool if (selectNew) { - LoadProjectTree(); + ProjectExplorer?.RefreshYndTreeNode(CurrentYndFile); ProjectExplorer?.TrySelectPathNodeTreeNode(n); CurrentPathNode = n; //ShowEditYndPanel(false);; @@ -8782,6 +8821,10 @@ private void LoadYndFromFile(YndFile ynd, string filename) ynd.Load(data); WorldForm.Space.PatchYndFile(ynd); + // Rebuild the imported file immediately so live dependency scans and link rendering + // operate on resolved Node2 references instead of the raw node dictionary only. + WorldForm?.UpdatePathYndGraphics(ynd, true); + if (WorldForm != null) { HashSet updatedFiles = new HashSet();