Compare commits
	
		
			7 Commits
		
	
	
		
			d3dd40eec3
			...
			236cf3b317
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
								 | 
						236cf3b317 | |
| 
							
							
								
								 | 
						12a8b4f06d | |
| 
							
							
								
								 | 
						63daccb3c1 | |
| 
							
							
								
								 | 
						b223d53824 | |
| 
							
							
								
								 | 
						b89a4d6154 | |
| 
							
							
								
								 | 
						9ae75a693e | |
| 
							
							
								
								 | 
						ef2f87af20 | 
| 
						 | 
					@ -15,6 +15,10 @@ run/main_scene="res://project/scenes/main.tscn"
 | 
				
			||||||
config/features=PackedStringArray("4.3", "C#", "Forward Plus")
 | 
					config/features=PackedStringArray("4.3", "C#", "Forward Plus")
 | 
				
			||||||
config/icon="res://icon.svg"
 | 
					config/icon="res://icon.svg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[autoload]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Context="*res://project/code/TMV/Context.cs"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dotnet]
 | 
					[dotnet]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
project/assembly_name="Thief Mission Viewer"
 | 
					project/assembly_name="Thief Mission Viewer"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.IO;
 | 
					using System.IO;
 | 
				
			||||||
using System.IO.Compression;
 | 
					using System.IO.Compression;
 | 
				
			||||||
| 
						 | 
					@ -5,7 +6,14 @@ using System.Linq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace KeepersCompound.LGS;
 | 
					namespace KeepersCompound.LGS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: Merge the two versions of GetXXX and handle null campaign string as OM
 | 
					public enum ResourceType
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    Mission,
 | 
				
			||||||
 | 
					    Object,
 | 
				
			||||||
 | 
					    ObjectTexture,
 | 
				
			||||||
 | 
					    Texture,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class ResourcePathManager
 | 
					public class ResourcePathManager
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private record CampaignResources
 | 
					    private record CampaignResources
 | 
				
			||||||
| 
						 | 
					@ -14,6 +22,33 @@ public class ResourcePathManager
 | 
				
			||||||
        public Dictionary<string, string> texturePathMap;
 | 
					        public Dictionary<string, string> texturePathMap;
 | 
				
			||||||
        public Dictionary<string, string> objectPathMap;
 | 
					        public Dictionary<string, string> objectPathMap;
 | 
				
			||||||
        public Dictionary<string, string> objectTexturePathMap;
 | 
					        public Dictionary<string, string> objectTexturePathMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public List<string> GetResourceNames(ResourceType type)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            List<string> keys = type switch
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ResourceType.Mission => [.. missionPathMap.Keys],
 | 
				
			||||||
 | 
					                ResourceType.Object => [.. objectPathMap.Keys],
 | 
				
			||||||
 | 
					                ResourceType.ObjectTexture => [.. objectTexturePathMap.Keys],
 | 
				
			||||||
 | 
					                ResourceType.Texture => [.. texturePathMap.Keys],
 | 
				
			||||||
 | 
					                _ => throw new ArgumentOutOfRangeException(nameof(type)),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            keys.Sort();
 | 
				
			||||||
 | 
					            return keys;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public string GetResourcePath(ResourceType type, string name)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var map = type switch
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ResourceType.Mission => missionPathMap,
 | 
				
			||||||
 | 
					                ResourceType.Object => objectPathMap,
 | 
				
			||||||
 | 
					                ResourceType.ObjectTexture => objectTexturePathMap,
 | 
				
			||||||
 | 
					                ResourceType.Texture => texturePathMap,
 | 
				
			||||||
 | 
					                _ => throw new ArgumentOutOfRangeException(nameof(type)),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            return map.TryGetValue(name, out var resourcePath) ? resourcePath : null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private bool _initialised = false;
 | 
					    private bool _initialised = false;
 | 
				
			||||||
| 
						 | 
					@ -102,127 +137,57 @@ public class ResourcePathManager
 | 
				
			||||||
        return names;
 | 
					        return names;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public string GetMissionPath(string campaignName, string missionName)
 | 
					    public List<string> GetResourceNames(ResourceType type, string campaignName)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (!_initialised) return null;
 | 
					        if (!_initialised)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            throw new InvalidOperationException("Resource Path Manager hasn't been initialised.");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (campaignName == null || campaignName == "")
 | 
					        if (campaignName == null || campaignName == "")
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (_omResources.missionPathMap.TryGetValue(missionName, out var omPath))
 | 
					            return _omResources.GetResourceNames(type);
 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return omPath;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (
 | 
					 | 
				
			||||||
            _fmResources.TryGetValue(campaignName, out var campaign) &&
 | 
					 | 
				
			||||||
            campaign.missionPathMap.TryGetValue(missionName, out var fmPath))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return fmPath;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public List<string> GetMissionNames(string campaignName)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (!_initialised) return null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (campaignName == null || campaignName == "")
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var names = new List<string>(_omResources.missionPathMap.Keys);
 | 
					 | 
				
			||||||
            names.Sort();
 | 
					 | 
				
			||||||
            return names;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (_fmResources.TryGetValue(campaignName, out var campaign))
 | 
					        else if (_fmResources.TryGetValue(campaignName, out var campaign))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var names = new List<string>(campaign.missionPathMap.Keys);
 | 
					            return campaign.GetResourceNames(type);
 | 
				
			||||||
            names.Sort();
 | 
					 | 
				
			||||||
            return names;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return null;
 | 
					
 | 
				
			||||||
 | 
					        throw new ArgumentException("No campaign found with given name", nameof(campaignName));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: OMs fail to find a path, but FMs using OM textures do  find them
 | 
					    public (string, string) GetResourcePath(
 | 
				
			||||||
    // The OMs still fail even if I put them in a folder with FMs that work=??
 | 
					        ResourceType type,
 | 
				
			||||||
    public string GetTexturePath(string campaignName, string textureName)
 | 
					        string campaignName,
 | 
				
			||||||
 | 
					        string resourceName)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (!_initialised) return null;
 | 
					        if (!_initialised)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        textureName = textureName.ToLower();
 | 
					 | 
				
			||||||
        if (campaignName == null || campaignName == "")
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (_omResources.texturePathMap.TryGetValue(textureName, out var path))
 | 
					            throw new InvalidOperationException("Resource Path Manager hasn't been initialised.");
 | 
				
			||||||
            {
 | 
					        }
 | 
				
			||||||
                return path;
 | 
					
 | 
				
			||||||
            }
 | 
					        resourceName = resourceName.ToLower();
 | 
				
			||||||
 | 
					        var omResourcePath = _omResources.GetResourcePath(type, resourceName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (campaignName == null || campaignName == "" && omResourcePath != null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return ("", omResourcePath);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (_fmResources.TryGetValue(campaignName, out var campaign))
 | 
					        else if (_fmResources.TryGetValue(campaignName, out var campaign))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (campaign.texturePathMap.TryGetValue(textureName, out var fmPath))
 | 
					            var fmResourcePath = campaign.GetResourcePath(type, resourceName);
 | 
				
			||||||
 | 
					            if (fmResourcePath != null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return fmPath;
 | 
					                return (campaignName, fmResourcePath);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else if (_omResources.texturePathMap.TryGetValue(textureName, out var omPath))
 | 
					            else if (omResourcePath != null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return omPath;
 | 
					                return ("", omResourcePath);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return null;
 | 
					        // throw new ArgumentException($"No resource found with given type and name: {type}, {resourceName}", nameof(resourceName));
 | 
				
			||||||
    }
 | 
					        return (null, null);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public string GetObjectPath(ref string campaignName, string objectName)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (!_initialised) return null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        objectName = objectName.ToLower();
 | 
					 | 
				
			||||||
        if (campaignName == null || campaignName == "")
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (_omResources.objectPathMap.TryGetValue(objectName, out var omPath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return omPath;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (_fmResources.TryGetValue(campaignName, out var campaign))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (campaign.objectPathMap.TryGetValue(objectName, out var fmPath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return fmPath;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else if (_omResources.objectPathMap.TryGetValue(objectName, out var omPath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                campaignName = "";
 | 
					 | 
				
			||||||
                return omPath;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public string GetObjectTexturePath(string campaignName, string textureName)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (!_initialised) return null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        textureName = Path.GetFileNameWithoutExtension(textureName).ToLower();
 | 
					 | 
				
			||||||
        if (campaignName == null || campaignName == "")
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (_omResources.objectTexturePathMap.TryGetValue(textureName, out var path))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return path;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (_fmResources.TryGetValue(campaignName, out var campaign))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (campaign.objectTexturePathMap.TryGetValue(textureName, out var fmPath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return fmPath;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else if (_omResources.objectTexturePathMap.TryGetValue(textureName, out var omPath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return omPath;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return null;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static Dictionary<string, string> GetObjectTexturePaths(string root)
 | 
					    private static Dictionary<string, string> GetObjectTexturePaths(string root)
 | 
				
			||||||
| 
						 | 
					@ -253,7 +218,6 @@ public class ResourcePathManager
 | 
				
			||||||
        return pathMap;
 | 
					        return pathMap;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: Handle object textures?
 | 
					 | 
				
			||||||
    private static Dictionary<string, string> GetTexturePaths(string root)
 | 
					    private static Dictionary<string, string> GetTexturePaths(string root)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        string[] validExtensions = { ".dds", ".png", ".tga", ".pcx", ".gif", ".bmp", ".cel", };
 | 
					        string[] validExtensions = { ".dds", ".png", ".tga", ".pcx", ".gif", ".bmp", ".cel", };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					using Godot;
 | 
				
			||||||
 | 
					using KeepersCompound.LGS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace KeepersCompound.TMV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public partial class Context : Node
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public static Context Instance { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public ResourcePathManager PathManager { get; private set; }
 | 
				
			||||||
 | 
					    public ModelLoader ModelLoader { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public override void _Ready()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var extractPath = ProjectSettings.GlobalizePath($"user://extracted/tmp");
 | 
				
			||||||
 | 
					        PathManager = new ResourcePathManager(extractPath);
 | 
				
			||||||
 | 
					        ModelLoader = new ModelLoader();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Instance = this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,57 +0,0 @@
 | 
				
			||||||
using System.IO;
 | 
					 | 
				
			||||||
using Godot;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace KeepersCompound.TMV;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// TODO: Error handling lol
 | 
					 | 
				
			||||||
public class InstallPaths
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public string rootPath;
 | 
					 | 
				
			||||||
    public string famPath;
 | 
					 | 
				
			||||||
    public string objPath;
 | 
					 | 
				
			||||||
    public string omsPath;
 | 
					 | 
				
			||||||
    public string fmsPath;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public InstallPaths(string root)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var searchOptions = new EnumerationOptions
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            MatchCasing = MatchCasing.CaseInsensitive,
 | 
					 | 
				
			||||||
            RecurseSubdirectories = true
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        rootPath = root;
 | 
					 | 
				
			||||||
        famPath = Directory.GetFiles(rootPath, "fam.crf", searchOptions)[0];
 | 
					 | 
				
			||||||
        objPath = Directory.GetFiles(rootPath, "obj.crf", searchOptions)[0];
 | 
					 | 
				
			||||||
        var paths = Directory.GetFiles(rootPath, "install.cfg", searchOptions);
 | 
					 | 
				
			||||||
        if (paths.Length == 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            paths = Directory.GetFiles(rootPath, "darkinst.cfg", searchOptions);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        var installCfgPath = paths[0];
 | 
					 | 
				
			||||||
        GD.Print($"Install.cfg: {installCfgPath}");
 | 
					 | 
				
			||||||
        foreach (var line in File.ReadLines(installCfgPath))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (line.StartsWith("load_path"))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var path = line.Split(" ")[1].Replace("\\", "/");
 | 
					 | 
				
			||||||
                omsPath = Path.GetFullPath(rootPath + path);
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var camModPath = Directory.GetFiles(rootPath, "cam_mod.ini", searchOptions)[0];
 | 
					 | 
				
			||||||
        fmsPath = rootPath + "/FMs";
 | 
					 | 
				
			||||||
        foreach (var line in File.ReadLines(camModPath))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (line.StartsWith("fm_path"))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var path = line.Split(" ")[1].Replace("\\", "/");
 | 
					 | 
				
			||||||
                fmsPath = Path.GetFullPath(rootPath + path);
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        GD.Print($"OMs Path: {omsPath}");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -41,18 +41,13 @@ public partial class Mission : Node3D
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	string _campaignName;
 | 
						string _campaignName;
 | 
				
			||||||
	string _missionName;
 | 
						string _missionName;
 | 
				
			||||||
	ResourcePathManager _installPaths;
 | 
					 | 
				
			||||||
	DbFile _file;
 | 
						DbFile _file;
 | 
				
			||||||
	TextureLoader _textureLoader;
 | 
						TextureLoader _textureLoader;
 | 
				
			||||||
	ModelLoader _modelLoader;
 | 
					 | 
				
			||||||
	List<ShaderMaterial> _materials;
 | 
						List<ShaderMaterial> _materials;
 | 
				
			||||||
	Vector2I _lmLayerMask;
 | 
						Vector2I _lmLayerMask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public override void _Ready()
 | 
						public override void _Ready()
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		var extractPath = ProjectSettings.GlobalizePath($"user://extracted/tmp");
 | 
					 | 
				
			||||||
		_installPaths = new ResourcePathManager(extractPath);
 | 
					 | 
				
			||||||
		_modelLoader = new ModelLoader(_installPaths);
 | 
					 | 
				
			||||||
		_materials = new List<ShaderMaterial>();
 | 
							_materials = new List<ShaderMaterial>();
 | 
				
			||||||
		_lmLayerMask = new Vector2I(~0, ~0);
 | 
							_lmLayerMask = new Vector2I(~0, ~0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,12 +55,17 @@ public partial class Mission : Node3D
 | 
				
			||||||
		lightmapToggler.Setup(this);
 | 
							lightmapToggler.Setup(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var missionSelector = GetNode<Control>("%MissionSelector") as MissionSelector;
 | 
							var missionSelector = GetNode<Control>("%MissionSelector") as MissionSelector;
 | 
				
			||||||
		missionSelector.pathManager = _installPaths;
 | 
					 | 
				
			||||||
		missionSelector.MissionSelected += (string campaign, string mission) =>
 | 
							missionSelector.MissionSelected += (string campaign, string mission) =>
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			_campaignName = campaign;
 | 
								_campaignName = campaign;
 | 
				
			||||||
			_missionName = mission;
 | 
								_missionName = mission;
 | 
				
			||||||
			FileName = _installPaths.GetMissionPath(campaign, mission);
 | 
								var pathManager = Context.Instance.PathManager;
 | 
				
			||||||
 | 
								var (newCampaign, missionPath) = pathManager.GetResourcePath(ResourceType.Mission, campaign, mission);
 | 
				
			||||||
 | 
								if (newCampaign != campaign)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									GD.Print($"Didn't find campaign mission: ({campaign}, {mission})");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								FileName = missionPath;
 | 
				
			||||||
			Build = true;
 | 
								Build = true;
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -115,7 +115,7 @@ public partial class Mission : Node3D
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		ClearMap();
 | 
							ClearMap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_textureLoader = new TextureLoader(_installPaths, _campaignName);
 | 
							_textureLoader = new TextureLoader(_campaignName);
 | 
				
			||||||
		Timing.TimeStage("DbFile Parse", () => _file = new(FileName));
 | 
							Timing.TimeStage("DbFile Parse", () => _file = new(FileName));
 | 
				
			||||||
		Timing.TimeStage("Register Textures", () => UseChunk<TxList>("TXLIST", RegisterTextures));
 | 
							Timing.TimeStage("Register Textures", () => UseChunk<TxList>("TXLIST", RegisterTextures));
 | 
				
			||||||
		Timing.TimeStage("Build WR", () => UseChunk<WorldRep>("WREXT", BuildWrMeshes));
 | 
							Timing.TimeStage("Build WR", () => UseChunk<WorldRep>("WREXT", BuildWrMeshes));
 | 
				
			||||||
| 
						 | 
					@ -249,7 +249,10 @@ public partial class Mission : Node3D
 | 
				
			||||||
			var rawRot = brush.angle;
 | 
								var rawRot = brush.angle;
 | 
				
			||||||
			var rot = new Vector3(rawRot.Y, rawRot.Z, rawRot.X) * 360 / ushort.MaxValue;
 | 
								var rot = new Vector3(rawRot.Y, rawRot.Z, rawRot.X) * 360 / ushort.MaxValue;
 | 
				
			||||||
			var scale = scaleProp == null ? Vector3.One : scaleProp.scale.ToGodotVec3(false);
 | 
								var scale = scaleProp == null ? Vector3.One : scaleProp.scale.ToGodotVec3(false);
 | 
				
			||||||
			var model = Timing.TimeStage("Get Models", () => { return _modelLoader.Load(_campaignName, modelName); });
 | 
								var model = Timing.TimeStage("Get Models", () =>
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									return Context.Instance.ModelLoader.Load(_campaignName, modelName);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
			if (model != null)
 | 
								if (model != null)
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
				model.Position = pos;
 | 
									model.Position = pos;
 | 
				
			||||||
| 
						 | 
					@ -265,14 +268,19 @@ public partial class Mission : Node3D
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					path = prop.value;
 | 
										path = prop.value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										var pathManager = Context.Instance.PathManager;
 | 
				
			||||||
					if (path.StartsWith("fam", StringComparison.OrdinalIgnoreCase))
 | 
										if (path.StartsWith("fam", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
						path = _installPaths.GetTexturePath(_campaignName, path);
 | 
											var resType = ResourceType.Texture;
 | 
				
			||||||
						return path != null;
 | 
											path = pathManager.GetResourcePath(resType, _campaignName, prop.value).Item2;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										else
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											var resType = ResourceType.ObjectTexture;
 | 
				
			||||||
 | 
											var resName = Path.GetFileNameWithoutExtension(prop.value);
 | 
				
			||||||
 | 
											path = pathManager.GetResourcePath(resType, _campaignName, resName).Item2;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					 | 
				
			||||||
					// gonna assume obj lol
 | 
					 | 
				
			||||||
					path = _installPaths.GetObjectTexturePath(_campaignName, path);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
					return path != null;
 | 
										return path != null;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,4 @@
 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.IO;
 | 
					 | 
				
			||||||
using Godot;
 | 
					using Godot;
 | 
				
			||||||
using KeepersCompound.LGS;
 | 
					 | 
				
			||||||
using KeepersCompound.TMV.UI;
 | 
					using KeepersCompound.TMV.UI;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace KeepersCompound.TMV;
 | 
					namespace KeepersCompound.TMV;
 | 
				
			||||||
| 
						 | 
					@ -11,104 +8,17 @@ public partial class Model : Node3D
 | 
				
			||||||
    public override void _Ready()
 | 
					    public override void _Ready()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var modelSelector = GetNode<Control>("%ModelSelector") as ModelSelector;
 | 
					        var modelSelector = GetNode<Control>("%ModelSelector") as ModelSelector;
 | 
				
			||||||
        modelSelector.LoadModel += BuildModel;
 | 
					        modelSelector.ModelSelected += BuildModel;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void BuildModel(string rootPath, string modelPath)
 | 
					    public void BuildModel(string campaignName, string modelPath)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        foreach (var node in GetChildren())
 | 
					        foreach (var node in GetChildren())
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            node.QueueFree();
 | 
					            node.QueueFree();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var modelFile = new ModelFile(modelPath);
 | 
					        var model = Context.Instance.ModelLoader.Load(campaignName, modelPath);
 | 
				
			||||||
        if (modelFile == null)
 | 
					        AddChild(model);
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            GD.Print($"Failed to load model file: {modelPath}");
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // TODO: Remove this disgusting hack. Not only is it a hack, it doesn't support custom models
 | 
					 | 
				
			||||||
        var baseDir = Path.GetDirectoryName(modelPath);
 | 
					 | 
				
			||||||
        var options = new EnumerationOptions
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            MatchCasing = MatchCasing.CaseInsensitive,
 | 
					 | 
				
			||||||
            RecurseSubdirectories = true,
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        var materials = new List<StandardMaterial3D>();
 | 
					 | 
				
			||||||
        foreach (var material in modelFile.Materials)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (material.Type == 0)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var paths = Directory.GetFiles(baseDir, material.Name, options);
 | 
					 | 
				
			||||||
                if (paths.IsEmpty()) continue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                materials.Add(new StandardMaterial3D
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    AlbedoTexture = TextureLoader.LoadTexture(paths[0])
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var b = (material.Handle) & 0xff;
 | 
					 | 
				
			||||||
                var g = (material.Handle >> 8) & 0xff;
 | 
					 | 
				
			||||||
                var r = (material.Handle >> 16) & 0xff;
 | 
					 | 
				
			||||||
                var colour = new Color(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f);
 | 
					 | 
				
			||||||
                materials.Add(new StandardMaterial3D
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    AlbedoColor = colour
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var surfaceDataMap = new Dictionary<int, MeshSurfaceData>();
 | 
					 | 
				
			||||||
        foreach (var poly in modelFile.Polygons)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var vertices = new List<Vector3>();
 | 
					 | 
				
			||||||
            var normal = modelFile.Normals[poly.Normal].ToGodotVec3();
 | 
					 | 
				
			||||||
            var uvs = new List<Vector2>();
 | 
					 | 
				
			||||||
            for (var i = 0; i < poly.VertexCount; i++)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var vertex = modelFile.Vertices[poly.VertexIndices[i]];
 | 
					 | 
				
			||||||
                vertices.Add(vertex.ToGodotVec3());
 | 
					 | 
				
			||||||
                if (i < poly.UvIndices.Length)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var uv = modelFile.Uvs[poly.UvIndices[i]];
 | 
					 | 
				
			||||||
                    uvs.Add(new Vector2(uv.X, uv.Y));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    uvs.Add(Vector2.Zero);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!surfaceDataMap.ContainsKey(poly.Data))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                surfaceDataMap.Add(poly.Data, new MeshSurfaceData());
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            surfaceDataMap[poly.Data].AddPolygon(vertices, normal, uvs, uvs);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var mesh = new ArrayMesh();
 | 
					 | 
				
			||||||
        foreach (var (materialId, surfaceData) in surfaceDataMap)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var array = surfaceData.BuildSurfaceArray();
 | 
					 | 
				
			||||||
            var surfaceIdx = mesh.GetSurfaceCount();
 | 
					 | 
				
			||||||
            mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array);
 | 
					 | 
				
			||||||
            for (var i = 0; i < materials.Count; i++)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var m = modelFile.Materials[i];
 | 
					 | 
				
			||||||
                if (m.Slot == materialId)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    mesh.SurfaceSetMaterial(surfaceIdx, materials[i]);
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var pos = -modelFile.Header.Center.ToGodotVec3();
 | 
					 | 
				
			||||||
        var meshInstance = new MeshInstance3D { Mesh = mesh, Position = pos };
 | 
					 | 
				
			||||||
        AddChild(meshInstance);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.IO;
 | 
				
			||||||
using Godot;
 | 
					using Godot;
 | 
				
			||||||
using KeepersCompound.LGS;
 | 
					using KeepersCompound.LGS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,14 +7,7 @@ namespace KeepersCompound.TMV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class ModelLoader
 | 
					public class ModelLoader
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private readonly Dictionary<(string, string), MeshInstance3D> _cache;
 | 
					    private readonly Dictionary<(string, string), MeshInstance3D> _cache = new();
 | 
				
			||||||
    private readonly ResourcePathManager _pathManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public ModelLoader(ResourcePathManager pathManager)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        _pathManager = pathManager;
 | 
					 | 
				
			||||||
        _cache = new Dictionary<(string, string), MeshInstance3D>();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public MeshInstance3D Load(string campaignName, string modelName, bool forceLoad = false)
 | 
					    public MeshInstance3D Load(string campaignName, string modelName, bool forceLoad = false)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
| 
						 | 
					@ -32,14 +26,16 @@ public class ModelLoader
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // We don't care if this is null actually, we'll still cache that it's null lol
 | 
					        // We don't care if this is null actually, we'll still cache that it's null lol
 | 
				
			||||||
        var model = Timing.TimeStage("Load Models", () => { return LoadModel(_pathManager, ref campaignName, modelName); });
 | 
					        var model = Timing.TimeStage("Load Models", () => { return LoadModel(ref campaignName, modelName); });
 | 
				
			||||||
        _cache[(campaignName, modelName)] = model;
 | 
					        _cache[(campaignName, modelName)] = model;
 | 
				
			||||||
        return model?.Duplicate() as MeshInstance3D;
 | 
					        return model?.Duplicate() as MeshInstance3D;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static MeshInstance3D LoadModel(ResourcePathManager pathManager, ref string campaignName, string modelName)
 | 
					    public static MeshInstance3D LoadModel(ref string campaignName, string modelName)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var modelPath = pathManager.GetObjectPath(ref campaignName, modelName);
 | 
					        var pathManager = Context.Instance.PathManager;
 | 
				
			||||||
 | 
					        var (newCampaignName, modelPath) = pathManager.GetResourcePath(ResourceType.Object, campaignName, modelName);
 | 
				
			||||||
 | 
					        campaignName = newCampaignName;
 | 
				
			||||||
        if (modelPath == null)
 | 
					        if (modelPath == null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return null;
 | 
					            return null;
 | 
				
			||||||
| 
						 | 
					@ -56,7 +52,8 @@ public class ModelLoader
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (material.Type == 0)
 | 
					            if (material.Type == 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var path = pathManager.GetObjectTexturePath(campaignName, material.Name);
 | 
					                var resName = Path.GetFileNameWithoutExtension(material.Name);
 | 
				
			||||||
 | 
					                var (_, path) = pathManager.GetResourcePath(ResourceType.ObjectTexture, campaignName, resName);
 | 
				
			||||||
                if (path == null)
 | 
					                if (path == null)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    path = "user://textures/jorge.png";
 | 
					                    path = "user://textures/jorge.png";
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.IO;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using Godot;
 | 
					using Godot;
 | 
				
			||||||
using KeepersCompound.LGS;
 | 
					using KeepersCompound.LGS;
 | 
				
			||||||
| 
						 | 
					@ -7,16 +8,14 @@ namespace KeepersCompound.TMV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public partial class TextureLoader
 | 
					public partial class TextureLoader
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private readonly ResourcePathManager _pathManager;
 | 
					 | 
				
			||||||
    private readonly string _fmName;
 | 
					    private readonly string _fmName;
 | 
				
			||||||
    private readonly List<ImageTexture> _textureCache = new();
 | 
					    private readonly List<ImageTexture> _textureCache = new();
 | 
				
			||||||
    private readonly Dictionary<int, int> _idMap = new();
 | 
					    private readonly Dictionary<int, int> _idMap = new();
 | 
				
			||||||
    private readonly Dictionary<int, string> _idPathMap = new();
 | 
					    private readonly Dictionary<int, string> _idPathMap = new();
 | 
				
			||||||
    private readonly Dictionary<string, int> _pathMap = new();
 | 
					    private readonly Dictionary<string, int> _pathMap = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public TextureLoader(ResourcePathManager pathManager, string fmName)
 | 
					    public TextureLoader(string fmName)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _pathManager = pathManager;
 | 
					 | 
				
			||||||
        _fmName = fmName;
 | 
					        _fmName = fmName;
 | 
				
			||||||
        LoadDefaultTexture();
 | 
					        LoadDefaultTexture();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -37,7 +36,8 @@ public partial class TextureLoader
 | 
				
			||||||
    private bool Load(int id, string path)
 | 
					    private bool Load(int id, string path)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var loaded = false;
 | 
					        var loaded = false;
 | 
				
			||||||
        string texPath = _pathManager.GetTexturePath(_fmName, path);
 | 
					        var pathManager = Context.Instance.PathManager;
 | 
				
			||||||
 | 
					        var (_, texPath) = pathManager.GetResourcePath(ResourceType.Texture, _fmName, path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (texPath != null)
 | 
					        if (texPath != null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,8 +8,6 @@ public partial class MissionSelector : Control
 | 
				
			||||||
	public event MissionSelectedEventHandler MissionSelected;
 | 
						public event MissionSelectedEventHandler MissionSelected;
 | 
				
			||||||
	public delegate void MissionSelectedEventHandler(string campaign, string mission);
 | 
						public delegate void MissionSelectedEventHandler(string campaign, string mission);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public ResourcePathManager pathManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private FileDialog _FolderSelect;
 | 
						private FileDialog _FolderSelect;
 | 
				
			||||||
	private LineEdit _FolderPath;
 | 
						private LineEdit _FolderPath;
 | 
				
			||||||
	private Button _BrowseButton;
 | 
						private Button _BrowseButton;
 | 
				
			||||||
| 
						 | 
					@ -52,7 +50,7 @@ public partial class MissionSelector : Control
 | 
				
			||||||
	private void SetInstallPath(string path)
 | 
						private void SetInstallPath(string path)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		_FolderPath.Text = path;
 | 
							_FolderPath.Text = path;
 | 
				
			||||||
		if (pathManager.Init(path))
 | 
							if (Context.Instance.PathManager.Init(path))
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			BuildCampaignList();
 | 
								BuildCampaignList();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -64,6 +62,7 @@ public partial class MissionSelector : Control
 | 
				
			||||||
		_Missions.Clear();
 | 
							_Missions.Clear();
 | 
				
			||||||
		_LoadButton.Disabled = true;
 | 
							_LoadButton.Disabled = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var pathManager = Context.Instance.PathManager;
 | 
				
			||||||
		_Campaigns.AddItem("Original Missions");
 | 
							_Campaigns.AddItem("Original Missions");
 | 
				
			||||||
		foreach (var campaign in pathManager.GetCampaignNames())
 | 
							foreach (var campaign in pathManager.GetCampaignNames())
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
| 
						 | 
					@ -76,8 +75,9 @@ public partial class MissionSelector : Control
 | 
				
			||||||
		_Missions.Clear();
 | 
							_Missions.Clear();
 | 
				
			||||||
		_LoadButton.Disabled = true;
 | 
							_LoadButton.Disabled = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var campaignName = _Campaigns.GetItemText((int)idx);
 | 
							var pathManager = Context.Instance.PathManager;
 | 
				
			||||||
		var missionNames = pathManager.GetMissionNames(idx == 0 ? null : campaignName);
 | 
							var campaignName = idx == 0 ? null : _Campaigns.GetItemText((int)idx);
 | 
				
			||||||
 | 
							var missionNames = pathManager.GetResourceNames(ResourceType.Mission, campaignName);
 | 
				
			||||||
		foreach (var mission in missionNames)
 | 
							foreach (var mission in missionNames)
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			_Missions.AddItem(mission);
 | 
								_Missions.AddItem(mission);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,20 +1,18 @@
 | 
				
			||||||
using System.IO;
 | 
					 | 
				
			||||||
using System.IO.Compression;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					 | 
				
			||||||
using Godot;
 | 
					using Godot;
 | 
				
			||||||
 | 
					using KeepersCompound.LGS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace KeepersCompound.TMV.UI;
 | 
					namespace KeepersCompound.TMV.UI;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public partial class ModelSelector : Control
 | 
					public partial class ModelSelector : Control
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	[Signal]
 | 
					 | 
				
			||||||
	public delegate void LoadModelEventHandler(string rootPath, string modelPath);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private InstallPaths _installPaths;
 | 
					{
 | 
				
			||||||
 | 
						public event ModelSelectedEventHandler ModelSelected;
 | 
				
			||||||
 | 
						public delegate void ModelSelectedEventHandler(string campaign, string mission);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private FileDialog _FolderSelect;
 | 
						private FileDialog _FolderSelect;
 | 
				
			||||||
	private LineEdit _FolderPath;
 | 
						private LineEdit _FolderPath;
 | 
				
			||||||
	private Button _BrowseButton;
 | 
						private Button _BrowseButton;
 | 
				
			||||||
 | 
						private ItemList _Campaigns;
 | 
				
			||||||
	private ItemList _Models;
 | 
						private ItemList _Models;
 | 
				
			||||||
	private Button _LoadButton;
 | 
						private Button _LoadButton;
 | 
				
			||||||
	private Button _CancelButton;
 | 
						private Button _CancelButton;
 | 
				
			||||||
| 
						 | 
					@ -28,13 +26,15 @@ public partial class ModelSelector : Control
 | 
				
			||||||
		_FolderSelect = GetNode<FileDialog>("%FolderSelect");
 | 
							_FolderSelect = GetNode<FileDialog>("%FolderSelect");
 | 
				
			||||||
		_FolderPath = GetNode<LineEdit>("%FolderPath");
 | 
							_FolderPath = GetNode<LineEdit>("%FolderPath");
 | 
				
			||||||
		_BrowseButton = GetNode<Button>("%BrowseButton");
 | 
							_BrowseButton = GetNode<Button>("%BrowseButton");
 | 
				
			||||||
 | 
							_Campaigns = GetNode<ItemList>("%Campaigns");
 | 
				
			||||||
		_Models = GetNode<ItemList>("%Models");
 | 
							_Models = GetNode<ItemList>("%Models");
 | 
				
			||||||
		_LoadButton = GetNode<Button>("%LoadButton");
 | 
							_LoadButton = GetNode<Button>("%LoadButton");
 | 
				
			||||||
		_CancelButton = GetNode<Button>("%CancelButton");
 | 
							_CancelButton = GetNode<Button>("%CancelButton");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_BrowseButton.Pressed += () => _FolderSelect.Visible = true;
 | 
							_BrowseButton.Pressed += () => _FolderSelect.Visible = true;
 | 
				
			||||||
		_FolderSelect.DirSelected += (string dir) => { _FolderPath.Text = dir; BuildModelList(dir); };
 | 
							_FolderSelect.DirSelected += SetInstallPath;
 | 
				
			||||||
		_FolderPath.TextSubmitted += BuildModelList;
 | 
							_FolderPath.TextSubmitted += SetInstallPath;
 | 
				
			||||||
 | 
							_Campaigns.ItemSelected += BuildModelList;
 | 
				
			||||||
		_Models.ItemSelected += (long _) => _LoadButton.Disabled = false;
 | 
							_Models.ItemSelected += (long _) => _LoadButton.Disabled = false;
 | 
				
			||||||
		_LoadButton.Pressed += EmitLoadModel;
 | 
							_LoadButton.Pressed += EmitLoadModel;
 | 
				
			||||||
		_CancelButton.Pressed += () => Visible = false;
 | 
							_CancelButton.Pressed += () => Visible = false;
 | 
				
			||||||
| 
						 | 
					@ -51,44 +51,57 @@ public partial class ModelSelector : Control
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void BuildModelList(string path)
 | 
						private void SetInstallPath(string path)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		_installPaths = new InstallPaths(path);
 | 
							_FolderPath.Text = path;
 | 
				
			||||||
		ExtractObjFiles();
 | 
							if (Context.Instance.PathManager.Init(path))
 | 
				
			||||||
 | 
					 | 
				
			||||||
		_Models.Clear();
 | 
					 | 
				
			||||||
		_LoadButton.Disabled = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var paths = Directory.GetFiles(_extractedObjectsPath, "*.bin", SearchOption.AllDirectories);
 | 
					 | 
				
			||||||
		foreach (var m in paths.OrderBy(s => s))
 | 
					 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			_Models.AddItem(m.TrimPrefix(_extractedObjectsPath));
 | 
								BuildCampaignList();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: Move this to a resource manager
 | 
						private void BuildCampaignList()
 | 
				
			||||||
	private void ExtractObjFiles()
 | 
					 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		var dir = new DirectoryInfo(_extractedObjectsPath);
 | 
							_Campaigns.Clear();
 | 
				
			||||||
		if (dir.Exists)
 | 
							_Models.Clear();
 | 
				
			||||||
		{
 | 
							_LoadButton.Disabled = true;
 | 
				
			||||||
			dir.Delete(true);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var zip = ZipFile.OpenRead(_installPaths.objPath);
 | 
							var pathManager = Context.Instance.PathManager;
 | 
				
			||||||
		zip.ExtractToDirectory(_extractedObjectsPath);
 | 
							_Campaigns.AddItem("Original Missions");
 | 
				
			||||||
 | 
							foreach (var campaign in pathManager.GetCampaignNames())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								_Campaigns.AddItem(campaign);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void BuildModelList(long idx)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							_Models.Clear();
 | 
				
			||||||
 | 
							_LoadButton.Disabled = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var pathManager = Context.Instance.PathManager;
 | 
				
			||||||
 | 
							var campaignName = idx == 0 ? null : _Campaigns.GetItemText((int)idx);
 | 
				
			||||||
 | 
							var modelNames = pathManager.GetResourceNames(ResourceType.Object, campaignName);
 | 
				
			||||||
 | 
							foreach (var model in modelNames)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								_Models.AddItem(model);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void EmitLoadModel()
 | 
						private void EmitLoadModel()
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		var selected = _Models.GetSelectedItems();
 | 
							var campaignIdxs = _Campaigns.GetSelectedItems();
 | 
				
			||||||
		if (selected.IsEmpty())
 | 
							var modelIdxs = _Models.GetSelectedItems();
 | 
				
			||||||
 | 
							if (campaignIdxs.IsEmpty() || modelIdxs.IsEmpty())
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var path = _extractedObjectsPath + _Models.GetItemText(selected[0]);
 | 
							var campaignIdx = campaignIdxs[0];
 | 
				
			||||||
		EmitSignal(SignalName.LoadModel, _installPaths.rootPath, path);
 | 
							var modelIdx = modelIdxs[0];
 | 
				
			||||||
 | 
							var campaignName = campaignIdx == 0 ? null : _Campaigns.GetItemText(campaignIdx);
 | 
				
			||||||
 | 
							var modelName = _Models.GetItemText(modelIdx);
 | 
				
			||||||
 | 
							ModelSelected(campaignName, modelName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Visible = false;
 | 
							Visible = false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,13 +15,15 @@ ssao_enabled = true
 | 
				
			||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.110309, 0.187101, -0.461656)
 | 
					transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.110309, 0.187101, -0.461656)
 | 
				
			||||||
script = ExtResource("1_dax7s")
 | 
					script = ExtResource("1_dax7s")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[node name="Camera3D" type="Camera3D" parent="."]
 | 
					 | 
				
			||||||
script = ExtResource("2_ov7rc")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[node name="UI" type="CanvasLayer" parent="."]
 | 
					[node name="UI" type="CanvasLayer" parent="."]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[node name="ModelSelector" parent="UI" instance=ExtResource("3_ovrmo")]
 | 
					[node name="ModelSelector" parent="UI" instance=ExtResource("3_ovrmo")]
 | 
				
			||||||
unique_name_in_owner = true
 | 
					unique_name_in_owner = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
 | 
					[node name="Environment" type="Node3D" parent="."]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[node name="WorldEnvironment" type="WorldEnvironment" parent="Environment"]
 | 
				
			||||||
environment = SubResource("Environment_e4172")
 | 
					environment = SubResource("Environment_e4172")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[node name="Camera3D" type="Camera3D" parent="Environment"]
 | 
				
			||||||
 | 
					script = ExtResource("2_ov7rc")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -77,6 +77,12 @@ text = "Browse"
 | 
				
			||||||
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
 | 
					[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
 | 
				
			||||||
layout_mode = 2
 | 
					layout_mode = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[node name="Campaigns" type="ItemList" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
 | 
				
			||||||
 | 
					unique_name_in_owner = true
 | 
				
			||||||
 | 
					custom_minimum_size = Vector2(0, 480)
 | 
				
			||||||
 | 
					layout_mode = 2
 | 
				
			||||||
 | 
					size_flags_horizontal = 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[node name="Models" type="ItemList" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
 | 
					[node name="Models" type="ItemList" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
 | 
				
			||||||
unique_name_in_owner = true
 | 
					unique_name_in_owner = true
 | 
				
			||||||
custom_minimum_size = Vector2(0, 480)
 | 
					custom_minimum_size = Vector2(0, 480)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue