Compare commits

..

No commits in common. "236cf3b31783f15fb04d5b4a519bcbd08e652eb3" and "d3dd40eec3712930bcdebee22e939e0ff0f51fc9" have entirely different histories.

12 changed files with 318 additions and 186 deletions

View File

@ -15,10 +15,6 @@ 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"

View File

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
@ -6,14 +5,7 @@ using System.Linq;
namespace KeepersCompound.LGS; namespace KeepersCompound.LGS;
public enum ResourceType // TODO: Merge the two versions of GetXXX and handle null campaign string as OM
{
Mission,
Object,
ObjectTexture,
Texture,
}
public class ResourcePathManager public class ResourcePathManager
{ {
private record CampaignResources private record CampaignResources
@ -22,33 +14,6 @@ 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;
@ -137,57 +102,127 @@ public class ResourcePathManager
return names; return names;
} }
public List<string> GetResourceNames(ResourceType type, string campaignName) public string GetMissionPath(string campaignName, string missionName)
{ {
if (!_initialised) if (!_initialised) return null;
{
throw new InvalidOperationException("Resource Path Manager hasn't been initialised.");
}
if (campaignName == null || campaignName == "") if (campaignName == null || campaignName == "")
{ {
return _omResources.GetResourceNames(type); if (_omResources.missionPathMap.TryGetValue(missionName, out var omPath))
{
return omPath;
}
} }
else if (_fmResources.TryGetValue(campaignName, out var campaign)) else if (
_fmResources.TryGetValue(campaignName, out var campaign) &&
campaign.missionPathMap.TryGetValue(missionName, out var fmPath))
{ {
return campaign.GetResourceNames(type); return fmPath;
} }
throw new ArgumentException("No campaign found with given name", nameof(campaignName)); return null;
} }
public (string, string) GetResourcePath( public List<string> GetMissionNames(string campaignName)
ResourceType type,
string campaignName,
string resourceName)
{ {
if (!_initialised) if (!_initialised) return null;
{
throw new InvalidOperationException("Resource Path Manager hasn't been initialised.");
}
resourceName = resourceName.ToLower(); if (campaignName == null || campaignName == "")
var omResourcePath = _omResources.GetResourcePath(type, resourceName);
if (campaignName == null || campaignName == "" && omResourcePath != null)
{ {
return ("", omResourcePath); 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 fmResourcePath = campaign.GetResourcePath(type, resourceName); var names = new List<string>(campaign.missionPathMap.Keys);
if (fmResourcePath != null) names.Sort();
return names;
}
return null;
}
// TODO: OMs fail to find a path, but FMs using OM textures do find them
// The OMs still fail even if I put them in a folder with FMs that work=??
public string GetTexturePath(string campaignName, string textureName)
{
if (!_initialised) return null;
textureName = textureName.ToLower();
if (campaignName == null || campaignName == "")
{
if (_omResources.texturePathMap.TryGetValue(textureName, out var path))
{ {
return (campaignName, fmResourcePath); return path;
} }
else if (omResourcePath != null) }
else if (_fmResources.TryGetValue(campaignName, out var campaign))
{
if (campaign.texturePathMap.TryGetValue(textureName, out var fmPath))
{ {
return ("", omResourcePath); return fmPath;
}
else if (_omResources.texturePathMap.TryGetValue(textureName, out var omPath))
{
return omPath;
} }
} }
// throw new ArgumentException($"No resource found with given type and name: {type}, {resourceName}", nameof(resourceName)); return null;
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)
@ -218,6 +253,7 @@ 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", };

View File

@ -1,21 +0,0 @@
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;
}
}

View File

@ -0,0 +1,57 @@
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}");
}
}

View File

