Files
MovementTests/addons/forge/editor/AssetRepairTool.cs
Minimata c4be97e0de
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 21s
Create tag and build when new code gets to main / Test (push) Successful in 6m56s
Create tag and build when new code gets to main / Export (push) Successful in 9m3s
added forge addon
2026-02-08 15:16:01 +01:00

211 lines
5.0 KiB
C#

// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using System.Collections.Generic;
using System.Linq;
using Gamesmiths.Forge.Godot.Core;
using Gamesmiths.Forge.Godot.Resources;
using Gamesmiths.Forge.Tags;
using Godot;
using Godot.Collections;
namespace Gamesmiths.Forge.Godot.Editor;
[Tool]
public partial class AssetRepairTool : EditorPlugin
{
public static void RepairAllAssetsTags()
{
ForgeData pluginData = ResourceLoader.Load<ForgeData>("uid://8j4xg16o3qnl");
var tagsManager = new TagsManager([.. pluginData.RegisteredTags]);
List<string> scenes = GetScenePaths("res://");
GD.Print($"Found {scenes.Count} scene(s) to process.");
var openedScenes = EditorInterface.Singleton.GetOpenScenes();
foreach (var originalScenePath in scenes)
{
// For some weird reason scenes from the GetScenePath are coming with 3 slashes instead of just two.
var scenePath = originalScenePath.Replace("res:///", "res://");
GD.Print($"Processing scene: {scenePath}.");
PackedScene? packedScene = ResourceLoader.Load<PackedScene>(scenePath);
if (packedScene is null)
{
GD.PrintErr($"Failed to load scene: {scenePath}.");
continue;
}
Node sceneInstance = packedScene.Instantiate();
var modified = ProcessNode(sceneInstance, tagsManager);
if (!modified)
{
GD.Print($"No changes needed for {scenePath}.");
continue;
}
// 'sceneInstance' is the modified scene instance in memory, need to save to disk and reload if needed.
var newScene = new PackedScene();
Error error = newScene.Pack(sceneInstance);
if (error != Error.Ok)
{
GD.PrintErr($"Failed to pack scene: {error}.");
continue;
}
error = ResourceSaver.Save(newScene, scenePath);
if (error != Error.Ok)
{
GD.PrintErr($"Failed to save scene: {error}.");
continue;
}
if (openedScenes.Contains(scenePath))
{
GD.Print($"Scene was opened, reloading background scene: {scenePath}.");
EditorInterface.Singleton.ReloadSceneFromPath(scenePath);
}
}
}
/// <summary>
/// Recursively get scene files from a folder.
/// </summary>
/// <param name="basePath">Current path iteration.</param>
/// <returns>List of scenes found.</returns>
private static List<string> GetScenePaths(string basePath)
{
var scenePaths = new List<string>();
var dir = DirAccess.Open(basePath);
if (dir is null)
{
GD.PrintErr($"Failed to open directory: {basePath}");
return scenePaths;
}
// Start listing directory entries; skip navigational and hidden files.
dir.ListDirBegin();
while (true)
{
var fileName = dir.GetNext();
if (string.IsNullOrEmpty(fileName))
{
break;
}
var filePath = $"{basePath}/{fileName}";
if (dir.CurrentIsDir())
{
// Recursively scan subdirectories.
scenePaths.AddRange(GetScenePaths(filePath));
}
else if (fileName.EndsWith(".tscn", StringComparison.InvariantCultureIgnoreCase)
|| fileName.EndsWith(".scn", StringComparison.InvariantCultureIgnoreCase))
{
scenePaths.Add(filePath);
}
}
dir.ListDirEnd();
return scenePaths;
}
/// <summary>
/// Recursively process nodes; returns true if any ForgeEntity was modified.
/// </summary>
/// <param name="node">Current node iteration.</param>
/// <param name="tagsManager">The tags manager used to validate tags.</param>
/// <returns><see langword="true"/> if any ForgeEntity was modified.</returns>
private static bool ProcessNode(Node node, TagsManager tagsManager)
{
var modified = ValidateNode(node, tagsManager);
foreach (Node child in node.GetChildren())
{
modified |= ProcessNode(child, tagsManager);
}
return modified;
}
private static bool ValidateNode(Node node, TagsManager tagsManager)
{
var modified = false;
foreach (Dictionary propertyInfo in node.GetPropertyList())
{
if (!propertyInfo.TryGetValue("class_name", out Variant className))
{
continue;
}
if (className.AsString() != "TagContainer")
{
continue;
}
if (!propertyInfo.TryGetValue("name", out Variant nameObj))
{
continue;
}
var propertyName = nameObj.AsString();
Variant value = node.Get(propertyName);
if (value.VariantType != Variant.Type.Object)
{
continue;
}
if (value.As<Resource>() is ForgeTagContainer tagContainer)
{
modified |= ValidateTagContainerProperty(tagContainer, node.Name, tagsManager);
}
}
return modified;
}
private static bool ValidateTagContainerProperty(
ForgeTagContainer container,
string nodeName,
TagsManager tagsManager)
{
if (container.ContainerTags is null)
{
return false;
}
Array<string> originalTags = container.ContainerTags;
var newTags = new Array<string>();
var modified = false;
foreach (var tag in originalTags)
{
try
{
Tag.RequestTag(tagsManager, tag);
newTags.Add(tag);
}
catch (TagNotRegisteredException)
{
GD.PrintRich(
$"[color=LIGHT_STEEL_BLUE][RepairTool] Removing invalid tag [{tag}] from node {nodeName}.");
modified = true;
}
}
if (modified)
{
container.ContainerTags = newTags;
}
return modified;
}
}
#endif