Initial commit

This commit is contained in:
Jarrod Doyle 2024-07-21 22:13:31 +01:00
commit 2e841276f3
Signed by: Jayrude
GPG Key ID: 38B57B16E7C0ADF7
20 changed files with 911 additions and 0 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
[*.{cs,vb}]
# IDE0130: Namespace does not match folder structure
dotnet_style_namespace_match_folder = false

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Godot 4+ specific ignores
.godot/

View File

@ -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>

19
Thief Mission Viewer.sln Normal file
View File

@ -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

1
icon.svg Normal file
View File

@ -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

37
icon.svg.import Normal file
View File

@ -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

20
project.godot Normal file
View File

@ -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"

View File

@ -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);
}
}

View File

@ -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]);
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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(),
};
}
}

View File

@ -0,0 +1,10 @@
using System.IO;
namespace KeepersCompound.LGS.Database;
public interface IPersistable
{
public void Read(BinaryReader reader);
public void Write(BinaryWriter writer);
}

View File

@ -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}";
}
}

View File

@ -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());
}
}

12
project/code/LGS/Utils.cs Normal file
View File

@ -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;
}
}

117
project/code/Mission.cs Normal file
View File

@ -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);
}
}

116
project/code/camera.gd Normal file
View File

@ -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))

25
project/scenes/main.tscn Normal file
View File

@ -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