@ -41,13 +41,18 @@ 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);
@ -55,17 +60,12 @@ 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;
var pathManager = Context.Instance.PathManager; FileName = _installPaths.GetMissionPath(campaign, mission);
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(_campaignName); _textureLoader = new TextureLoader(_installPaths, _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,10 +249,7 @@ 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", () => var model = Timing.TimeStage("Get Models", () => { return _modelLoader.Load(_campaignName, modelName); });
{
return Context.Instance.ModelLoader.Load(_campaignName, modelName);
});
if (model != null) if (model != null)
{ {
model.Position = pos; model.Position = pos;
@ -268,20 +265,15 @@ 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))
{ {
var resType = ResourceType.Texture; path = _installPaths.GetTexturePath(_campaignName, path);
path = pathManager.GetResourcePath(resType, _campaignName, prop.value).Item2; return path != null;
}
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;
} }

View File

@ -1,4 +1,7 @@
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;
@ -8,17 +11,104 @@ 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.ModelSelected += BuildModel; modelSelector.LoadModel += BuildModel;
} }
public void BuildModel(string campaignName, string modelPath) public void BuildModel(string rootPath, string modelPath)
{ {
foreach (var node in GetChildren()) foreach (var node in GetChildren())
{ {
node.QueueFree(); node.QueueFree();
} }
var model = Context.Instance.ModelLoader.Load(campaignName, modelPath); var modelFile = new ModelFile(modelPath);
AddChild(model); if (modelFile == null)
{
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);
} }
} }

