Skip to content

Commit

Permalink
(Wayforward) Experimental Collision Support
Browse files Browse the repository at this point in the history
  • Loading branch information
Knuxfan24 committed Oct 2, 2024
1 parent 52b31bc commit 2c82ad9
Show file tree
Hide file tree
Showing 4 changed files with 392 additions and 0 deletions.
357 changes: 357 additions & 0 deletions KnuxLib/Engines/Wayforward/Collision.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
namespace KnuxLib.Engines.Wayforward
{
// TODO: Format saving.
// TODO: Figure out the unknown behaviour flags for the different versions of this format.
// Especially the Ducktales Remastered ones, as I've never even played it and am trying to go off videos at a quick glance.
// TODO: Figure out the unknown values in non Shantae: Half Genie Hero versions of this format.
// TODO: Figure out the massive chunk of data that controls screen transitions(?) in Shantae and the Seven Sirens.
// TODO: Format importing.
public class Collision : FileBase
{
// Generic VS stuff to allow creating an object that instantly loads a file.
public Collision() { }
public Collision(string filepath, FormatVersion version = FormatVersion.hero, bool bigEndian = false, bool export = false)
{
// Load this file.
Load(filepath, version, bigEndian);

// If the export flag is set, then export this format.
if (export)
ExportOBJ($@"{Helpers.GetExtension(filepath, true)}.obj");
}

// Classes for this format.
public enum FormatVersion
{
/// <summary>
/// Ducktales Remastered.
/// </summary>
duck = 0,

/// <summary>
/// Shantae: Half-Genie Hero.
/// </summary>
hero = 1,

/// <summary>
/// Shantae and the Seven Sirens.
/// </summary>
sevensirens = 2
}

[Flags]
public enum Behaviour_Duck
{
// TODO: Some models in exeanm.clb have a behaviour value of 0?
Solid = 0x00000001,
Unknown1 = 0x00000010,
BottomlessPit = 0x00000020,
Water = 0x00000040,
Unknown2 = 0x00000080, // Area transition maybe? Just based on placements.
Rail = 0x00000100, // Not all rails seem to have this? Just some end pieces?
Unknown3 = 0x00000800, // Only used on a single block in vesuvius.clb
Snow = 0x00100000, // Only used through himalayas.clb, verify that this is correct.
Ice = 0x00200000, // Only used through himalayas.clb, verify that this is correct.
Unknown4 = 0x00800000 // Only used on two platforms in amazon.clb, one block in himalayas.clb and a roof in moon.clb
}

[Flags]
public enum Behaviour_Hero : uint
{
Solid = 0x00000001,
TopSolid = 0x00000002,
Boundry = 0x00000004, // TODO: Confirm
Spikes = 0x00000008,
NoMonkey = 0x00000010,
BottomlessPit = 0x00000020,
Unknown1 = 0x00000100,
WoodSound = 0x00000400,
Unknown2 = 0x00004000, // Often found out of bounds?
Slide = 0x00400000,
Unknown3 = 0x00800000, // Only used on a single block at the top of Mermaid Falls Act 1? Something to do with the mouse maybe?
Unknown4 = 0x01000000, // Used in Tassle Town Act 1 and Hypno Baron's Castle Act 1 for the down left slopes. Also found on some walls in the Intro Caves.
Unknown5 = 0x02000000, // Used in Tassle Town Act 1 and Hypno Baron's Castle Act 1 for the down right slopes. Also found on a single wall in the Intro Workshop.
Unknown6 = 0x10000000, // Used in a couple of odd spots in Burning Town Act 1 and multiple acts of Risky's Hideout.
Lava = 0x80000000 // TODO: Confirm, only assumed based on placement, as its all over Risky's Hideout and nowhere else.
}

[Flags]
public enum Behaviour_SevenSirens
{
Solid = 0x00000001,
TopSolid = 0x00000002,
Unknown1 = 0x00000005, // Often placed on weird corners? Always has the NoNewt tag as well for some reason.
Spikes = 0x00000008,
NoNewt = 0x00000010,
BottomlessPit = 0x00000020,
DamageZone = 0x00000040, // Has to be paired with Water to use.
HealingZone = 0x00000400, // Has to be paired with Water to use.
DrillZone = 0x00008000, // How does this one actually work?
Water = 0x00200000,
Unknown2 = 0x10000000, // Seems to be some sort of door way almost? Just based on placement.
Unknown3 = 0x20000000 // Only used on four pillars in level_labyrinth_02.clb.
}

public class FormatData
{
public Model[] Models { get; set; } = [];

public byte[]? ScreenTransition { get; set; }
}

public class Model
{
/// <summary>
/// This model's axis aligned bounding box.
/// </summary>
public AABB? AxisAlignedBoundingBox { get; set; }

/// <summary>
/// An unknown Vector3 value that is only present in Ducktales Remastered.
/// TODO: What is this? Do this and the next value replace the AABB in some way?
/// </summary>
public Vector3? UnknownVector3_1 { get; set; }

/// <summary>
/// An unknown integer value that is only present in Ducktales Remastered.
/// TODO: What is this? Do this and the last value replace the AABB in some way?
/// </summary>
public uint? UnknownUInt32_1 { get; set; }

/// <summary>
/// The behaviour/surface type of this collision.
/// TODO: Figure out the values for each type.
/// </summary>
public object Behaviour { get; set; }

/// <summary>
/// An unknown integer value.
/// TODO: What is this? It's always 0 except for a couple of instances in Seven Sirens.
/// </summary>
public uint UnknownUInt32_2 { get; set; }

/// <summary>
/// An unknown 64 bit(?) integer value that is only present in Seven Sirens.
/// TODO: What is this?
/// </summary>
public ulong? UnknownULong_1 { get; set; }

/// <summary>
/// The coordinates for the various vertices that make up this model.
/// </summary>
public Vector3[] Vertices { get; set; } = [];

/// <summary>
/// The faces that make up this model.
/// </summary>
public Face[] Faces { get; set; } = [];

/// <summary>
/// Initialises this model with default data.
/// </summary>
public Model() { }

Check warning on line 152 in KnuxLib/Engines/Wayforward/Collision.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Behaviour' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

/// <summary>
/// Initialises this model by reading its data from a BinaryReader.
/// </summary>
public Model(ExtendedBinaryReader reader, FormatVersion version) => Read(reader, version);

Check warning on line 157 in KnuxLib/Engines/Wayforward/Collision.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Behaviour' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

/// <summary>
/// Reads the data for this model.
/// </summary>
public void Read(ExtendedBinaryReader reader, FormatVersion version)
{
// If this isn't a Ducktales Remastered format, then read this model's axis aligned bounding box.
if (version != FormatVersion.duck)
AxisAlignedBoundingBox = new(reader);

// Check if this is a Ducktales Remastered format.
else
{
// Read an unknown Vector3.
UnknownVector3_1 = reader.ReadVector3();

// Read an unknown integer value.
UnknownUInt32_1 = reader.ReadUInt32();
}

// Read this model's behaviour flag, depending on the format version.
switch (version)
{
case FormatVersion.duck: Behaviour = (Behaviour_Duck)reader.ReadUInt32(); break;
case FormatVersion.hero: Behaviour = (Behaviour_Hero)reader.ReadUInt32(); break;
case FormatVersion.sevensirens: Behaviour = (Behaviour_SevenSirens)reader.ReadUInt32(); break;
default: Behaviour = reader.ReadUInt32(); break;
}

// Read an unknown integer value.
UnknownUInt32_2 = reader.ReadUInt32();

// If this is a Shantae and the Seven Sirens format, then read an unknown long value.
if (version == FormatVersion.sevensirens)
UnknownULong_1 = reader.ReadUInt64();

// Read the offset to this model's data.
ulong modelDataOffset = reader.ReadUInt64();

// Save our current position so we can jump back for the next model.
long position = reader.BaseStream.Position;

// Jump to this model's data.
reader.JumpTo(modelDataOffset);

// Check for a value of 0.
reader.CheckValue(0x00);

// Initialise this model's vertex array.
Vertices = new Vector3[reader.ReadUInt32()];

// Read the offset to this model's vertex table, additive to modelDataOffset. This seems to always be 0x20.
ulong vertexTableOffset = reader.ReadUInt64();

// Check for a value of 0.
reader.CheckValue(0x00);

// Initialise this model's face array.
Faces = new Face[reader.ReadUInt32()];

// Read the offset to this model's face table, additive to modelDataOffset.
ulong faceTableOffset = reader.ReadUInt64();

// Jump to this model's vertex table.
reader.JumpTo(modelDataOffset + vertexTableOffset);

// Loop through and read all of this model's vertices.
for (int vertexIndex = 0; vertexIndex < Vertices.Length; vertexIndex++)
Vertices[vertexIndex] = reader.ReadVector3();

// Jump to this model's face table.
reader.JumpTo(modelDataOffset + faceTableOffset);

// Loop through and read each of this model's faces.
for (int faceIndex = 0; faceIndex < Faces.Length; faceIndex++)
{
Faces[faceIndex] = new()
{
IndexA = reader.ReadUInt32(),
IndexB = reader.ReadUInt32(),
IndexC = reader.ReadUInt32()
};
}

// Jump back for the next model.
reader.JumpTo(position);
}
}

// Actual data presented to the end user.
public FormatData Data = new();

/// <summary>
/// Loads and parses this format's file.
/// </summary>
/// <param name="filepath">The path to the file to load and parse.</param>
/// <param name="version">The format version to read this file as.</param>
/// <param name="bigEndian">Whether we need to read this file in big endian or not.</param>
public void Load(string filepath, FormatVersion version = FormatVersion.hero, bool bigEndian = false)
{
// Load this file into a BinaryReader.
ExtendedBinaryReader reader = new(File.OpenRead(filepath), bigEndian);

// Skip an unknown value that is always 0.
reader.CheckValue(0x00);

// Initialise this file's model array.
Data.Models = new Model[reader.ReadUInt32()];

// Read the offset to this file's model table.
ulong modelTableOffset = reader.ReadUInt64();

// Read whether or not this file has any screen transition data. It seems that only Shantae and the Seven Sirens does?
bool hasScreenTransitionData = reader.ReadBoolean(0x08);

// Read the offset to this file's screen transition data, will be 0 if it has none.
ulong screenTransitionTableOffset = reader.ReadUInt64();

// Realign to 0x40 bytes.
reader.FixPadding(0x40);

// Jump to this file's model table offset.
reader.JumpTo(modelTableOffset);

// Loop through and read each model.
for (int modelIndex = 0; modelIndex < Data.Models.Length; modelIndex++)
Data.Models[modelIndex] = new(reader, version);

// Check if this file has screen transition data.
if (hasScreenTransitionData)
{
// Jump to the offset for this data.
reader.JumpTo(screenTransitionTableOffset);

// TODO: Actually read this data properly.

// Calculate the length of this file's screen transition data.
uint length = (uint)(reader.BaseStream.Length - reader.BaseStream.Position);

// Read the bytes that make up this file's screen transition data.
Data.ScreenTransition = reader.ReadBytes(length);
}

// Close our BinaryReader.
reader.Close();
}

/// <summary>
/// Exports this collision's model data to an OBJ file.
/// TODO: Figure out what I want to do with the screen transition data when it comes to this.
/// TODO: Figure out how I want to present the tags, the @ system is what I want but 3DS Max's OBJ importer is stupid and changes most special characters to an underscore.
/// </summary>
/// <param name="filepath">The filepath to export to.</param>
public void ExportOBJ(string filepath)
{
// Set up an integer to keep track of the amount of vertices.
int vertexCount = 0;

// Create the StreamWriter.
StreamWriter obj = new(filepath);

// Loop through each model.
for (int modelIndex = 0; modelIndex < Data.Models.Length; modelIndex++)
{
// Write the Vertex Comment for this model.
obj.WriteLine($"# Model {modelIndex} Vertices\r\n");

// Write each vertex.
foreach (Vector3 vertex in Data.Models[modelIndex].Vertices)
obj.WriteLine($"v {vertex.X} {vertex.Y} {vertex.Z}");

// Write the Name/Behaviour Tags Comment for this model.
obj.WriteLine($"\r\n# Model {modelIndex} Name and Behaviour Tags\r\n");

// Split the flags for this model.
string flags = $"@{string.Join('@', Data.Models[modelIndex].Behaviour.ToString().Split(", "))}";

Check warning on line 333 in KnuxLib/Engines/Wayforward/Collision.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

// Write this model's name and flags.
obj.WriteLine($"g model{modelIndex}{flags}");
obj.WriteLine($"o model{modelIndex}{flags}");

// Write the Faces Comment for this model.
obj.WriteLine($"\r\n# Model {modelIndex} Faces\r\n");

// Write each face for this model, with the indices incremented by 1 (and the current value of vertexCount) due to OBJ counting from 1 not 0.
foreach (Face face in Data.Models[modelIndex].Faces)
obj.WriteLine($"f {face.IndexA + 1 + vertexCount} {face.IndexB + 1 + vertexCount} {face.IndexC + 1 + vertexCount}");

// Add the amount of vertices in this model to the count.
vertexCount += Data.Models[modelIndex].Vertices.Length;

// Write an empty line for neatness.
obj.WriteLine();
}

// Close the StreamWriter.
obj.Close();
}
}
}
6 changes: 6 additions & 0 deletions KnuxTools/FormatPrints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ public static void Wayforward()
Console.WriteLine("Package Archive (.pak) - Extracts to a directory of the same name as the input archive and creates an archive from an\r\ninput directory.");
Helpers.ColourConsole(" Version Flag - wayforward", true, ConsoleColor.Yellow);
Helpers.ColourConsole(" Version Flag - wayforward_bigendian", true, ConsoleColor.Yellow);
Helpers.ColourConsole("Collision (.clb)");
Helpers.ColourConsole(" Version Flag (Ducktales Remastered) - duck", true, ConsoleColor.Yellow);
Helpers.ColourConsole(" Version Flag (Ducktales Remastered (Wii U)) - duck_cafe", true, ConsoleColor.Yellow);
Helpers.ColourConsole(" Version Flag (Shantae: Half-Genie Hero) - hero", true, ConsoleColor.Yellow);
Helpers.ColourConsole(" Version Flag (Shantae: Half-Genie Hero (Wii U)) - hero_cafe", true, ConsoleColor.Yellow);
Helpers.ColourConsole(" Version Flag (Shantae and the Seven Sirens) - sevensirens", true, ConsoleColor.Yellow);
}
}
}
Loading

0 comments on commit 2c82ad9

Please sign in to comment.