Compare commits
7 Commits
d3dd40eec3
...
236cf3b317
Author | SHA1 | Date |
---|---|---|
Jarrod Doyle | 236cf3b317 | |
Jarrod Doyle | 12a8b4f06d | |
Jarrod Doyle | 63daccb3c1 | |
Jarrod Doyle | b223d53824 | |
Jarrod Doyle | b89a4d6154 | |
Jarrod Doyle | 9ae75a693e | |
Jarrod Doyle | 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