View File

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Godot; using Godot;
using KeepersCompound.LGS; using KeepersCompound.LGS;
@ -7,7 +6,14 @@ namespace KeepersCompound.TMV;
public class ModelLoader public class ModelLoader
{ {
private readonly Dictionary<(string, string), MeshInstance3D> _cache = new(); private readonly Dictionary<(string, string), MeshInstance3D> _cache;
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)
{ {
@ -26,16 +32,14 @@ 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(ref campaignName, modelName); }); var model = Timing.TimeStage("Load Models", () => { return LoadModel(_pathManager, 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(ref string campaignName, string modelName) public static MeshInstance3D LoadModel(ResourcePathManager pathManager, ref string campaignName, string modelName)
{ {
var pathManager = Context.Instance.PathManager; var modelPath = pathManager.GetObjectPath(ref campaignName, modelName);
var (newCampaignName, modelPath) = pathManager.GetResourcePath(ResourceType.Object, campaignName, modelName);
campaignName = newCampaignName;
if (modelPath == null) if (modelPath == null)
{ {
return null; return null;
@ -52,8 +56,7 @@ public class ModelLoader
{ {
if (material.Type == 0) if (material.Type == 0)
{ {
var resName = Path.GetFileNameWithoutExtension(material.Name); var path = pathManager.GetObjectTexturePath(campaignName, 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";

View File

@ -1,5 +1,4 @@
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;
@ -8,14 +7,16 @@ 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(string fmName) public TextureLoader(ResourcePathManager pathManager, string fmName)
{ {
_pathManager = pathManager;
_fmName = fmName; _fmName = fmName;
LoadDefaultTexture(); LoadDefaultTexture();
} }
@ -36,8 +37,7 @@ public partial class TextureLoader
private bool Load(int id, string path) private bool Load(int id, string path)
{ {
var loaded = false; var loaded = false;
var pathManager = Context.Instance.PathManager; string texPath = _pathManager.GetTexturePath(_fmName, path);
var (_, texPath) = pathManager.GetResourcePath(ResourceType.Texture, _fmName, path);
if (texPath != null) if (texPath != null)
{ {

View File

@ -8,6 +8,8 @@ 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;
@ -50,7 +52,7 @@ public partial class MissionSelector : Control
private void SetInstallPath(string path) private void SetInstallPath(string path)
{ {
_FolderPath.Text = path; _FolderPath.Text = path;
if (Context.Instance.PathManager.Init(path)) if (pathManager.Init(path))
{ {
BuildCampaignList(); BuildCampaignList();
} }
@ -62,7 +64,6 @@ 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())
{ {
@ -75,9 +76,8 @@ public partial class MissionSelector : Control
_Missions.Clear(); _Missions.Clear();
_LoadButton.Disabled = true; _LoadButton.Disabled = true;
var pathManager = Context.Instance.PathManager; var campaignName = _Campaigns.GetItemText((int)idx);
var campaignName = idx == 0 ? null : _Campaigns.GetItemText((int)idx); var missionNames = pathManager.GetMissionNames(idx == 0 ? null : campaignName);
var missionNames = pathManager.GetResourceNames(ResourceType.Mission, campaignName);
foreach (var mission in missionNames) foreach (var mission in missionNames)
{ {
_Missions.AddItem(mission); _Missions.AddItem(mission);

View File

@ -1,18 +1,20 @@
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
{ {
public event ModelSelectedEventHandler ModelSelected; [Signal]
public delegate void ModelSelectedEventHandler(string campaign, string mission); public delegate void LoadModelEventHandler(string rootPath, string modelPath);
private InstallPaths _installPaths;
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;
@ -26,15 +28,13 @@ 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 += SetInstallPath; _FolderSelect.DirSelected += (string dir) => { _FolderPath.Text = dir; BuildModelList(dir); };
_FolderPath.TextSubmitted += SetInstallPath; _FolderPath.TextSubmitted += BuildModelList;
_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,57 +51,44 @@ public partial class ModelSelector : Control
} }
} }
private void SetInstallPath(string path) private void BuildModelList(string path)
{ {
_FolderPath.Text = path; _installPaths = new InstallPaths(path);
if (Context.Instance.PathManager.Init(path)) ExtractObjFiles();
{
BuildCampaignList();
}
}
private void BuildCampaignList()
{
_Campaigns.Clear();
_Models.Clear(); _Models.Clear();
_LoadButton.Disabled = true; _LoadButton.Disabled = true;
var pathManager = Context.Instance.PathManager; var paths = Directory.GetFiles(_extractedObjectsPath, "*.bin", SearchOption.AllDirectories);
_Campaigns.AddItem("Original Missions"); foreach (var m in paths.OrderBy(s => s))
foreach (var campaign in pathManager.GetCampaignNames())
{ {
_Campaigns.AddItem(campaign); _Models.AddItem(m.TrimPrefix(_extractedObjectsPath));
} }
} }
private void BuildModelList(long idx) // TODO: Move this to a resource manager
private void ExtractObjFiles()
{ {
_Models.Clear(); var dir = new DirectoryInfo(_extractedObjectsPath);
_LoadButton.Disabled = true; if (dir.Exists)
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); dir.Delete(true);
} }
var zip = ZipFile.OpenRead(_installPaths.objPath);
zip.ExtractToDirectory(_extractedObjectsPath);
} }
private void EmitLoadModel() private void EmitLoadModel()
{ {
var campaignIdxs = _Campaigns.GetSelectedItems(); var selected = _Models.GetSelectedItems();
var modelIdxs = _Models.GetSelectedItems(); if (selected.IsEmpty())
if (campaignIdxs.IsEmpty() || modelIdxs.IsEmpty())
{ {
return; return;
} }
var campaignIdx = campaignIdxs[0]; var path = _extractedObjectsPath + _Models.GetItemText(selected[0]);
var modelIdx = modelIdxs[0]; EmitSignal(SignalName.LoadModel, _installPaths.rootPath, path);
var campaignName = campaignIdx == 0 ? null : _Campaigns.GetItemText(campaignIdx);
var modelName = _Models.GetItemText(modelIdx);
ModelSelected(campaignName, modelName);
Visible = false; Visible = false;
} }

View File

@ -15,15 +15,13 @@ 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="Environment" type="Node3D" parent="."] [node name="WorldEnvironment" type="WorldEnvironment" 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")

View File

@ -77,12 +77,6 @@ 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)