Compare commits
8 Commits
b9172df8c3
...
cb2f016455
Author | SHA1 | Date |
---|---|---|
Jarrod Doyle | cb2f016455 | |
Jarrod Doyle | de4f3d6381 | |
Jarrod Doyle | 9fd76815a2 | |
Jarrod Doyle | 62ff5306fe | |
Jarrod Doyle | c48eb018f5 | |
Jarrod Doyle | db293791e0 | |
Jarrod Doyle | acc3add512 | |
Jarrod Doyle | 0eb918d863 |
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace KeepersCompound.LGS.Database.Chunks;
|
||||
|
||||
public class GamFile : IChunk
|
||||
{
|
||||
public ChunkHeader Header { get; set; }
|
||||
public string fileName;
|
||||
|
||||
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||
{
|
||||
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(256)).Replace("\0", string.Empty);
|
||||
fileName = tmpName[..Math.Min(255, tmpName.Length)];
|
||||
}
|
||||
|
||||
public void WriteData(BinaryWriter writer)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace KeepersCompound.LGS.Database.Chunks;
|
||||
|
||||
public record LinkId
|
||||
{
|
||||
private readonly uint _data;
|
||||
|
||||
public LinkId(uint data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public uint GetId()
|
||||
{
|
||||
return _data & 0xFFFF;
|
||||
}
|
||||
|
||||
public bool IsConcrete()
|
||||
{
|
||||
return (_data & 0xF0000) != 0;
|
||||
}
|
||||
|
||||
public uint GetRelation()
|
||||
{
|
||||
return (_data >> 20) & 0xFFF;
|
||||
}
|
||||
|
||||
public uint GetRaw()
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
}
|
||||
|
||||
public class LinkChunk : IChunk
|
||||
{
|
||||
public record Link
|
||||
{
|
||||
public LinkId linkId;
|
||||
public int source;
|
||||
public int destination;
|
||||
public ushort relation;
|
||||
|
||||
public Link(BinaryReader reader)
|
||||
{
|
||||
linkId = new LinkId(reader.ReadUInt32());
|
||||
source = reader.ReadInt32();
|
||||
destination = reader.ReadInt32();
|
||||
relation = reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
|
||||
public ChunkHeader Header { get; set; }
|
||||
public List<Link> links;
|
||||
|
||||
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||
{
|
||||
links = new List<Link>();
|
||||
while (reader.BaseStream.Position < entry.Offset + entry.Size + 24)
|
||||
{
|
||||
links.Add(new Link(reader));
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteData(BinaryWriter writer)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class LinkDataMetaProp : IChunk
|
||||
{
|
||||
public record LinkData
|
||||
{
|
||||
public LinkId linkId;
|
||||
public int priority;
|
||||
|
||||
public LinkData(BinaryReader reader)
|
||||
{
|
||||
linkId = new LinkId(reader.ReadUInt32());
|
||||
priority = reader.ReadInt32();
|
||||
}
|
||||
}
|
||||
|
||||
public ChunkHeader Header { get; set; }
|
||||
public int DataSize;
|
||||
public List<LinkData> linkData;
|
||||
|
||||
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||
{
|
||||
DataSize = reader.ReadInt32();
|
||||
linkData = new List<LinkData>();
|
||||
while (reader.BaseStream.Position < entry.Offset + entry.Size + 24)
|
||||
{
|
||||
linkData.Add(new LinkData(reader));
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteData(BinaryWriter writer)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace KeepersCompound.LGS.Database.Chunks;
|
||||
|
||||
public class PropertyModelName : IChunk
|
||||
{
|
||||
public record Property
|
||||
{
|
||||
public int objectId;
|
||||
public int length;
|
||||
public string modelName;
|
||||
|
||||
public Property(BinaryReader reader)
|
||||
{
|
||||
objectId = reader.ReadInt32();
|
||||
length = (int)reader.ReadUInt32();
|
||||
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(length)).Replace("\0", string.Empty);
|
||||
modelName = tmpName[..Math.Min(length - 1, tmpName.Length)];
|
||||
}
|
||||
}
|
||||
|
||||
public ChunkHeader Header { get; set; }
|
||||
public List<Property> properties;
|
||||
|
||||
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||
{
|
||||
properties = new List<Property>();
|
||||
while (reader.BaseStream.Position < entry.Offset + entry.Size + 24)
|
||||
{
|
||||
properties.Add(new Property(reader));
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteData(BinaryWriter writer)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -98,9 +98,13 @@ public class DbFile
|
|||
{
|
||||
// "AI_ROOM_DB" => new AiRoomDb(),
|
||||
// "AICONVERSE" => new AiConverseChunk(),
|
||||
"GAM_FILE" => new GamFile(),
|
||||
"TXLIST" => new TxList(),
|
||||
"WREXT" => new WorldRep(),
|
||||
"BRLIST" => new BrList(),
|
||||
"P$ModelName" => new PropertyModelName(),
|
||||
"LD$MetaProp" => new LinkDataMetaProp(),
|
||||
_ when entryName.StartsWith("L$") => new LinkChunk(),
|
||||
_ => new GenericChunk(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,362 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
|
||||
namespace KeepersCompound.LGS;
|
||||
|
||||
// TODO: Support FMs resource paths!
|
||||
public class ResourcePathManager
|
||||
{
|
||||
private record CampaignResources
|
||||
{
|
||||
public Dictionary<string, string> missionPathMap;
|
||||
public Dictionary<string, string> texturePathMap;
|
||||
public Dictionary<string, string> objectPathMap;
|
||||
}
|
||||
|
||||
private bool _initialised = false;
|
||||
private readonly string _extractionPath;
|
||||
private CampaignResources _omResources;
|
||||
private Dictionary<string, CampaignResources> _fmResources;
|
||||
|
||||
public ResourcePathManager(string extractionPath)
|
||||
{
|
||||
_extractionPath = extractionPath;
|
||||
}
|
||||
|
||||
public bool Init(string installPath)
|
||||
{
|
||||
// TODO: This can be done less awkwardly with the resource paths
|
||||
if (DirContainsThiefExe(installPath) &&
|
||||
TryGetInstallCfgPath(installPath, out var installCfgPath) &&
|
||||
TryBuildOmsPathMap(installPath, installCfgPath, out var omsMap) &&
|
||||
TryBuildFmsPathMap(installPath, out var fmsMap, out var fmsDir) &&
|
||||
TryGetPath(installPath, "fam.crf", out var famPath) &&
|
||||
TryGetPath(installPath, "obj.crf", out var objPath))
|
||||
{
|
||||
// Register OM resources
|
||||
{
|
||||
var famExtractPath = Path.Join(_extractionPath, "fam");
|
||||
if (Directory.Exists(famExtractPath))
|
||||
{
|
||||
Directory.Delete(famExtractPath, true);
|
||||
}
|
||||
ZipFile.OpenRead(famPath).ExtractToDirectory(famExtractPath);
|
||||
var texturePathMap = GetTexturePaths(_extractionPath);
|
||||
|
||||
var objExtractPath = Path.Join(_extractionPath, "obj");
|
||||
if (Directory.Exists(objExtractPath))
|
||||
{
|
||||
Directory.Delete(objExtractPath, true);
|
||||
}
|
||||
ZipFile.OpenRead(objPath).ExtractToDirectory(objExtractPath);
|
||||
var objectPathMap = GetObjectPaths(_extractionPath);
|
||||
|
||||
_omResources = new CampaignResources
|
||||
{
|
||||
missionPathMap = omsMap,
|
||||
texturePathMap = texturePathMap,
|
||||
objectPathMap = objectPathMap,
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
_fmResources = new Dictionary<string, CampaignResources>();
|
||||
foreach (var (campaign, missionPathMap) in fmsMap)
|
||||
{
|
||||
var texturePathMap = GetTexturePaths(Path.Join(fmsDir, campaign));
|
||||
var objectPathMap = GetObjectPaths(Path.Join(fmsDir, campaign));
|
||||
var resource = new CampaignResources
|
||||
{
|
||||
missionPathMap = missionPathMap,
|
||||
texturePathMap = texturePathMap,
|
||||
objectPathMap = objectPathMap,
|
||||
};
|
||||
_fmResources.Add(campaign, resource);
|
||||
}
|
||||
}
|
||||
|
||||
_initialised = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetMissionPath(string missionName)
|
||||
{
|
||||
if (!_initialised) return null;
|
||||
|
||||
if (_omResources.missionPathMap.TryGetValue(missionName, out var path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetMissionPath(string campaignName, string missionName)
|
||||
{
|
||||
if (!_initialised) return null;
|
||||
|
||||
if (_fmResources.TryGetValue(campaignName, out var campaign) &&
|
||||
campaign.missionPathMap.TryGetValue(missionName, out var path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetTexturePath(string textureName)
|
||||
{
|
||||
if (!_initialised) return null;
|
||||
|
||||
textureName = textureName.ToLower();
|
||||
if (_omResources.texturePathMap.TryGetValue(textureName, out var path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetTexturePath(string campaignName, string textureName)
|
||||
{
|
||||
if (!_initialised) return null;
|
||||
|
||||
textureName = textureName.ToLower();
|
||||
if (_fmResources.TryGetValue(campaignName, out var campaign) &&
|
||||
campaign.texturePathMap.TryGetValue(textureName, out var path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetObjectPath(string objectName)
|
||||
{
|
||||
if (!_initialised) return null;
|
||||
|
||||
objectName = objectName.ToLower();
|
||||
if (_omResources.objectPathMap.TryGetValue(objectName, out var path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetObjectPath(string campaignName, string objectName)
|
||||
{
|
||||
if (!_initialised) return null;
|
||||
|
||||
objectName = objectName.ToLower();
|
||||
if (_fmResources.TryGetValue(campaignName, out var campaign) &&
|
||||
campaign.objectPathMap.TryGetValue(objectName, out var path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Handle object textures?
|
||||
private static Dictionary<string, string> GetTexturePaths(string root)
|
||||
{
|
||||
string[] validExtensions = { ".dds", ".png", ".tga", ".pcx", ".gif", ".bmp", ".cel", };
|
||||
|
||||
var famOptions = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
|
||||
var textureOptions = new EnumerationOptions
|
||||
{
|
||||
MatchCasing = MatchCasing.CaseInsensitive,
|
||||
RecurseSubdirectories = true,
|
||||
};
|
||||
|
||||
var pathMap = new Dictionary<string, string>();
|
||||
foreach (var dir in Directory.EnumerateDirectories(root, "fam", famOptions))
|
||||
{
|
||||
foreach (var path in Directory.EnumerateFiles(dir, "*", textureOptions))
|
||||
{
|
||||
var ext = Path.GetExtension(path);
|
||||
if (validExtensions.Contains(ext.ToLower()))
|
||||
{
|
||||
var key = Path.GetRelativePath(root, path)[..^ext.Length].ToLower();
|
||||
pathMap.TryAdd(key, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pathMap;
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> GetObjectPaths(string root)
|
||||
{
|
||||
var dirOptions = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
|
||||
var binOptions = new EnumerationOptions
|
||||
{
|
||||
MatchCasing = MatchCasing.CaseInsensitive,
|
||||
RecurseSubdirectories = true,
|
||||
};
|
||||
|
||||
var pathMap = new Dictionary<string, string>();
|
||||
foreach (var dir in Directory.EnumerateDirectories(root, "obj", dirOptions))
|
||||
{
|
||||
foreach (var path in Directory.EnumerateFiles(dir, "*.bin", binOptions))
|
||||
{
|
||||
var key = Path.GetRelativePath(dir, path).ToLower();
|
||||
pathMap.TryAdd(key, path);
|
||||
}
|
||||
}
|
||||
|
||||
return pathMap;
|
||||
}
|
||||
|
||||
private static bool TryBuildOmsPathMap(string root, string cfgPath, out Dictionary<string, string> map)
|
||||
{
|
||||
map = new Dictionary<string, string>();
|
||||
|
||||
var omsPath = "";
|
||||
foreach (var line in File.ReadLines(cfgPath))
|
||||
{
|
||||
if (line.StartsWith("load_path"))
|
||||
{
|
||||
// TODO: Do we only need to replace on systems with a different path separator?
|
||||
var path = line.Split(" ")[1].Replace("\\", "/");
|
||||
omsPath = Path.GetFullPath(root + path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (omsPath == "")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var searchOptions = new EnumerationOptions
|
||||
{
|
||||
MatchCasing = MatchCasing.CaseInsensitive,
|
||||
};
|
||||
foreach (var path in Directory.GetFiles(root, "*.mis", searchOptions))
|
||||
{
|
||||
var baseName = Path.GetFileName(path).ToLower();
|
||||
map.Add(baseName, path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryBuildFmsPathMap(
|
||||
string root,
|
||||
out Dictionary<string, Dictionary<string, string>> fmsMap,
|
||||
out string fmsPath)
|
||||
{
|
||||
fmsMap = new Dictionary<string, Dictionary<string, string>>();
|
||||
fmsPath = root + "/FMs";
|
||||
|
||||
var searchOptions = new EnumerationOptions
|
||||
{
|
||||
MatchCasing = MatchCasing.CaseInsensitive,
|
||||
RecurseSubdirectories = true
|
||||
};
|
||||
|
||||
// Cam Mod tells us where any FMs are installed (alongside other things)
|
||||
// If it doesn't exist then something is wrong with the install directory
|
||||
if (!TryGetPath(root, "cam_mod.ini", out var camModPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// We're looking for an uncommented line defining an fm_path, but its
|
||||
// find if we don't find one because there's a default path.
|
||||
foreach (var line in File.ReadLines(camModPath))
|
||||
{
|
||||
if (line.StartsWith("fm_path"))
|
||||
{
|
||||
// TODO: I think this can technically contain multiple paths
|
||||
var path = line.Split(" ")[1].Replace("\\", "/");
|
||||
fmsPath = Path.GetFullPath(root + path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Now we can iterate through all the FM directories and map their mission paths
|
||||
// if they have any.
|
||||
string[] extensions = { ".mis", ".cow" };
|
||||
searchOptions.RecurseSubdirectories = false;
|
||||
foreach (var dir in Directory.GetDirectories(fmsPath))
|
||||
{
|
||||
var campaignMap = new Dictionary<string, string>();
|
||||
foreach (var path in Directory.GetFiles(dir))
|
||||
{
|
||||
if (extensions.Contains(Path.GetExtension(path).ToLower()))
|
||||
{
|
||||
var baseName = Path.GetFileName(path).ToLower();
|
||||
campaignMap.Add(baseName, path);
|
||||
}
|
||||
}
|
||||
|
||||
if (campaignMap.Count != 0)
|
||||
{
|
||||
fmsMap.Add(Path.GetFileName(dir), campaignMap);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool DirContainsThiefExe(string dir)
|
||||
{
|
||||
var searchOptions = new EnumerationOptions
|
||||
{
|
||||
MatchCasing = MatchCasing.CaseInsensitive,
|
||||
};
|
||||
|
||||
foreach (var path in Directory.GetFiles(dir, "*.exe", searchOptions))
|
||||
{
|
||||
var baseName = Path.GetFileName(path).ToLower();
|
||||
if (baseName.Contains("thief"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetInstallCfgPath(string dir, out string installCfgPath)
|
||||
{
|
||||
var searchOptions = new EnumerationOptions
|
||||
{
|
||||
MatchCasing = MatchCasing.CaseInsensitive,
|
||||
RecurseSubdirectories = true
|
||||
};
|
||||
|
||||
foreach (var path in Directory.GetFiles(dir, "*.cfg", searchOptions))
|
||||
{
|
||||
var baseName = Path.GetFileName(path).ToLower();
|
||||
if (baseName == "install.cfg" || baseName == "darkinst.cfg")
|
||||
{
|
||||
installCfgPath = path;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
installCfgPath = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetPath(string dir, string searchPattern, out string path)
|
||||
{
|
||||
var searchOptions = new EnumerationOptions
|
||||
{
|
||||
MatchCasing = MatchCasing.CaseInsensitive,
|
||||
RecurseSubdirectories = true
|
||||
};
|
||||
|
||||
var paths = Directory.GetFiles(dir, searchPattern, searchOptions);
|
||||
if (paths.Length > 0)
|
||||
{
|
||||
path = paths[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
path = "";
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
using Godot;
|
||||
using KeepersCompound.LGS;
|
||||
using KeepersCompound.LGS.Database;
|
||||
using KeepersCompound.LGS.Database.Chunks;
|
||||
using KeepersCompound.TMV.UI;
|
||||
using RectpackSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace KeepersCompound.TMV;
|
||||
|
||||
|
@ -37,17 +39,19 @@ public partial class Mission : Node3D
|
|||
[Export]
|
||||
public bool Dump = false;
|
||||
|
||||
|
||||
InstallPaths _installPaths;
|
||||
ResourcePathManager _installPaths;
|
||||
DbFile _file;
|
||||
TextureLoader _textureLoader;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
var extractPath = ProjectSettings.GlobalizePath($"user://extracted/tmp");
|
||||
_installPaths = new ResourcePathManager(extractPath);
|
||||
var missionSelector = GetNode<Control>("%MissionSelector") as MissionSelector;
|
||||
missionSelector.LoadMission += (string rootPath, string missionPath) =>
|
||||
{
|
||||
_installPaths = new InstallPaths(rootPath);
|
||||
var inited = _installPaths.Init(rootPath);
|
||||
GD.Print($"Inited paths: {inited}");
|
||||
FileName = missionPath;
|
||||
Build = true;
|
||||
};
|
||||
|
@ -90,11 +94,53 @@ public partial class Mission : Node3D
|
|||
{
|
||||
ClearMap();
|
||||
|
||||
_textureLoader = new TextureLoader(_installPaths.famPath, FileName.GetBaseDir());
|
||||
// TODO: This shouldn't be set for things that aren't actually FMs
|
||||
var fmName = FileName.GetBaseDir().GetFile();
|
||||
_textureLoader = new TextureLoader(fmName);
|
||||
_file = new(FileName);
|
||||
UseChunk<TxList>("TXLIST", LoadTextures);
|
||||
UseChunk<WorldRep>("WREXT", BuildWrMeshes);
|
||||
UseChunk<BrList>("BRLIST", PlaceObjects);
|
||||
if (
|
||||
_file.Chunks.TryGetValue("BRLIST", out var brListRaw) &&
|
||||
_file.Chunks.TryGetValue("P$ModelName", out var modelNamesRaw) &&
|
||||
_file.Chunks.TryGetValue("L$MetaProp", out var metaPropLinksRaw) &&
|
||||
_file.Chunks.TryGetValue("LD$MetaProp", out var metaPropLinkDataRaw)
|
||||
)
|
||||
{
|
||||
var brList = (BrList)brListRaw;
|
||||
var modelNames = (PropertyModelName)modelNamesRaw;
|
||||
var metaPropLinks = (LinkChunk)metaPropLinksRaw;
|
||||
var metaPropLinkData = (LinkDataMetaProp)metaPropLinkDataRaw;
|
||||
|
||||
// TODO: Do this somewhere else lol
|
||||
if (_file.Chunks.TryGetValue("GAM_FILE", out var gamFileChunk))
|
||||
{
|
||||
GD.Print("GAM_FILE detected");
|
||||
|
||||
var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
|
||||
var name = ((GamFile)gamFileChunk).fileName;
|
||||
GD.Print($"Searching for GAM: {FileName.GetBaseDir()}/{name}");
|
||||
var paths = Directory.GetFiles(FileName.GetBaseDir(), name, options);
|
||||
GD.Print($"Found paths: {paths.Length}");
|
||||
if (!paths.IsEmpty())
|
||||
{
|
||||
GD.Print($"Attempting to load GAM at: {paths[0]}");
|
||||
var gamFile = new DbFile(paths[0]);
|
||||
if (gamFile.Chunks.TryGetValue("P$ModelName", out var gamChunk1) &&
|
||||
gamFile.Chunks.TryGetValue("L$MetaProp", out var gamChunk2) &&
|
||||
gamFile.Chunks.TryGetValue("LD$MetaProp", out var gamChunk3))
|
||||
{
|
||||
GD.Print($"Pre-Merged chunks: {modelNames.properties.Count} {metaPropLinks.links.Count} {metaPropLinkData.linkData.Count}");
|
||||
modelNames.properties.AddRange(((PropertyModelName)gamChunk1).properties);
|
||||
metaPropLinks.links.AddRange(((LinkChunk)gamChunk2).links);
|
||||
metaPropLinkData.linkData.AddRange(((LinkDataMetaProp)gamChunk3).linkData);
|
||||
GD.Print($"Post-Merged chunks: {modelNames.properties.Count} {metaPropLinks.links.Count} {metaPropLinkData.linkData.Count}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlaceObjects(brList, modelNames, metaPropLinks, metaPropLinkData);
|
||||
}
|
||||
}
|
||||
|
||||
private void UseChunk<T>(string name, Action<T> action)
|
||||
|
@ -109,7 +155,11 @@ public partial class Mission : Node3D
|
|||
}
|
||||
}
|
||||
|
||||
private void PlaceObjects(BrList brList)
|
||||
private void PlaceObjects(
|
||||
BrList brList,
|
||||
PropertyModelName modelNames,
|
||||
LinkChunk metapropLink,
|
||||
LinkDataMetaProp metaPropLinkData)
|
||||
{
|
||||
foreach (var brush in brList.Brushes)
|
||||
{
|
||||
|
@ -118,12 +168,68 @@ public partial class Mission : Node3D
|
|||
continue;
|
||||
}
|
||||
|
||||
var pos = brush.position.ToGodotVec3();
|
||||
var cube = new CsgBox3D
|
||||
// TODO: Build an actual hierarchy and such :)
|
||||
// TODO: We need to load the gamesys :)
|
||||
// Determine if we have a model name :))
|
||||
var id = (int)brush.brushInfo;
|
||||
var modelName = "";
|
||||
while (true)
|
||||
{
|
||||
Position = pos
|
||||
};
|
||||
AddChild(cube);
|
||||
// See if there's a modelname property
|
||||
foreach (var prop in modelNames.properties)
|
||||
{
|
||||
if (prop.objectId == id)
|
||||
{
|
||||
modelName = prop.modelName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (modelName != "") break;
|
||||
|
||||
// No modelname so check for a parent
|
||||
var length = metapropLink.links.Count;
|
||||
var prevId = id;
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var link = metapropLink.links[i];
|
||||
var linkData = metaPropLinkData.linkData[i];
|
||||
if (link.source == id && linkData.priority == 0)
|
||||
{
|
||||
id = link.destination;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// No parent found
|
||||
if (id == prevId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (modelName == "")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Let's try and place an object :)
|
||||
var fmName = FileName.GetBaseDir().GetFile();
|
||||
var objPath = _installPaths.GetObjectPath(fmName, modelName + ".bin");
|
||||
objPath ??= _installPaths.GetObjectPath(modelName + ".bin");
|
||||
|
||||
var pos = brush.position.ToGodotVec3();
|
||||
var model = new Model();
|
||||
model.Position = pos;
|
||||
if (objPath != null)
|
||||
{
|
||||
model.BuildModel("", objPath);
|
||||
}
|
||||
AddChild(model);
|
||||
|
||||
// var pos = brush.position.ToGodotVec3();
|
||||
// var cube = new CsgBox3D
|
||||
// {
|
||||
// Position = pos
|
||||
// };
|
||||
// AddChild(cube);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,7 +460,7 @@ public partial class Mission : Node3D
|
|||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var item = textureList.Items[i];
|
||||
var path = "/";
|
||||
var path = "";
|
||||
for (var j = 0; j < item.Tokens.Length; j++)
|
||||
{
|
||||
var token = item.Tokens[j];
|
||||
|
@ -367,7 +473,7 @@ public partial class Mission : Node3D
|
|||
}
|
||||
path += item.Name;
|
||||
|
||||
if (!_textureLoader.Load(i, path))
|
||||
if (!_textureLoader.Load(_installPaths, i, path))
|
||||
{
|
||||
GD.Print($"Failed to load texture: {path}");
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ public partial class Model : Node3D
|
|||
modelSelector.LoadModel += BuildModel;
|
||||
}
|
||||
|
||||
private void BuildModel(string rootPath, string modelPath)
|
||||
public void BuildModel(string rootPath, string modelPath)
|
||||
{
|
||||
foreach (var node in GetChildren())
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ public partial class Model : Node3D
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove this disgusting hack
|
||||
// TODO: Remove this disgusting hack. Not only is it a hack, it doesn't support custom models
|
||||
var baseDir = ProjectSettings.GlobalizePath($"user://objects/tmp");
|
||||
var options = new EnumerationOptions
|
||||
{
|
||||
|
|
|
@ -1,45 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using Godot;
|
||||
using KeepersCompound.LGS;
|
||||
|
||||
namespace KeepersCompound.TMV;
|
||||
|
||||
public partial class TextureLoader
|
||||
{
|
||||
private readonly string _userTexturesPath;
|
||||
private readonly string _rootFamPath; // TODO: Load from installation resources
|
||||
private readonly string _fmPath;
|
||||
private readonly Dictionary<string, string> _rootTexturePaths = new();
|
||||
private readonly Dictionary<string, string> _fmTexturePaths = new();
|
||||
|
||||
private readonly string _fmName;
|
||||
private readonly List<ImageTexture> _textureCache = new();
|
||||
private readonly Dictionary<int, int> _idMap = new();
|
||||
private readonly Dictionary<string, int> _pathMap = new();
|
||||
|
||||
public TextureLoader(string rootFamPath, string fmPath)
|
||||
public TextureLoader(string fmName)
|
||||
{
|
||||
_rootFamPath = rootFamPath;
|
||||
_fmPath = fmPath;
|
||||
_userTexturesPath = ProjectSettings.GlobalizePath($"user://textures/tmp");
|
||||
|
||||
ExtractRootFamFiles();
|
||||
_fmName = fmName;
|
||||
LoadDefaultTexture();
|
||||
RegisterTexturePaths(_fmPath, _fmTexturePaths);
|
||||
RegisterTexturePaths(_userTexturesPath, _rootTexturePaths);
|
||||
}
|
||||
|
||||
private void ExtractRootFamFiles()
|
||||
{
|
||||
var dir = new DirectoryInfo(_userTexturesPath);
|
||||
if (dir.Exists)
|
||||
{
|
||||
dir.Delete(true);
|
||||
}
|
||||
|
||||
var zip = ZipFile.OpenRead(_rootFamPath);
|
||||
zip.ExtractToDirectory(_userTexturesPath.PathJoin("fam"));
|
||||
}
|
||||
|
||||
private void LoadDefaultTexture()
|
||||
|
@ -50,46 +26,28 @@ public partial class TextureLoader
|
|||
_textureCache.Add(texture);
|
||||
}
|
||||
|
||||
private static void RegisterTexturePaths(string rootDir, Dictionary<string, string> map)
|
||||
{
|
||||
// TODO: Load DDS BMP GIF CEL
|
||||
string[] validExtensions = { "png", "tga", "pcx", "gif" };
|
||||
|
||||
var famOptions = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
|
||||
var textureOptions = new EnumerationOptions
|
||||
{
|
||||
MatchCasing = MatchCasing.CaseInsensitive,
|
||||
RecurseSubdirectories = true,
|
||||
};
|
||||
|
||||
foreach (var dirPath in Directory.EnumerateDirectories(rootDir, "fam", famOptions))
|
||||
{
|
||||
foreach (var path in Directory.EnumerateFiles(dirPath, "*", textureOptions))
|
||||
{
|
||||
if (validExtensions.Contains(path.GetExtension().ToLower()))
|
||||
{
|
||||
// TODO: This only adds the first one found rather than the highest priority
|
||||
var key = path.TrimPrefix(rootDir).GetBaseName().ToLower();
|
||||
map.TryAdd(key, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GD.Print($"Registered {map.Count} texture paths at : {rootDir}");
|
||||
}
|
||||
|
||||
public bool Load(int id, string path)
|
||||
public bool Load(ResourcePathManager installManager, int id, string path)
|
||||
{
|
||||
var loaded = false;
|
||||
if (_fmTexturePaths.TryGetValue(path.ToLower(), out var fmTexPath))
|
||||
string texPath;
|
||||
if (_fmName != null)
|
||||
{
|
||||
_textureCache.Add(LoadTexture(fmTexPath));
|
||||
texPath = installManager.GetTexturePath(_fmName, path);
|
||||
texPath ??= installManager.GetTexturePath(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
texPath = installManager.GetTexturePath(path);
|
||||
}
|
||||
|
||||
if (texPath != null)
|
||||
{
|
||||
var texture = LoadTexture(texPath);
|
||||
if (texture != null)
|
||||
{
|
||||
_textureCache.Add(texture);
|
||||
loaded = true;
|
||||
}
|
||||
else if (_rootTexturePaths.TryGetValue(path.ToLower(), out var rootTexPath))
|
||||
{
|
||||
_textureCache.Add(LoadTexture(rootTexPath));
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
var index = loaded ? _textureCache.Count - 1 : 0;
|
||||
|
@ -101,6 +59,9 @@ public partial class TextureLoader
|
|||
public static ImageTexture LoadTexture(string path)
|
||||
{
|
||||
var ext = path.GetExtension().ToLower();
|
||||
string[] validExtensions = { "png", "tga", "pcx", "gif" };
|
||||
if (validExtensions.Contains(ext))
|
||||
{
|
||||
var texture = ext switch
|
||||
{
|
||||
"pcx" => LoadPcx(path),
|
||||
|
@ -109,6 +70,8 @@ public partial class TextureLoader
|
|||
};
|
||||
return texture;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ImageTexture Get(int id)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue