Initial commit
This commit is contained in:
commit
2e841276f3
|
@ -0,0 +1,4 @@
|
||||||
|
[*.{cs,vb}]
|
||||||
|
|
||||||
|
# IDE0130: Namespace does not match folder structure
|
||||||
|
dotnet_style_namespace_match_folder = false
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Normalize EOL for all files that Git considers text files.
|
||||||
|
* text=auto eol=lf
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Godot 4+ specific ignores
|
||||||
|
.godot/
|
|
@ -0,0 +1,9 @@
|
||||||
|
<Project Sdk="Godot.NET.Sdk/4.2.2">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
|
||||||
|
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
|
||||||
|
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||||
|
<RootNamespace>ThiefMissionViewer</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
|
@ -0,0 +1,19 @@
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 2012
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thief Mission Viewer", "Thief Mission Viewer.csproj", "{D0FB3ED6-2135-4056-A00B-78F54E8A3C45}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||||
|
ExportRelease|Any CPU = ExportRelease|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{D0FB3ED6-2135-4056-A00B-78F54E8A3C45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D0FB3ED6-2135-4056-A00B-78F54E8A3C45}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D0FB3ED6-2135-4056-A00B-78F54E8A3C45}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||||
|
{D0FB3ED6-2135-4056-A00B-78F54E8A3C45}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||||
|
{D0FB3ED6-2135-4056-A00B-78F54E8A3C45}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
||||||
|
{D0FB3ED6-2135-4056-A00B-78F54E8A3C45}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1 @@
|
||||||
|
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="124" height="124" rx="14" fill="#363d52" stroke="#212532" stroke-width="4"/><g transform="scale(.101) translate(122 122)"><g fill="#fff"><path d="M105 673v33q407 354 814 0v-33z"/><path d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z" fill="#478cbf"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></g></svg>
|
After Width: | Height: | Size: 949 B |
|
@ -0,0 +1,37 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://b64hn7rvvw68c"
|
||||||
|
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://icon.svg"
|
||||||
|
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.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
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
|
@ -0,0 +1,20 @@
|
||||||
|
; Engine configuration file.
|
||||||
|
; It's best edited using the editor UI and not directly,
|
||||||
|
; since the parameters that go here are not all obvious.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
|
config_version=5
|
||||||
|
|
||||||
|
[application]
|
||||||
|
|
||||||
|
config/name="Thief Mission Viewer"
|
||||||
|
run/main_scene="res://project/scenes/main.tscn"
|
||||||
|
config/features=PackedStringArray("4.2", "C#", "Forward Plus")
|
||||||
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
|
[dotnet]
|
||||||
|
|
||||||
|
project/assembly_name="Thief Mission Viewer"
|
|
@ -0,0 +1,68 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database;
|
||||||
|
|
||||||
|
public struct ChunkHeader
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public Version Version { get; set; }
|
||||||
|
|
||||||
|
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)];
|
||||||
|
Version = new(reader);
|
||||||
|
reader.ReadBytes(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
var writeBytes = new byte[12];
|
||||||
|
var nameBytes = Encoding.UTF8.GetBytes(Name);
|
||||||
|
nameBytes[..Math.Min(12, nameBytes.Length)].CopyTo(writeBytes, 0);
|
||||||
|
writer.Write(writeBytes);
|
||||||
|
Version.Write(writer);
|
||||||
|
writer.Write(new byte[4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IChunk
|
||||||
|
{
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
|
||||||
|
public void Read(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
reader.BaseStream.Seek(entry.Offset, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
Header = new(reader);
|
||||||
|
ReadData(reader, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
Header.Write(writer);
|
||||||
|
WriteData(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry);
|
||||||
|
public abstract void WriteData(BinaryWriter writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GenericChunk : IChunk
|
||||||
|
{
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public byte[] Data { get; set; }
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
Data = reader.ReadBytes((int)entry.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(Data);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
class AiConverseChunk : IChunk
|
||||||
|
{
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public uint Count;
|
||||||
|
public uint[] Ids;
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
Count = reader.ReadUInt32();
|
||||||
|
Ids = new uint[Count];
|
||||||
|
for (var i = 0; i < Count; i++)
|
||||||
|
{
|
||||||
|
Ids[i] = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(Count);
|
||||||
|
for (var i = 0; i < Count; i++)
|
||||||
|
{
|
||||||
|
writer.Write(Ids[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
class AiRoomDb : IChunk
|
||||||
|
{
|
||||||
|
public struct Cell
|
||||||
|
{
|
||||||
|
int Size { get; set; }
|
||||||
|
uint[] CellIds { get; set; }
|
||||||
|
|
||||||
|
public Cell(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Size = reader.ReadInt32();
|
||||||
|
CellIds = new uint[Size];
|
||||||
|
for (var i = 0; i < Size; i++)
|
||||||
|
{
|
||||||
|
CellIds[i] = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(Size);
|
||||||
|
for (var i = 0; i < Size; i++)
|
||||||
|
{
|
||||||
|
writer.Write(CellIds[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public bool IsEmpty { get; set; }
|
||||||
|
public int ValidCellCount { get; set; }
|
||||||
|
public int CellCount { get; set; }
|
||||||
|
public Cell[] Cells { get; set; }
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
IsEmpty = reader.ReadBoolean();
|
||||||
|
reader.ReadBytes(3);
|
||||||
|
ValidCellCount = reader.ReadInt32();
|
||||||
|
CellCount = reader.ReadInt16();
|
||||||
|
Cells = new Cell[CellCount];
|
||||||
|
for (var i = 0; i < CellCount; i++)
|
||||||
|
{
|
||||||
|
Cells[i] = new Cell(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(IsEmpty);
|
||||||
|
writer.Write(new byte[3]);
|
||||||
|
writer.Write(ValidCellCount);
|
||||||
|
writer.Write(CellCount);
|
||||||
|
for (var i = 0; i < CellCount; i++)
|
||||||
|
{
|
||||||
|
Cells[i].Write(writer);
|
||||||
|
}
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
public class WorldRep : IChunk
|
||||||
|
{
|
||||||
|
public struct WrHeader
|
||||||
|
{
|
||||||
|
// Extended header content
|
||||||
|
public int Size { get; set; }
|
||||||
|
public int Version { get; set; }
|
||||||
|
public int Flags { get; set; }
|
||||||
|
public uint LightmapFormat { get; set; }
|
||||||
|
public int LightmapScale { get; set; }
|
||||||
|
|
||||||
|
// Standard header
|
||||||
|
public uint DataSize { get; set; }
|
||||||
|
public uint CellCount { get; set; }
|
||||||
|
|
||||||
|
public WrHeader(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Size = reader.ReadInt32();
|
||||||
|
Version = reader.ReadInt32();
|
||||||
|
Flags = reader.ReadInt32();
|
||||||
|
LightmapFormat = reader.ReadUInt32();
|
||||||
|
LightmapScale = reader.ReadInt32();
|
||||||
|
DataSize = reader.ReadUInt32();
|
||||||
|
CellCount = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Cell
|
||||||
|
{
|
||||||
|
public struct Poly
|
||||||
|
{
|
||||||
|
public byte Flags { get; set; }
|
||||||
|
public byte VertexCount { get; set; }
|
||||||
|
public byte PlaneId { get; set; }
|
||||||
|
public byte ClutId { get; set; }
|
||||||
|
public ushort Destination { get; set; }
|
||||||
|
public byte MotionIndex { get; set; }
|
||||||
|
|
||||||
|
public Poly(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Flags = reader.ReadByte();
|
||||||
|
VertexCount = reader.ReadByte();
|
||||||
|
PlaneId = reader.ReadByte();
|
||||||
|
ClutId = reader.ReadByte();
|
||||||
|
Destination = reader.ReadUInt16();
|
||||||
|
MotionIndex = reader.ReadByte();
|
||||||
|
reader.ReadByte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct RenderPoly
|
||||||
|
{
|
||||||
|
public (Vector3, Vector3) TextureVectors { get; set; }
|
||||||
|
public (float, float) TextureBases { get; set; }
|
||||||
|
public ushort TextureId { get; set; }
|
||||||
|
public ushort CachedSurface { get; set; }
|
||||||
|
public float TextureMagnitude { get; set; }
|
||||||
|
public Vector3 Center { get; set; }
|
||||||
|
|
||||||
|
public RenderPoly(BinaryReader reader)
|
||||||
|
{
|
||||||
|
TextureVectors = (reader.ReadVec3(), reader.ReadVec3());
|
||||||
|
TextureBases = (reader.ReadSingle(), reader.ReadSingle());
|
||||||
|
TextureId = reader.ReadUInt16();
|
||||||
|
CachedSurface = reader.ReadUInt16();
|
||||||
|
TextureMagnitude = reader.ReadSingle();
|
||||||
|
Center = reader.ReadVec3();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct LightmapInfo
|
||||||
|
{
|
||||||
|
public (short, short) Bases { get; set; }
|
||||||
|
public short PaddedWidth { get; set; }
|
||||||
|
public byte Height { get; set; }
|
||||||
|
public byte Width { get; set; }
|
||||||
|
public uint DataPtr { get; set; }
|
||||||
|
public uint DynamicLightPtr { get; set; }
|
||||||
|
public uint AnimLightBitmask { get; set; }
|
||||||
|
|
||||||
|
public LightmapInfo(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Bases = (reader.ReadInt16(), reader.ReadInt16());
|
||||||
|
PaddedWidth = reader.ReadInt16();
|
||||||
|
Height = reader.ReadByte();
|
||||||
|
Width = reader.ReadByte();
|
||||||
|
DataPtr = reader.ReadUInt32();
|
||||||
|
DynamicLightPtr = reader.ReadUInt32();
|
||||||
|
AnimLightBitmask = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Lightmap
|
||||||
|
{
|
||||||
|
public byte[,,,] Pixels { get; set; }
|
||||||
|
|
||||||
|
public Lightmap(BinaryReader reader, byte width, byte height, uint bitmask, int bytesPerPixel)
|
||||||
|
{
|
||||||
|
var count = 1 + BitOperations.PopCount(bitmask);
|
||||||
|
Pixels = new byte[count, height, width, bytesPerPixel];
|
||||||
|
for (var c = 0; c < count; c++)
|
||||||
|
{
|
||||||
|
for (var y = 0; y < height; y++)
|
||||||
|
{
|
||||||
|
for (var x = 0; x < width; x++)
|
||||||
|
{
|
||||||
|
for (var b = 0; b < bytesPerPixel; b++)
|
||||||
|
{
|
||||||
|
Pixels[c, y, x, b] = reader.ReadByte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte VertexCount { get; set; }
|
||||||
|
public byte PolyCount { get; set; }
|
||||||
|
public byte RenderPolyCount { get; set; }
|
||||||
|
public byte PortalPolyCount { get; set; }
|
||||||
|
public byte PlaneCount { get; set; }
|
||||||
|
public byte Medium { get; set; }
|
||||||
|
public byte Flags { get; set; }
|
||||||
|
public int PortalVertices { get; set; }
|
||||||
|
public ushort NumVList { get; set; }
|
||||||
|
public byte AnimLightCount { get; set; }
|
||||||
|
public byte MotionIndex { get; set; }
|
||||||
|
public Vector3 SphereCenter { get; set; }
|
||||||
|
public float SphereRadius { get; set; }
|
||||||
|
public Vector3[] Vertices { get; set; }
|
||||||
|
public Poly[] Polys { get; set; }
|
||||||
|
public RenderPoly[] RenderPolys { get; set; }
|
||||||
|
public uint IndexCount { get; set; }
|
||||||
|
public byte[] Indices { get; set; }
|
||||||
|
public Plane[] Planes { get; set; }
|
||||||
|
public ushort[] AnimLights { get; set; }
|
||||||
|
public LightmapInfo[] LightList { get; set; }
|
||||||
|
public Lightmap[] Lightmaps { get; set; }
|
||||||
|
public int LightIndexCount { get; set; }
|
||||||
|
public ushort[] LightIndices { get; set; }
|
||||||
|
|
||||||
|
public Cell(BinaryReader reader)
|
||||||
|
{
|
||||||
|
VertexCount = reader.ReadByte();
|
||||||
|
PolyCount = reader.ReadByte();
|
||||||
|
RenderPolyCount = reader.ReadByte();
|
||||||
|
PortalPolyCount = reader.ReadByte();
|
||||||
|
PlaneCount = reader.ReadByte();
|
||||||
|
Medium = reader.ReadByte();
|
||||||
|
Flags = reader.ReadByte();
|
||||||
|
PortalVertices = reader.ReadInt32();
|
||||||
|
NumVList = reader.ReadUInt16();
|
||||||
|
AnimLightCount = reader.ReadByte();
|
||||||
|
MotionIndex = reader.ReadByte();
|
||||||
|
SphereCenter = reader.ReadVec3();
|
||||||
|
SphereRadius = reader.ReadSingle();
|
||||||
|
Vertices = new Vector3[VertexCount];
|
||||||
|
for (var i = 0; i < VertexCount; i++)
|
||||||
|
{
|
||||||
|
Vertices[i] = reader.ReadVec3();
|
||||||
|
}
|
||||||
|
Polys = new Poly[PolyCount];
|
||||||
|
for (var i = 0; i < PolyCount; i++)
|
||||||
|
{
|
||||||
|
Polys[i] = new Poly(reader);
|
||||||
|
}
|
||||||
|
RenderPolys = new RenderPoly[RenderPolyCount];
|
||||||
|
for (var i = 0; i < RenderPolyCount; i++)
|
||||||
|
{
|
||||||
|
RenderPolys[i] = new RenderPoly(reader);
|
||||||
|
}
|
||||||
|
IndexCount = reader.ReadUInt32();
|
||||||
|
Indices = new byte[IndexCount];
|
||||||
|
for (var i = 0; i < IndexCount; i++)
|
||||||
|
{
|
||||||
|
Indices[i] = reader.ReadByte();
|
||||||
|
}
|
||||||
|
Planes = new Plane[PlaneCount];
|
||||||
|
for (var i = 0; i < PlaneCount; i++)
|
||||||
|
{
|
||||||
|
Planes[i] = new Plane(reader.ReadVec3(), reader.ReadSingle());
|
||||||
|
}
|
||||||
|
AnimLights = new ushort[AnimLightCount];
|
||||||
|
for (var i = 0; i < AnimLightCount; i++)
|
||||||
|
{
|
||||||
|
AnimLights[i] = reader.ReadUInt16();
|
||||||
|
}
|
||||||
|
LightList = new LightmapInfo[RenderPolyCount];
|
||||||
|
for (var i = 0; i < RenderPolyCount; i++)
|
||||||
|
{
|
||||||
|
LightList[i] = new LightmapInfo(reader);
|
||||||
|
}
|
||||||
|
Lightmaps = new Lightmap[RenderPolyCount];
|
||||||
|
for (var i = 0; i < RenderPolyCount; i++)
|
||||||
|
{
|
||||||
|
var info = LightList[i];
|
||||||
|
var bpp = 4; // TODO: WREXT only!
|
||||||
|
Lightmaps[i] = new Lightmap(reader, info.Width, info.Height, info.AnimLightBitmask, bpp);
|
||||||
|
}
|
||||||
|
LightIndexCount = reader.ReadInt32();
|
||||||
|
LightIndices = new ushort[LightIndexCount];
|
||||||
|
for (var i = 0; i < LightIndexCount; i++)
|
||||||
|
{
|
||||||
|
LightIndices[i] = reader.ReadUInt16();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public WrHeader DataHeader { get; set; }
|
||||||
|
public Cell[] Cells { get; set; }
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
DataHeader = new(reader);
|
||||||
|
Cells = new Cell[DataHeader.CellCount];
|
||||||
|
for (var i = 0; i < DataHeader.CellCount; i++)
|
||||||
|
{
|
||||||
|
Cells[i] = new Cell(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: All the other info lol
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database;
|
||||||
|
|
||||||
|
public class DbFile
|
||||||
|
{
|
||||||
|
public struct FHeader
|
||||||
|
{
|
||||||
|
public uint TocOffset { get; set; }
|
||||||
|
public Version Version { get; }
|
||||||
|
public string Deadbeef { get; }
|
||||||
|
|
||||||
|
public FHeader(BinaryReader reader)
|
||||||
|
{
|
||||||
|
TocOffset = reader.ReadUInt32();
|
||||||
|
Version = new Version(reader);
|
||||||
|
reader.ReadBytes(256);
|
||||||
|
Deadbeef = BitConverter.ToString(reader.ReadBytes(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(TocOffset);
|
||||||
|
Version.Write(writer);
|
||||||
|
writer.Write(new byte[256]);
|
||||||
|
writer.Write(Array.ConvertAll(Deadbeef.Split('-'), s => byte.Parse(s, System.Globalization.NumberStyles.HexNumber)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct TableOfContents
|
||||||
|
{
|
||||||
|
public readonly struct Entry
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public uint Offset { get; }
|
||||||
|
public uint Size { get; }
|
||||||
|
|
||||||
|
public Entry(BinaryReader reader)
|
||||||
|
{
|
||||||
|
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(12)).Replace("\0", string.Empty);
|
||||||
|
Name = tmpName[..Math.Min(11, tmpName.Length)];
|
||||||
|
Offset = reader.ReadUInt32();
|
||||||
|
Size = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
// return $"Name: {Name}, Offset: {O}"
|
||||||
|
return base.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint ItemCount { get; }
|
||||||
|
public List<Entry> Items { get; }
|
||||||
|
|
||||||
|
public TableOfContents(BinaryReader reader)
|
||||||
|
{
|
||||||
|
ItemCount = reader.ReadUInt32();
|
||||||
|
Items = new List<Entry>();
|
||||||
|
for (var i = 0; i < ItemCount; i++)
|
||||||
|
Items.Add(new Entry(reader));
|
||||||
|
Items.Sort((a, b) => a.Offset.CompareTo(b.Offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FHeader Header { get; private set; }
|
||||||
|
public TableOfContents Toc { get; }
|
||||||
|
public Dictionary<string, IChunk> Chunks { get; set; }
|
||||||
|
|
||||||
|
public DbFile(string filename)
|
||||||
|
{
|
||||||
|
if (!File.Exists(filename)) return;
|
||||||
|
|
||||||
|
using MemoryStream stream = new(File.ReadAllBytes(filename));
|
||||||
|
using BinaryReader reader = new(stream, Encoding.UTF8, false);
|
||||||
|
|
||||||
|
Header = new(reader);
|
||||||
|
stream.Seek(Header.TocOffset, SeekOrigin.Begin);
|
||||||
|
Toc = new(reader);
|
||||||
|
|
||||||
|
Chunks = new Dictionary<string, IChunk>();
|
||||||
|
foreach (var entry in Toc.Items)
|
||||||
|
{
|
||||||
|
var chunk = NewChunk(entry.Name);
|
||||||
|
chunk.Read(reader, entry);
|
||||||
|
Chunks.Add(entry.Name, chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IChunk NewChunk(string entryName)
|
||||||
|
{
|
||||||
|
return entryName switch
|
||||||
|
{
|
||||||
|
"AI_ROOM_DB" => new AiRoomDb(),
|
||||||
|
"AICONVERSE" => new AiConverseChunk(),
|
||||||
|
"WREXT" => new WorldRep(),
|
||||||
|
_ => new GenericChunk(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database;
|
||||||
|
|
||||||
|
public interface IPersistable
|
||||||
|
{
|
||||||
|
public void Read(BinaryReader reader);
|
||||||
|
|
||||||
|
public void Write(BinaryWriter writer);
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database;
|
||||||
|
|
||||||
|
public struct Version
|
||||||
|
{
|
||||||
|
public uint Major { get; set; }
|
||||||
|
public uint Minor { get; set; }
|
||||||
|
|
||||||
|
public Version(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Major = reader.ReadUInt32();
|
||||||
|
Minor = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(Major);
|
||||||
|
writer.Write(Minor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly string ToString()
|
||||||
|
{
|
||||||
|
return $"{Major}.{Minor}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS;
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static Vector3 ReadVec3(this BinaryReader reader)
|
||||||
|
{
|
||||||
|
return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS;
|
||||||
|
|
||||||
|
|
||||||
|
public static class Utils
|
||||||
|
{
|
||||||
|
public static Godot.Vector3 ToGodotVec3(this Vector3 vec, float inverseScale)
|
||||||
|
{
|
||||||
|
return new Godot.Vector3(vec.Y, vec.Z, vec.X) / inverseScale;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
using Godot;
|
||||||
|
using KeepersCompound.LGS;
|
||||||
|
using KeepersCompound.LGS.Database;
|
||||||
|
using KeepersCompound.LGS.Database.Chunks;
|
||||||
|
using Microsoft.VisualBasic;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace KeepersCompound;
|
||||||
|
|
||||||
|
[Tool]
|
||||||
|
public partial class Mission : Node3D
|
||||||
|
{
|
||||||
|
[Export(PropertyHint.GlobalFile, "*.mis")]
|
||||||
|
public string FileName { get; set; }
|
||||||
|
[Export]
|
||||||
|
public bool Build = false;
|
||||||
|
|
||||||
|
|
||||||
|
DbFile _file;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
RebuildMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
if (Build)
|
||||||
|
{
|
||||||
|
RebuildMap();
|
||||||
|
Build = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Input(InputEvent @event)
|
||||||
|
{
|
||||||
|
if (@event is InputEventKey keyEvent && keyEvent.Pressed)
|
||||||
|
{
|
||||||
|
if (keyEvent.Keycode == Key.R)
|
||||||
|
{
|
||||||
|
Build = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RebuildMap()
|
||||||
|
{
|
||||||
|
foreach (var node in GetChildren())
|
||||||
|
{
|
||||||
|
node.QueueFree();
|
||||||
|
}
|
||||||
|
|
||||||
|
_file = new(FileName);
|
||||||
|
var wr = (WorldRep)_file.Chunks["WREXT"];
|
||||||
|
|
||||||
|
foreach (var cell in wr.Cells)
|
||||||
|
{
|
||||||
|
BuildCellMesh(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildCellMesh(WorldRep.Cell cell)
|
||||||
|
{
|
||||||
|
var numPolys = cell.PolyCount;
|
||||||
|
var numRenderPolys = cell.RenderPolyCount;
|
||||||
|
var numPortalPolys = cell.PortalPolyCount;
|
||||||
|
|
||||||
|
var vertices = new List<Vector3>();
|
||||||
|
var normals = new List<Vector3>();
|
||||||
|
var idxOffset = 0;
|
||||||
|
for (int i = 0; i < numPolys; i++)
|
||||||
|
{
|
||||||
|
var poly = cell.Polys[i];
|
||||||
|
// TODO: Make this break lol
|
||||||
|
if (i >= numRenderPolys || i >= numPolys - numPortalPolys)
|
||||||
|
{
|
||||||
|
idxOffset += poly.VertexCount;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var plane = cell.Planes[poly.PlaneId];
|
||||||
|
var normal = plane.Normal;
|
||||||
|
|
||||||
|
var numPolyVertices = poly.VertexCount;
|
||||||
|
for (int j = 1; j < numPolyVertices - 1; j++)
|
||||||
|
{
|
||||||
|
foreach (var offset in new int[] { 0, j, j + 1 })
|
||||||
|
// foreach (var offset in new int[] { j + 1, j, 0 })
|
||||||
|
{
|
||||||
|
var vertex = cell.Vertices[cell.Indices[idxOffset + offset]];
|
||||||
|
vertices.Add(vertex.ToGodotVec3(4.0f));
|
||||||
|
normals.Add(normal.ToGodotVec3(4.0f));
|
||||||
|
// vertices.Add(new Vector3(vertex.Y, vertex.Z, vertex.X));
|
||||||
|
// normals.Add(new Vector3(normal.Y, normal.Z, normal.X));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idxOffset += poly.VertexCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
var arrMesh = new ArrayMesh();
|
||||||
|
var arrays = new Godot.Collections.Array();
|
||||||
|
arrays.Resize((int)Mesh.ArrayType.Max);
|
||||||
|
arrays[(int)Mesh.ArrayType.Vertex] = vertices.ToArray();
|
||||||
|
arrays[(int)Mesh.ArrayType.Normal] = normals.ToArray();
|
||||||
|
|
||||||
|
arrMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, arrays);
|
||||||
|
var meshInstance = new MeshInstance3D
|
||||||
|
{
|
||||||
|
Mesh = arrMesh,
|
||||||
|
CastShadow = GeometryInstance3D.ShadowCastingSetting.On
|
||||||
|
};
|
||||||
|
|
||||||
|
AddChild(meshInstance);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
class_name FreeLookCamera extends Camera3D
|
||||||
|
|
||||||
|
# Modifier keys' speed multiplier
|
||||||
|
const SHIFT_MULTIPLIER = 2.5
|
||||||
|
const ALT_MULTIPLIER = 1.0 / SHIFT_MULTIPLIER
|
||||||
|
|
||||||
|
|
||||||
|
@export_range(0.0, 1.0) var sensitivity: float = 0.25
|
||||||
|
|
||||||
|
# Mouse state
|
||||||
|
var _mouse_position = Vector2(0.0, 0.0)
|
||||||
|
var _total_pitch = 0.0
|
||||||
|
|
||||||
|
# Movement state
|
||||||
|
var _direction = Vector3(0.0, 0.0, 0.0)
|
||||||
|
var _velocity = Vector3(0.0, 0.0, 0.0)
|
||||||
|
var _acceleration = 30
|
||||||
|
var _deceleration = -10
|
||||||
|
var _vel_multiplier = 4
|
||||||
|
|
||||||
|
# Keyboard state
|
||||||
|
var _w = false
|
||||||
|
var _s = false
|
||||||
|
var _a = false
|
||||||
|
var _d = false
|
||||||
|
var _q = false
|
||||||
|
var _e = false
|
||||||
|
var _shift = false
|
||||||
|
var _alt = false
|
||||||
|
|
||||||
|
func _input(event):
|
||||||
|
# Receives mouse motion
|
||||||
|
if event is InputEventMouseMotion:
|
||||||
|
_mouse_position = event.relative
|
||||||
|
|
||||||
|
# Receives mouse button input
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
match event.button_index:
|
||||||
|
MOUSE_BUTTON_RIGHT: # Only allows rotation if right click down
|
||||||
|
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED if event.pressed else Input.MOUSE_MODE_VISIBLE)
|
||||||
|
MOUSE_BUTTON_WHEEL_UP: # Increases max velocity
|
||||||
|
_vel_multiplier = clamp(_vel_multiplier * 1.1, 0.2, 20)
|
||||||
|
MOUSE_BUTTON_WHEEL_DOWN: # Decereases max velocity
|
||||||
|
_vel_multiplier = clamp(_vel_multiplier / 1.1, 0.2, 20)
|
||||||
|
|
||||||
|
# Receives key input
|
||||||
|
if event is InputEventKey:
|
||||||
|
match event.keycode:
|
||||||
|
KEY_W:
|
||||||
|
_w = event.pressed
|
||||||
|
KEY_S:
|
||||||
|
_s = event.pressed
|
||||||
|
KEY_A:
|
||||||
|
_a = event.pressed
|
||||||
|
KEY_D:
|
||||||
|
_d = event.pressed
|
||||||
|
KEY_Q:
|
||||||
|
_q = event.pressed
|
||||||
|
KEY_E:
|
||||||
|
_e = event.pressed
|
||||||
|
KEY_SHIFT:
|
||||||
|
_shift = event.pressed
|
||||||
|
KEY_ALT:
|
||||||
|
_alt = event.pressed
|
||||||
|
|
||||||
|
# Updates mouselook and movement every frame
|
||||||
|
func _process(delta):
|
||||||
|
_update_mouselook()
|
||||||
|
_update_movement(delta)
|
||||||
|
|
||||||
|
# Updates camera movement
|
||||||
|
func _update_movement(delta):
|
||||||
|
# Computes desired direction from key states
|
||||||
|
_direction = Vector3(
|
||||||
|
(_d as float) - (_a as float),
|
||||||
|
(_e as float) - (_q as float),
|
||||||
|
(_s as float) - (_w as float)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Computes the change in velocity due to desired direction and "drag"
|
||||||
|
# The "drag" is a constant acceleration on the camera to bring it's velocity to 0
|
||||||
|
var offset = _direction.normalized() * _acceleration * _vel_multiplier * delta \
|
||||||
|
+ _velocity.normalized() * _deceleration * _vel_multiplier * delta
|
||||||
|
|
||||||
|
# Compute modifiers' speed multiplier
|
||||||
|
var speed_multi = 1
|
||||||
|
if _shift: speed_multi *= SHIFT_MULTIPLIER
|
||||||
|
if _alt: speed_multi *= ALT_MULTIPLIER
|
||||||
|
|
||||||
|
# Checks if we should bother translating the camera
|
||||||
|
if _direction == Vector3.ZERO and offset.length_squared() > _velocity.length_squared():
|
||||||
|
# Sets the velocity to 0 to prevent jittering due to imperfect deceleration
|
||||||
|
_velocity = Vector3.ZERO
|
||||||
|
else:
|
||||||
|
# Clamps speed to stay within maximum value (_vel_multiplier)
|
||||||
|
_velocity.x = clamp(_velocity.x + offset.x, -_vel_multiplier, _vel_multiplier)
|
||||||
|
_velocity.y = clamp(_velocity.y + offset.y, -_vel_multiplier, _vel_multiplier)
|
||||||
|
_velocity.z = clamp(_velocity.z + offset.z, -_vel_multiplier, _vel_multiplier)
|
||||||
|
|
||||||
|
translate(_velocity * delta * speed_multi)
|
||||||
|
|
||||||
|
# Updates mouse look
|
||||||
|
func _update_mouselook():
|
||||||
|
# Only rotates mouse if the mouse is captured
|
||||||
|
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
||||||
|
_mouse_position *= sensitivity
|
||||||
|
var yaw = _mouse_position.x
|
||||||
|
var pitch = _mouse_position.y
|
||||||
|
_mouse_position = Vector2(0, 0)
|
||||||
|
|
||||||
|
# Prevents looking up/down too far
|
||||||
|
pitch = clamp(pitch, -90 - _total_pitch, 90 - _total_pitch)
|
||||||
|
_total_pitch += pitch
|
||||||
|
|
||||||
|
rotate_y(deg_to_rad(-yaw))
|
||||||
|
rotate_object_local(Vector3(1,0,0), deg_to_rad(-pitch))
|
|
@ -0,0 +1,25 @@
|
||||||
|
[gd_scene load_steps=4 format=3 uid="uid://boxi211q3kx6c"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://project/code/Mission.cs" id="1_xhqt7"]
|
||||||
|
[ext_resource type="Script" path="res://project/code/camera.gd" id="2_w5otl"]
|
||||||
|
|
||||||
|
[sub_resource type="Environment" id="Environment_oxkvl"]
|
||||||
|
|
||||||
|
[node name="Main" type="Node3D"]
|
||||||
|
|
||||||
|
[node name="Mission" type="Node3D" parent="."]
|
||||||
|
script = ExtResource("1_xhqt7")
|
||||||
|
FileName = "/home/jarrod/Dev/thief/de-specs/test_data/wr-test.cow"
|
||||||
|
|
||||||
|
[node name="Camera3D" type="Camera3D" parent="."]
|
||||||
|
script = ExtResource("2_w5otl")
|
||||||
|
|
||||||
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||||
|
environment = SubResource("Environment_oxkvl")
|
||||||
|
|
||||||
|
[node name="OmniLight3D" type="OmniLight3D" parent="."]
|
||||||
|
shadow_enabled = true
|
||||||
|
|
||||||
|
[node name="OmniLight3D3" type="OmniLight3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.509499, 0.98353, 1.1662)
|
||||||
|
shadow_enabled = true
|
Loading…
Reference in New Issue