Compare commits

...

12 Commits

18 changed files with 321 additions and 63 deletions

34
.vscode/snippets.code-snippets vendored Normal file
View File

@ -0,0 +1,34 @@
{
// Place your Thief Mission Viewer workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
// "Print to console": {
// "scope": "javascript,typescript",
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
"Property": {
"prefix": "SProp",
"body": [
"public class Prop${1:PropName} : Property",
"{",
" ${2:}",
"",
" public override void Read(BinaryReader reader)",
" {",
" base.Read(reader);",
" ${3:}",
" }",
"}"
],
"description": "Property"
}
}

View File

@ -10,8 +10,6 @@ namespace KeepersCompound.Images;
public class GifDecoder public class GifDecoder
{ {
// TODO: I can drop all these structs and shit lol // TODO: I can drop all these structs and shit lol
// TODO: This fails somewhat on the texture for model "skull". CBA to workout why now
private record Header private record Header
{ {
public string Signature; public string Signature;

View File

@ -11,9 +11,7 @@ public struct ChunkHeader
public ChunkHeader(BinaryReader reader) public ChunkHeader(BinaryReader reader)
{ {
var nameBytes = reader.ReadBytes(12); Name = reader.ReadNullString(12);
var nameTmp = Encoding.UTF8.GetString(nameBytes).Replace("\0", string.Empty);
Name = nameTmp[..Math.Min(11, nameTmp.Length)];
Version = new(reader); Version = new(reader);
reader.ReadBytes(4); reader.ReadBytes(4);
} }

View File

@ -79,11 +79,11 @@ public class BrList : IChunk
flags = reader.ReadSByte(); flags = reader.ReadSByte();
position = reader.ReadVec3(); position = reader.ReadVec3();
size = reader.ReadVec3(); size = reader.ReadVec3();
angle = new Vector3(reader.ReadUInt16(), reader.ReadUInt16(), reader.ReadUInt16()); angle = reader.ReadRotation();
currentFaceIndex = reader.ReadInt16(); currentFaceIndex = reader.ReadInt16();
gridLineSpacing = reader.ReadSingle(); gridLineSpacing = reader.ReadSingle();
gridPhaseShift = reader.ReadVec3(); gridPhaseShift = reader.ReadVec3();
gridOrientation = new Vector3(reader.ReadUInt16(), reader.ReadUInt16(), reader.ReadUInt16()); gridOrientation = reader.ReadRotation();
gridEnabled = reader.ReadBoolean(); gridEnabled = reader.ReadBoolean();
numFaces = reader.ReadByte(); numFaces = reader.ReadByte();
edgeSelected = reader.ReadSByte(); edgeSelected = reader.ReadSByte();

View File

@ -11,8 +11,7 @@ public class GamFile : IChunk
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry) public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
{ {
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(256)).Replace("\0", string.Empty); fileName = reader.ReadNullString(256);
fileName = tmpName[..Math.Min(255, tmpName.Length)];
} }
public void WriteData(BinaryWriter writer) public void WriteData(BinaryWriter writer)

View File

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Numerics; using System.Numerics;
using System.Text;
namespace KeepersCompound.LGS.Database.Chunks; namespace KeepersCompound.LGS.Database.Chunks;
@ -56,26 +54,71 @@ public class PropGeneric : Property
} }
} }
public class PropModelName : Property public class PropBool : Property
{ {
public string modelName; public bool value;
public override void Read(BinaryReader reader) public override void Read(BinaryReader reader)
{ {
base.Read(reader); base.Read(reader);
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(length)).Replace("\0", string.Empty); value = reader.ReadInt32() != 0;
modelName = tmpName[..Math.Min(length - 1, tmpName.Length)];
} }
} }
public class PropScale : Property public class PropInt : Property
{ {
public Vector3 scale; public int value;
public override void Read(BinaryReader reader) public override void Read(BinaryReader reader)
{ {
base.Read(reader); base.Read(reader);
scale = reader.ReadVec3(); value = reader.ReadInt32();
}
}
public class PropLabel : Property
{
public string value;
public override void Read(BinaryReader reader)
{
base.Read(reader);
value = reader.ReadNullString(length);
}
}
public class PropString : Property
{
public int stringLength;
public string value;
public override void Read(BinaryReader reader)
{
base.Read(reader);
stringLength = reader.ReadInt32();
value = reader.ReadNullString(stringLength);
}
}
public class PropFloat : Property
{
public float value;
public override void Read(BinaryReader reader)
{
base.Read(reader);
value = reader.ReadSingle();
}
}
public class PropVector : Property
{
public Vector3 value;
public override void Read(BinaryReader reader)
{
base.Read(reader);
value = reader.ReadVec3();
} }
} }
@ -99,31 +142,149 @@ public class PropRenderType : Property
} }
} }
public class PropString : Property public class PropSlayResult : Property
{ {
public int stringLength; public enum Effect
public string value; {
Normal, NoEffect, Terminate, Destroy,
}
public Effect effect;
public override void Read(BinaryReader reader) public override void Read(BinaryReader reader)
{ {
base.Read(reader); base.Read(reader);
stringLength = reader.ReadInt32(); effect = (Effect)reader.ReadUInt32();
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(stringLength));
var idx = tmpName.IndexOf('\0');
if (idx >= 0) tmpName = tmpName[..idx];
value = tmpName;
// var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(length)).Replace("\0", string.Empty);
// value = tmpName[..Math.Min(length - 1, tmpName.Length)];
} }
} }
public class PropFloat : Property public class PropInventoryType : Property
{ {
public float value; public enum Slot
{
Junk, Item, Weapon,
}
public Slot type;
public override void Read(BinaryReader reader) public override void Read(BinaryReader reader)
{ {
base.Read(reader); base.Read(reader);
value = reader.ReadSingle(); type = (Slot)reader.ReadUInt32();
}
}
public class PropCollisionType : Property
{
public bool Bounce;
public bool DestroyOnImpact;
public bool SlayOnImpact;
public bool NoCollisionSound;
public bool NoResult;
public bool FullCollisionSound;
public override void Read(BinaryReader reader)
{
base.Read(reader);
var flags = reader.ReadUInt32();
Bounce = (flags & 0x1) != 0;
DestroyOnImpact = (flags & (0x1 << 1)) != 0;
SlayOnImpact = (flags & (0x1 << 2)) != 0;
NoCollisionSound = (flags & (0x1 << 3)) != 0;
NoResult = (flags & (0x1 << 4)) != 0;
FullCollisionSound = (flags & (0x1 << 5)) != 0;
}
}
public class PropPosition : Property
{
public Vector3 Location;
public Vector3 Rotation;
public override void Read(BinaryReader reader)
{
base.Read(reader);
Location = reader.ReadVec3();
reader.ReadBytes(4); // Runtime Cell/Hint in editor
Rotation = reader.ReadRotation();
}
}
public class PropLight : Property
{
public float Brightness;
public Vector3 Offset;
public float Radius;
public float InnerRadius;
public bool QuadLit;
public override void Read(BinaryReader reader)
{
base.Read(reader);
Brightness = reader.ReadSingle();
Offset = reader.ReadVec3();
Radius = reader.ReadSingle();
QuadLit = reader.ReadBoolean();
reader.ReadBytes(3);
InnerRadius = reader.ReadSingle();
}
}
public class PropLightColor : Property
{
public float Hue;
public float Saturation;
public override void Read(BinaryReader reader)
{
base.Read(reader);
Hue = reader.ReadSingle();
Saturation = reader.ReadSingle();
}
}
public class PropSpotlight : Property
{
public float InnerAngle;
public float OuterAngle;
public override void Read(BinaryReader reader)
{
base.Read(reader);
InnerAngle = reader.ReadSingle();
OuterAngle = reader.ReadSingle();
reader.ReadBytes(4); // Z is unused
}
}
public class PropSpotlightAndAmbient : Property
{
public float InnerAngle;
public float OuterAngle;
public float SpotBrightness;
public override void Read(BinaryReader reader)
{
base.Read(reader);
InnerAngle = reader.ReadSingle();
OuterAngle = reader.ReadSingle();
SpotBrightness = reader.ReadSingle();
}
}
// TODO: Work out what this property actually does
public class PropLightBasedAlpha : Property
{
public bool Enabled;
public Vector2 AlphaRange;
public Vector2 LightRange;
public override void Read(BinaryReader reader)
{
base.Read(reader);
Enabled = reader.ReadBoolean();
reader.ReadBytes(3);
AlphaRange = reader.ReadVec2();
LightRange = new Vector2(reader.ReadInt32(), reader.ReadInt32());
} }
} }

