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
{
// 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
{
public string Signature;

View File

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

View File

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

View File

@ -11,8 +11,7 @@ public class GamFile : IChunk
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)];
fileName = reader.ReadNullString(256);
}
public void WriteData(BinaryWriter writer)

View File

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Text;
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)
{
base.Read(reader);
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(length)).Replace("\0", string.Empty);
modelName = tmpName[..Math.Min(length - 1, tmpName.Length)];
value = reader.ReadInt32() != 0;
}
}
public class PropScale : Property
public class PropInt : Property
{
public Vector3 scale;
public int value;
public override void Read(BinaryReader 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 string value;
public enum Effect
{
Normal, NoEffect, Terminate, Destroy,
}
public Effect effect;
public override void Read(BinaryReader reader)
{
base.Read(reader);
stringLength = reader.ReadInt32();
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)];
effect = (Effect)reader.ReadUInt32();
}
}
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)
{
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)
{
Tokens = reader.ReadBytes(4);
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(16)).Replace("\0", string.Empty);
Name = tmpName[..Math.Min(15, tmpName.Length)];
Name = reader.ReadNullString(16);
}
}
@ -37,8 +36,7 @@ public class TxList : IChunk
Tokens = new string[TokenCount];
for (var i = 0; i < TokenCount; i++)
{
var tmpToken = Encoding.UTF8.GetString(reader.ReadBytes(16)).Replace("\0", string.Empty);
Tokens[i] = tmpToken[..Math.Min(16, tmpToken.Length)];
Tokens[i] = reader.ReadNullString(16);
}
Items = new Item[ItemCount];
for (var i = 0; i < ItemCount; i++)

View File

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

View File

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

View File

@ -6,6 +6,12 @@ namespace KeepersCompound.LGS;
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)
{
return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
@ -16,7 +22,6 @@ public static class Extensions
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)
{
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(length));

View File

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

View File

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

View File

@ -57,7 +57,8 @@ public class ModelLoader
var (_, path) = pathManager.GetResourcePath(ResourceType.ObjectTexture, campaignName, resName);
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}");
}

View File

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