View File

@ -14,8 +14,7 @@ public class TxList : IChunk
public Item(BinaryReader reader) public Item(BinaryReader reader)
{ {
Tokens = reader.ReadBytes(4); Tokens = reader.ReadBytes(4);
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(16)).Replace("\0", string.Empty); Name = reader.ReadNullString(16);
Name = tmpName[..Math.Min(15, tmpName.Length)];
} }
} }
@ -37,8 +36,7 @@ public class TxList : IChunk
Tokens = new string[TokenCount]; Tokens = new string[TokenCount];
for (var i = 0; i < TokenCount; i++) for (var i = 0; i < TokenCount; i++)
{ {
var tmpToken = Encoding.UTF8.GetString(reader.ReadBytes(16)).Replace("\0", string.Empty); Tokens[i] = reader.ReadNullString(16);
Tokens[i] = tmpToken[..Math.Min(16, tmpToken.Length)];
} }
Items = new Item[ItemCount]; Items = new Item[ItemCount];
for (var i = 0; i < ItemCount; i++) for (var i = 0; i < ItemCount; i++)

View File

@ -42,8 +42,7 @@ public class DbFile
public Entry(BinaryReader reader) public Entry(BinaryReader reader)
{ {
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(12)).Replace("\0", string.Empty); Name = reader.ReadNullString(12);
Name = tmpName[..Math.Min(11, tmpName.Length)];
Offset = reader.ReadUInt32(); Offset = reader.ReadUInt32();
Size = reader.ReadUInt32(); Size = reader.ReadUInt32();
} }
@ -102,8 +101,8 @@ public class DbFile
"TXLIST" => new TxList(), "TXLIST" => new TxList(),
"WREXT" => new WorldRep(), "WREXT" => new WorldRep(),
"BRLIST" => new BrList(), "BRLIST" => new BrList(),
"P$ModelName" => new PropertyChunk<PropModelName>(), "P$ModelName" => new PropertyChunk<PropLabel>(),
"P$Scale" => new PropertyChunk<PropScale>(), "P$Scale" => new PropertyChunk<PropVector>(),
"P$RenderTyp" => new PropertyChunk<PropRenderType>(), "P$RenderTyp" => new PropertyChunk<PropRenderType>(),
"P$OTxtRepr0" => new PropertyChunk<PropString>(), "P$OTxtRepr0" => new PropertyChunk<PropString>(),
"P$OTxtRepr1" => new PropertyChunk<PropString>(), "P$OTxtRepr1" => new PropertyChunk<PropString>(),

View File

@ -91,8 +91,8 @@ public class ObjectHierarchy
} }
} }
AddProp<PropModelName>("P$ModelName"); AddProp<PropLabel>("P$ModelName");
AddProp<PropScale>("P$Scale"); AddProp<PropVector>("P$Scale");
AddProp<PropRenderType>("P$RenderTyp"); AddProp<PropRenderType>("P$RenderTyp");
AddProp<PropString>("P$OTxtRepr0"); AddProp<PropString>("P$OTxtRepr0");
AddProp<PropString>("P$OTxtRepr1"); AddProp<PropString>("P$OTxtRepr1");

View File

@ -6,6 +6,12 @@ namespace KeepersCompound.LGS;
public static class Extensions public static class Extensions
{ {
public static Vector3 ReadRotation(this BinaryReader reader)
{
var raw = new Vector3(reader.ReadUInt16(), reader.ReadUInt16(), reader.ReadUInt16());
return raw * 360 / (ushort.MaxValue + 1);
}
public static Vector3 ReadVec3(this BinaryReader reader) public static Vector3 ReadVec3(this BinaryReader reader)
{ {
return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
@ -16,7 +22,6 @@ public static class Extensions
return new Vector2(reader.ReadSingle(), reader.ReadSingle()); return new Vector2(reader.ReadSingle(), reader.ReadSingle());
} }
// TODO: Go through and replace all usages of string reading with this
public static string ReadNullString(this BinaryReader reader, int length) public static string ReadNullString(this BinaryReader reader, int length)
{ {
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(length)); var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(length));

View File

@ -15,7 +15,7 @@ public class ModelFile
public BHeader(BinaryReader reader) public BHeader(BinaryReader reader)
{ {
Signature = Encoding.UTF8.GetString(reader.ReadBytes(4)).Replace("\0", string.Empty); Signature = reader.ReadNullString(4);
Version = reader.ReadInt32(); Version = reader.ReadInt32();
} }
} }
@ -55,8 +55,7 @@ public class ModelFile
public MHeader(BinaryReader reader, int version) public MHeader(BinaryReader reader, int version)
{ {
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(8)).Replace("\0", string.Empty); Name = reader.ReadNullString(8);
Name = tmpName[..Math.Min(7, tmpName.Length)];
Radius = reader.ReadSingle(); Radius = reader.ReadSingle();
MaxPolygonRadius = reader.ReadSingle(); MaxPolygonRadius = reader.ReadSingle();
MaxBounds = reader.ReadVec3(); MaxBounds = reader.ReadVec3();
@ -146,7 +145,7 @@ public class ModelFile
public Material(BinaryReader reader) public Material(BinaryReader reader)
{ {
Name = Encoding.UTF8.GetString(reader.ReadBytes(16)).Replace("\0", string.Empty); Name = reader.ReadNullString(16);
Type = reader.ReadByte(); Type = reader.ReadByte();
Slot = reader.ReadByte(); Slot = reader.ReadByte();
Handle = reader.ReadUInt32(); Handle = reader.ReadUInt32();

View File

@ -246,8 +246,8 @@ public partial class Mission : Node3D
} }
var id = (int)brush.brushInfo; var id = (int)brush.brushInfo;
var modelNameProp = objHierarchy.GetProperty<PropModelName>(id, "P$ModelName"); var modelNameProp = objHierarchy.GetProperty<PropLabel>(id, "P$ModelName");
var scaleProp = objHierarchy.GetProperty<PropScale>(id, "P$Scale"); var scaleProp = objHierarchy.GetProperty<PropVector>(id, "P$Scale");
var renderTypeProp = objHierarchy.GetProperty<PropRenderType>(id, "P$RenderTyp"); var renderTypeProp = objHierarchy.GetProperty<PropRenderType>(id, "P$RenderTyp");
var txtRepl0 = objHierarchy.GetProperty<PropString>(id, "P$OTxtRepr0"); var txtRepl0 = objHierarchy.GetProperty<PropString>(id, "P$OTxtRepr0");
var txtRepl1 = objHierarchy.GetProperty<PropString>(id, "P$OTxtRepr1"); var txtRepl1 = objHierarchy.GetProperty<PropString>(id, "P$OTxtRepr1");
@ -262,11 +262,10 @@ public partial class Mission : Node3D
} }
// Let's try and place an object :) // Let's try and place an object :)
var modelName = modelNameProp.modelName + ".bin"; var modelName = modelNameProp.value + ".bin";
var pos = brush.position.ToGodotVec3(); var pos = brush.position.ToGodotVec3();
var rawRot = brush.angle; var rot = brush.angle.ToGodotVec3(false);
var rot = new Vector3(rawRot.Y, rawRot.Z, rawRot.X) * 360 / ushort.MaxValue; var scale = scaleProp == null ? Vector3.One : scaleProp.value.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 Context.Instance.ModelLoader.Load(_campaignName, modelName); return Context.Instance.ModelLoader.Load(_campaignName, modelName);

View File

@ -57,7 +57,8 @@ public class ModelLoader
var (_, path) = pathManager.GetResourcePath(ResourceType.ObjectTexture, campaignName, resName); var (_, path) = pathManager.GetResourcePath(ResourceType.ObjectTexture, campaignName, resName);
if (path == null) if (path == null)
{ {
path = "user://textures/jorge.png"; // Might fail in exported projects
path = "res://project/jorge.png";
GD.Print($"Failed to load model texture: {material.Name}"); GD.Print($"Failed to load model texture: {material.Name}");
} }

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;
@ -9,7 +8,7 @@ namespace KeepersCompound.TMV;
public partial class TextureLoader public partial class TextureLoader
{ {
private readonly string _fmName; private readonly string _fmName;
private readonly List<ImageTexture> _textureCache = new(); private readonly List<Texture2D> _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();
@ -17,15 +16,15 @@ public partial class TextureLoader
public TextureLoader(string fmName) public TextureLoader(string fmName)
{ {
_fmName = fmName; _fmName = fmName;
LoadDefaultTexture(); LoadDefaultTextures();
} }
private void LoadDefaultTexture() private void LoadDefaultTextures()
{ {
// TODO: This should be a resource loaded from RES _textureCache.Add(ResourceLoader.Load<CompressedTexture2D>("res://project/jorge.png"));
const string path = "user://textures/jorge.png"; _textureCache.Add(ResourceLoader.Load<CompressedTexture2D>("res://project/sky.png"));
var texture = ImageTexture.CreateFromImage(Image.LoadFromFile(path)); _idMap.Add(0, 0);
_textureCache.Add(texture); _idMap.Add(249, 1);
} }
public bool Register(int id, string path) public bool Register(int id, string path)
@ -73,7 +72,7 @@ public partial class TextureLoader
} }
// TODO: We should report load failures // TODO: We should report load failures
public ImageTexture Get(int id) public Texture2D Get(int id)
{ {
if (_idMap.TryGetValue(id, out int value)) if (_idMap.TryGetValue(id, out int value))
{ {
@ -89,7 +88,7 @@ public partial class TextureLoader
} }
// TODO: Load by path :) // TODO: Load by path :)
public ImageTexture Get(string path) public Texture2D Get(string path)
{ {
if (!_pathMap.ContainsKey(path)) if (!_pathMap.ContainsKey(path))
{ {

BIN
project/jorge.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

34
project/jorge.png.import Normal file
View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b208ufsau5jhb"
path="res://.godot/imported/jorge.png-d1d5cd5a7775d205c32df9d2770127a7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://project/jorge.png"
dest_files=["res://.godot/imported/jorge.png-d1d5cd5a7775d205c32df9d2770127a7.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
project/sky.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

34
project/sky.png.import Normal file
View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d1rqwnr7u0rq7"
path="res://.godot/imported/sky.png-312b3ba912840be802229125c22a74bb.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://project/sky.png"
dest_files=["res://.godot/imported/sky.png-312b3ba912840be802229125c22a74bb.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1