diff --git a/CathodeLib/CathodeLib.csproj b/CathodeLib/CathodeLib.csproj
index 36d7e70..9c5a0bf 100644
--- a/CathodeLib/CathodeLib.csproj
+++ b/CathodeLib/CathodeLib.csproj
@@ -9,10 +9,10 @@
CathodeLib
Matt Filer
Provides support for parsing and writing common Alien: Isolation formats from the Cathode engine.
- Matt Filer 2023
- 0.6.0
+ Matt Filer 2024
+ 0.7.0
Library
- 0.6.0.0
+ 0.7.0.0
0.6.0.0
False
diff --git a/CathodeLib/Scripts/CATHODE/AlphaLightLevel.cs b/CathodeLib/Scripts/CATHODE/AlphaLightLevel.cs
index e2fdf26..93b7c77 100644
--- a/CathodeLib/Scripts/CATHODE/AlphaLightLevel.cs
+++ b/CathodeLib/Scripts/CATHODE/AlphaLightLevel.cs
@@ -20,7 +20,19 @@ override protected bool LoadInternal()
{
using (BinaryReader reader = new BinaryReader(File.OpenRead(_filepath)))
{
- //todo
+ reader.BaseStream.Position += 8;
+
+ //NOTE: these values are always 64/128/256 i think
+ int count = reader.ReadInt32();
+ int length = reader.ReadInt32() * 8;
+
+ for (int i = 0; i < count; i++)
+ {
+ Entries.Add(new Entry()
+ {
+ content = reader.ReadBytes(length)
+ });
+ }
}
return true;
}
@@ -31,8 +43,13 @@ override protected bool SaveInternal()
{
writer.BaseStream.SetLength(0);
Utilities.WriteString("alph", writer);
-
- //todo
+ writer.Write(0);
+ writer.Write(Entries.Count);
+ writer.Write(Entries.Count);
+ for (int i = 0; i < Entries.Count; i++)
+ {
+ writer.Write(Entries[i].content);
+ }
}
return true;
}
@@ -41,7 +58,7 @@ override protected bool SaveInternal()
#region STRUCTURES
public class Entry
{
- //todo
+ public byte[] content;
};
#endregion
}
diff --git a/CathodeLib/Scripts/CATHODE/CharacterAccessorySets.cs b/CathodeLib/Scripts/CATHODE/CharacterAccessorySets.cs
index 338bb5c..623ae48 100644
--- a/CathodeLib/Scripts/CATHODE/CharacterAccessorySets.cs
+++ b/CathodeLib/Scripts/CATHODE/CharacterAccessorySets.cs
@@ -25,7 +25,7 @@ override protected bool LoadInternal()
for (int i = 0; i < entryCount; i++)
{
Entry entry = new Entry();
- entry.character = Utilities.Consume(reader);
+ entry.character = Utilities.Consume(reader);
entry.shirt_composite = Utilities.Consume(reader);
entry.trousers_composite = Utilities.Consume(reader);
@@ -112,7 +112,7 @@ override protected bool SaveInternal()
#region STRUCTURES
public class Entry
{
- public CommandsEntityReference character = new CommandsEntityReference();
+ public EntityHandle character = new EntityHandle();
public ShortGuid shirt_composite = ShortGuid.Invalid;
public ShortGuid trousers_composite = ShortGuid.Invalid;
diff --git a/CathodeLib/Scripts/CATHODE/CollisionMaps.cs b/CathodeLib/Scripts/CATHODE/CollisionMaps.cs
index c18ba0b..dbe9df1 100644
--- a/CathodeLib/Scripts/CATHODE/CollisionMaps.cs
+++ b/CathodeLib/Scripts/CATHODE/CollisionMaps.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Runtime.InteropServices;
namespace CATHODE
@@ -19,45 +20,117 @@ public CollisionMaps(string path) : base(path) { }
#region FILE_IO
override protected bool LoadInternal()
{
+ int minUnk1 = 0;
+ int minUnk2 = 0;
+ int minColIn = 0;
+
+ List flags = new List();
+ Dictionary> dictest = new Dictionary>();
+
using (BinaryReader reader = new BinaryReader(File.OpenRead(_filepath)))
{
- //It seems typically in this file at the start there are a bunch of empty entries, and then there are a bunch of unresolvable ones, and then a bunch that can be resolved.
+ //The way this works:
+ // - First 18 entries are empty
+ // - Next set of entries are all the COLLISION_MAPPING resources referenced by COMMANDS.PAK (hence they have no composite_instance_id, as the composites aren't instanced - but they do have entity_ids)
+ // - There are then a few entries that have composite_instance_ids set but I can't resolve them - perhaps these are things from GLOBAL?
+ // - Then there's all the instanced entities with resolvable composite_instance_ids
reader.BaseStream.Position = 4;
int entryCount = reader.ReadInt32();
for (int i = 0; i < entryCount; i++)
{
Entry entry = new Entry();
- entry.unk0 = reader.ReadInt32(); //flag?
- entry.Unknown1_ = reader.ReadInt32(); //some sort of index ?
- entry.ID = Utilities.Consume(reader);
- entry.entity = Utilities.Consume(reader);
- entry.Unknown2_ = reader.ReadInt32(); //Is sometimes -1 and other times a small positive integer. Is this tree node parent?
- entry.CollisionHKXEntryIndex = reader.ReadInt16();
- entry.Unknown3_ = reader.ReadInt16(); //Most of the time it is -1.
- entry.MVRZoneIDThing = reader.ReadInt32();
+
+ entry.UnknownFlag = reader.ReadInt32(); //NOTE: if you filter by this value, all the UnknownIndex1s increment, UnknownIndex2s/collision_index don't increment but are grouped by -1s and non -1s,
+
+ //todo: compare flag value across levels
+
+ entry.UnknownIndex1= reader.ReadInt32();
+ entry.id = Utilities.Consume(reader);
+ entry.entity = Utilities.Consume(reader);
+ entry.UnknownIndex2 = reader.ReadInt32();
+ entry.collision_index = reader.ReadInt16();
+ entry.UnknownValue = reader.ReadInt16();
+ entry.zone_id = Utilities.Consume(reader);
reader.BaseStream.Position += 16;
Entries.Add(entry);
+
+ if (minUnk1 < entry.UnknownIndex1)
+ minUnk1 = entry.UnknownIndex1;
+ if (minUnk2 < entry.UnknownIndex2)
+ minUnk2 = entry.UnknownIndex2;
+ if (minColIn < entry.collision_index)
+ minColIn = entry.collision_index;
+
+ if (!flags.Contains(entry.UnknownFlag))
+ flags.Add(entry.UnknownFlag);
+
+ if (entry.collision_index != -1 && entry.UnknownIndex1 == -1 && entry.UnknownIndex2 == -1 && entry.UnknownValue == -1)
+ {
+ string sdfsdf = "";
+ }
+
+ if (entry.UnknownIndex1 == -1 && entry.UnknownIndex2 == -1 && entry.UnknownValue == -1)
+ {
+ string sdfsdf = "";
+ }
+
+ string flagBin = BitConverter.ToString(BitConverter.GetBytes(entry.UnknownFlag));
+ if (!dictest.ContainsKey(flagBin))
+ dictest.Add(flagBin, new List());
+
+ dictest[flagBin].Add(entry.UnknownIndex1 + " -> " + entry.UnknownIndex2 + " -> " + entry.collision_index);
+
+ //if (entry.UnknownFlag == -1073737335)
+ // Console.WriteLine(entry.UnknownIndex1);
+
+ //if (entry.UnknownFlag == 4429)
+ // Console.WriteLine(entry.UnknownIndex1);
+
+ //if (entry.UnknownFlag == -1073737405)
+ // Console.WriteLine(entry.UnknownIndex1);
+
+ //Console.WriteLine(entry.UnknownFlag + " -> " + entry.UnknownIndex1 + " -> " + entry.UnknownIndex2 + " -> " + entry.collision_index + " -> " + entry.UnknownValue);
}
}
+
+
return true;
}
override protected bool SaveInternal()
{
+ //composite_instance_id defo has something to do with the ordering as all the zeros are first
+
+
+ //Entries = Entries.OrderBy(o => o.entity.entity_id.ToUInt32() + o.id.ToUInt32()).ThenBy(o => o.entity.composite_instance_id.ToUInt32()).ThenBy(o => o.zone_id.ToUInt32()).ToList();
+
using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(_filepath)))
{
writer.BaseStream.SetLength(0);
- writer.Write(Entries.Count * 80);
+ writer.Write((Entries.Count) * 48);
writer.Write(Entries.Count);
+
for (int i = 0; i < Entries.Count; i++)
{
- writer.Write(Entries[i].Unknown1_);
- Utilities.Write(writer, Entries[i].ID);
- Utilities.Write(writer, Entries[i].entity);
- writer.Write(Entries[i].CollisionHKXEntryIndex);
- writer.Write(Entries[i].Unknown3_);
- writer.Write(Entries[i].MVRZoneIDThing);
+ //writer.Write(-268427008);
+ //writer.Write(-1);
+
+ writer.Write(Entries[i].UnknownFlag);
+ writer.Write(Entries[i].UnknownIndex1);
+
+ Utilities.Write(writer, Entries[i].id);
+ Utilities.Write(writer, Entries[i].entity);
+
+ writer.Write(-1);
+ //writer.Write(Entries[i].UnknownIndex2);
+
+ writer.Write((Int16)Entries[i].collision_index);
+
+ writer.Write((short)-1);
+ //writer.Write((Int16)Entries[i].UnknownValue);
+
+ Utilities.Write(writer, Entries[i].zone_id);
writer.Write(new byte[16]);
}
}
@@ -68,19 +141,47 @@ override protected bool SaveInternal()
#region STRUCTURES
public class Entry
{
- public int unk0 = 0; //flags?
- public int Unknown1_ = -1; // Is this tree node id?
+ public ShortGuid id = ShortGuid.Invalid; //This is the name of the entity hashed via ShortGuid
+ public EntityHandle entity = new EntityHandle();
+ public ShortGuid zone_id = ShortGuid.Invalid; //this maps the entity to a zone ID. interestingly, this seems to be the point of truth for the zone rendering
- public ShortGuid ID = ShortGuid.Invalid; //This is the name of the entity hashed via ShortGuid, as a result, we can't resolve a lot of them. Does the game care about the value? I doubt it. We definitely don't.
+ public int collision_index = -1; //maps to havok hkx entry
- public CommandsEntityReference entity = new CommandsEntityReference();
+ public int UnknownFlag = 0;
+ public int UnknownIndex1 = -1;
+ public int UnknownIndex2 = -1;
+ public int UnknownValue = -1;
- public int Unknown2_= -1; // NOTE: Is sometimes -1 and other times a small positive integer. Is this tree node parent?
+ public static bool operator ==(Entry x, Entry y)
+ {
+ if (ReferenceEquals(x, null)) return ReferenceEquals(y, null);
+ if (ReferenceEquals(y, null)) return ReferenceEquals(x, null);
+ if (x.id != y.id) return false;
+ if (x.zone_id != y.zone_id) return false;
+ if (x.entity != y.entity) return false;
+ return true;
+ }
+ public static bool operator !=(Entry x, Entry y)
+ {
+ return !(x == y);
+ }
- public Int16 CollisionHKXEntryIndex = -1; // NOTE: Most of the time is a positive integer, sometimes -1.
+ public override bool Equals(object obj)
+ {
+ return obj is Entry entry &&
+ EqualityComparer.Default.Equals(id, entry.id) &&
+ EqualityComparer.Default.Equals(entity, entry.entity) &&
+ EqualityComparer.Default.Equals(zone_id, entry.zone_id);
+ }
- public Int16 Unknown3_ = -1; // NOTE: Most of the time it is -1.
- public int MVRZoneIDThing = 0; // NOTE: This is CollisionMapThingIDs[0] from alien_mvr_entry
+ public override int GetHashCode()
+ {
+ int hashCode = 1001543423;
+ hashCode = hashCode * -1521134295 + id.GetHashCode();
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(entity);
+ hashCode = hashCode * -1521134295 + zone_id.GetHashCode();
+ return hashCode;
+ }
};
#endregion
}
diff --git a/CathodeLib/Scripts/CATHODE/Commands.cs b/CathodeLib/Scripts/CATHODE/Commands.cs
index b91e9fc..245c69d 100644
--- a/CathodeLib/Scripts/CATHODE/Commands.cs
+++ b/CathodeLib/Scripts/CATHODE/Commands.cs
@@ -189,7 +189,7 @@ override protected bool LoadInternal()
reader_parallel.BaseStream.Position = (offsetPairs[x].GlobalOffset * 4) + (y * 12);
AliasEntity overrider = new AliasEntity(new ShortGuid(reader_parallel));
int NumberOfParams = JumpToOffset(reader_parallel);
- overrider.alias.path.AddRange(Utilities.ConsumeArray(reader_parallel, NumberOfParams));
+ overrider.alias.path = Utilities.ConsumeArray(reader_parallel, NumberOfParams);
composite.aliases.Add(overrider);
break;
}
@@ -214,7 +214,7 @@ override protected bool LoadInternal()
ProxyEntity thisProxy = new ProxyEntity(new ShortGuid(reader_parallel));
int resetPos = (int)reader_parallel.BaseStream.Position + 8; //TODO: This is a HACK - I need to rework JumpToOffset to make a temp stream
int NumberOfParams = JumpToOffset(reader_parallel);
- thisProxy.proxy.path.AddRange(Utilities.ConsumeArray(reader_parallel, NumberOfParams)); //Last is always 0x00, 0x00, 0x00, 0x00
+ thisProxy.proxy.path = Utilities.ConsumeArray(reader_parallel, NumberOfParams); //Last is always 0x00, 0x00, 0x00, 0x00
reader_parallel.BaseStream.Position = resetPos;
ShortGuid idCheck = new ShortGuid(reader_parallel);
if (idCheck != thisProxy.shortGUID) throw new Exception("Proxy ID mismatch!");
@@ -264,9 +264,9 @@ override protected bool LoadInternal()
ResourceReference resource = new ResourceReference();
resource.position = new Vector3(reader_parallel.ReadSingle(), reader_parallel.ReadSingle(), reader_parallel.ReadSingle());
resource.rotation = new Vector3(reader_parallel.ReadSingle(), reader_parallel.ReadSingle(), reader_parallel.ReadSingle());
- resource.resourceID = new ShortGuid(reader_parallel);
- resource.entryType = CommandsUtils.GetResourceEntryType(reader_parallel.ReadBytes(4));
- switch (resource.entryType)
+ resource.resource_id = new ShortGuid(reader_parallel);
+ resource.resource_type = CommandsUtils.GetResourceEntryType(reader_parallel.ReadBytes(4));
+ switch (resource.resource_type)
{
case ResourceType.RENDERABLE_INSTANCE:
resource.index = reader_parallel.ReadInt32();
@@ -274,7 +274,7 @@ override protected bool LoadInternal()
break;
case ResourceType.COLLISION_MAPPING:
resource.index = reader_parallel.ReadInt32();
- resource.collisionID = new ShortGuid(reader_parallel);
+ resource.entityID = new ShortGuid(reader_parallel);
break;
case ResourceType.ANIMATED_MODEL:
case ResourceType.DYNAMIC_PHYSICS_SYSTEM:
@@ -315,7 +315,7 @@ override protected bool LoadInternal()
header.parameterSubID = new ShortGuid(reader_parallel);
int hierarchyCount = JumpToOffset(reader_parallel);
- header.connectedEntity.path = Utilities.ConsumeArray(reader_parallel, hierarchyCount).ToList();
+ header.connectedEntity.path = Utilities.ConsumeArray(reader_parallel, hierarchyCount);
animEntity.connections.Add(header);
}
@@ -397,7 +397,7 @@ override protected bool LoadInternal()
TriggerSequence.Entity thisTrigger = new TriggerSequence.Entity();
thisTrigger.timing = reader_parallel.ReadSingle();
reader_parallel.BaseStream.Position = hierarchyOffset;
- thisTrigger.connectedEntity.path = Utilities.ConsumeArray(reader_parallel, hierarchyCount).ToList();
+ thisTrigger.connectedEntity.path = Utilities.ConsumeArray(reader_parallel, hierarchyCount);
trigEntity.entities.Add(thisTrigger);
}
@@ -445,18 +445,18 @@ override protected bool LoadInternal()
if (composite.functions[x].parameters[y].name != resParamID) continue;
cResource resourceParam = (cResource)composite.functions[x].parameters[y].content;
- resourceParam.value.AddRange(resourceRefs.Where(o => o.resourceID == resourceParam.shortGUID));
- resourceRefs.RemoveAll(o => o.resourceID == resourceParam.shortGUID);
+ resourceParam.value.AddRange(resourceRefs.Where(o => o.resource_id == resourceParam.shortGUID));
+ resourceRefs.RemoveAll(o => o.resource_id == resourceParam.shortGUID);
}
}
//Check to see if this resource applies directly to an ENTITY
for (int x = 0; x < composite.functions.Count; x++)
{
- composite.functions[x].resources.AddRange(resourceRefs.Where(o => o.resourceID == composite.functions[x].shortGUID));
- resourceRefs.RemoveAll(o => o.resourceID == composite.functions[x].shortGUID);
+ composite.functions[x].resources.AddRange(resourceRefs.Where(o => o.resource_id == composite.functions[x].shortGUID));
+ resourceRefs.RemoveAll(o => o.resource_id == composite.functions[x].shortGUID);
}
- //Any that are left over will be applied to PhysicsSystem entities
- if (resourceRefs.Count == 1 && resourceRefs[0].entryType == ResourceType.DYNAMIC_PHYSICS_SYSTEM)
+ //Any that are left over will be applied to PhysicsSystem entities - really these just exist in the composite, but it's easier for us to track this way
+ if (resourceRefs.Count == 1 && resourceRefs[0].resource_type == ResourceType.DYNAMIC_PHYSICS_SYSTEM)
{
FunctionEntity physEnt = composite.functions.FirstOrDefault(o => o.function == physEntID);
if (physEnt != null) physEnt.resources.Add(resourceRefs[0]);
@@ -574,6 +574,7 @@ override protected bool SaveInternal()
Entries[i].functions[x].AddResource(ResourceType.EXCLUSIVE_MASTER_STATE_RESOURCE);
break;
+ //NOTE: Really, DYNAMIC_PHYSICS_SYSTEM isn't actually on the entity, it's on the composite
case FunctionType.PhysicsSystem:
Parameter dps_index = Entries[i].functions[x].GetParameter("system_index");
if (dps_index == null)
@@ -583,6 +584,7 @@ override protected bool SaveInternal()
}
Entries[i].functions[x].AddResource(ResourceType.DYNAMIC_PHYSICS_SYSTEM).index = ((cInteger)dps_index.content).value;
break;
+
case FunctionType.EnvironmentModelReference:
Parameter rsc = Entries[i].functions[x].GetParameter("resource");
if (rsc == null)
@@ -722,7 +724,7 @@ override protected bool SaveInternal()
stringStartRaw[3] = 0x80;
writer.Write(stringStartRaw);
string str = ((cString)parameters[i]).value.Replace("\u0092", "'");
- writer.Write(ShortGuidUtils.Generate(str).ToUInt32());
+ writer.Write(ShortGuidUtils.Generate(str, false).ToUInt32());
for (int x = 0; x < str.Length; x++) writer.Write(str[x]);
writer.Write((char)0x00);
Utilities.Align(writer, 4);
@@ -782,7 +784,7 @@ override protected bool SaveInternal()
{
int scriptStartPos = (int)writer.BaseStream.Position / 4;
- Utilities.Write(writer, ShortGuidUtils.Generate(Entries[i].name));
+ Utilities.Write(writer, ShortGuidUtils.Generate(Entries[i].name, false));
for (int x = 0; x < Entries[i].name.Length; x++) writer.Write(Entries[i].name[x]);
writer.Write((char)0x00);
Utilities.Align(writer, 4);
@@ -847,7 +849,7 @@ override protected bool SaveInternal()
List offsetPairs = new List(reshuffledAliases[i].Count);
for (int p = 0; p < reshuffledAliases[i].Count; p++)
{
- offsetPairs.Add(new OffsetPair(writer.BaseStream.Position, reshuffledAliases[i][p].alias.path.Count));
+ offsetPairs.Add(new OffsetPair(writer.BaseStream.Position, reshuffledAliases[i][p].alias.path.Length));
Utilities.Write(writer, reshuffledAliases[i][p].alias.path);
}
@@ -886,7 +888,7 @@ override protected bool SaveInternal()
List offsetPairs = new List();
for (int p = 0; p < Entries[i].proxies.Count; p++)
{
- offsetPairs.Add(new OffsetPair(writer.BaseStream.Position, Entries[i].proxies[p].proxy.path.Count));
+ offsetPairs.Add(new OffsetPair(writer.BaseStream.Position, Entries[i].proxies[p].proxy.path.Length));
Utilities.Write(writer, Entries[i].proxies[p].proxy.path);
}
@@ -931,9 +933,9 @@ override protected bool SaveInternal()
writer.Write(resourceReferences[i][p].rotation.Y);
writer.Write(resourceReferences[i][p].rotation.Z);
#endif
- writer.Write(resourceReferences[i][p].resourceID.ToUInt32()); //Sometimes this is the entity ID that uses the resource, other times it's the "resource" parameter ID link
- writer.Write(CommandsUtils.GetResourceEntryTypeGUID(resourceReferences[i][p].entryType).ToUInt32());
- switch (resourceReferences[i][p].entryType)
+ writer.Write(resourceReferences[i][p].resource_id.ToUInt32()); //Sometimes this is the entity ID that uses the resource, other times it's the "resource" parameter ID link
+ writer.Write(CommandsUtils.GetResourceEntryTypeGUID(resourceReferences[i][p].resource_type).ToUInt32());
+ switch (resourceReferences[i][p].resource_type)
{
case ResourceType.RENDERABLE_INSTANCE:
writer.Write(resourceReferences[i][p].index);
@@ -941,7 +943,7 @@ override protected bool SaveInternal()
break;
case ResourceType.COLLISION_MAPPING:
writer.Write(resourceReferences[i][p].index);
- writer.Write(resourceReferences[i][p].collisionID.ToUInt32());
+ writer.Write(resourceReferences[i][p].entityID.ToUInt32());
break;
case ResourceType.ANIMATED_MODEL:
case ResourceType.DYNAMIC_PHYSICS_SYSTEM:
@@ -981,7 +983,7 @@ override protected bool SaveInternal()
Utilities.Write(writer, CommandsUtils.GetDataTypeGUID(header.parameterDataType));
Utilities.Write(writer, header.parameterSubID);
writer.Write(hierarchyOffsets[pp] / 4);
- writer.Write(header.connectedEntity.path.Count);
+ writer.Write(header.connectedEntity.path.Length);
}
List internalOffsets = new List(cageAnimationEntities[i][p].animations.Count);
@@ -1092,7 +1094,7 @@ override protected bool SaveInternal()
for (int pp = 0; pp < triggerSequenceEntities[i][p].entities.Count; pp++)
{
writer.Write(hierarchyOffsets[pp] / 4);
- writer.Write(triggerSequenceEntities[i][p].entities[pp].connectedEntity.path.Count);
+ writer.Write(triggerSequenceEntities[i][p].entities[pp].connectedEntity.path.Length);
writer.Write(triggerSequenceEntities[i][p].entities[pp].timing);
}
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Composite.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Composite.cs
index 02760c3..14d7066 100644
--- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Composite.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Composite.cs
@@ -57,6 +57,20 @@ public List GetEntities()
return toReturn;
}
+ /* Returns a collection of function entities in the composite matching the given type */
+ public List GetFunctionEntitiesOfType(FunctionType type)
+ {
+ ShortGuid guid = CommandsUtils.GetFunctionTypeGUID(type);
+ return functions.FindAll(o => o.function == guid);
+ }
+
+ /* Removes all function entities in the composite matching the given type */
+ public void RemoveAllFunctionEntitiesOfType(FunctionType type)
+ {
+ ShortGuid guid = CommandsUtils.GetFunctionTypeGUID(type);
+ functions.RemoveAll(o => o.function == guid);
+ }
+
/* Add a new function entity */
public FunctionEntity AddFunction(FunctionType function, bool autopopulateParameters = false)
{
@@ -91,10 +105,10 @@ public VariableEntity AddVariable(string parameter, DataType type, bool addDefau
}
/* Add a new proxy entity */
- public ProxyEntity AddProxy(Commands commands, List hierarchy, bool addDefaultParam = false)
+ public ProxyEntity AddProxy(Commands commands, ShortGuid[] hierarchy, bool addDefaultParam = false)
{
CommandsUtils.ResolveHierarchy(commands, this, hierarchy, out Composite targetComposite, out string str);
- Entity ent = targetComposite.GetEntityByID(hierarchy[hierarchy.Count - 2]);
+ Entity ent = targetComposite.GetEntityByID(hierarchy[hierarchy.Length - 2]);
if (ent.variant != EntityVariant.FUNCTION) return null;
ProxyEntity proxy = new ProxyEntity(hierarchy, ((FunctionEntity)ent).function, addDefaultParam);
@@ -103,7 +117,7 @@ public ProxyEntity AddProxy(Commands commands, List hierarchy, bool a
}
/* Add a new alias entity */
- public AliasEntity AddAlias(List hierarchy)
+ public AliasEntity AddAlias(ShortGuid[] hierarchy)
{
AliasEntity alias = new AliasEntity(hierarchy);
aliases.Add(alias);
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Entity.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Entity.cs
index de2579b..6c33589 100644
--- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Entity.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Entity.cs
@@ -73,6 +73,29 @@ public Parameter GetParameter(ShortGuid id)
return parameters.FirstOrDefault(o => o.name == id);
}
+ /* Get all links out matching the given name */
+ public List GetLinksOut(string name)
+ {
+ ShortGuid id = ShortGuidUtils.Generate(name);
+ return GetLinksOut(id);
+ }
+ public List GetLinksOut(ShortGuid id)
+ {
+ return childLinks.FindAll(o => o.thisParamID == id);
+ }
+
+ /* Get all links in matching the given name */
+ public List GetLinksIn(string name, Composite comp)
+ {
+ ShortGuid id = ShortGuidUtils.Generate(name);
+ return GetLinksIn(id, comp);
+ }
+ public List GetLinksIn(ShortGuid id, Composite comp)
+ {
+ List parent_links = GetParentLinks(comp);
+ return parent_links.FindAll(o => o.linkedParamID == id && o.linkedEntityID == this.shortGUID);
+ }
+
/* Add a data-supplying parameter to the entity */
/*
public Parameter AddParameter(string name, T data, ParameterVariant variant = ParameterVariant.PARAMETER)
@@ -162,12 +185,25 @@ public Parameter AddParameter(ShortGuid id, ParameterData data, ParameterVariant
}
return param;
}
+ public Parameter AddParameter(Parameter param)
+ {
+ parameters.Add(param);
+ return param;
+ }
/* Remove a parameter from the entity */
public void RemoveParameter(string name)
{
ShortGuid name_id = ShortGuidUtils.Generate(name);
- parameters.RemoveAll(o => o.name == name_id);
+ RemoveParameter(name_id);
+ }
+ public void RemoveParameter(Parameter param)
+ {
+ RemoveParameter(param.name);
+ }
+ public void RemoveParameter(ShortGuid guid)
+ {
+ parameters.RemoveAll(o => o.name == guid);
}
/* Add a link from a parameter on us out to a parameter on another entity */
@@ -175,6 +211,18 @@ public void AddParameterLink(string parameter, Entity childEntity, string childP
{
childLinks.Add(new EntityConnector(childEntity.shortGUID, ShortGuidUtils.Generate(parameter), ShortGuidUtils.Generate(childParameter)));
}
+ public void AddParameterLink(Parameter parameter, Entity childEntity, Parameter childParameter)
+ {
+ childLinks.Add(new EntityConnector(childEntity.shortGUID, parameter.name, childParameter.name));
+ }
+ public void AddParameterLink(ShortGuid parameterGUID, Entity childEntity, ShortGuid childParameterGUID)
+ {
+ childLinks.Add(new EntityConnector(childEntity.shortGUID, parameterGUID, childParameterGUID));
+ }
+ public void AddParameterLink(ShortGuid parameterGUID, ShortGuid childEntityGUID, ShortGuid childParameterGUID)
+ {
+ childLinks.Add(new EntityConnector(childEntityGUID, parameterGUID, childParameterGUID));
+ }
/* Remove a link to another entity */
public void RemoveParameterLink(string parameter, Entity childEntity, string childParameter)
@@ -182,7 +230,108 @@ public void RemoveParameterLink(string parameter, Entity childEntity, string chi
ShortGuid parameter_id = ShortGuidUtils.Generate(parameter);
ShortGuid childParameter_id = ShortGuidUtils.Generate(childParameter);
//TODO: do we want to do RemoveAll? should probably just remove the first
- childLinks.RemoveAll(o => o.parentParamID == parameter_id && o.childID == childEntity.shortGUID && o.childParamID == childParameter_id);
+ childLinks.RemoveAll(o => o.thisParamID == parameter_id && o.linkedEntityID == childEntity.shortGUID && o.linkedParamID == childParameter_id);
+ }
+ public void RemoveParameterLink(Parameter parameter, Entity childEntity, Parameter childParameter)
+ {
+ childLinks.RemoveAll(o => o.thisParamID == parameter.name && o.linkedEntityID == childEntity.shortGUID && o.linkedParamID == childParameter.name);
+ }
+ public void RemoveParameterLink(ShortGuid parameterGUID, Entity childEntity, ShortGuid childParameterGUID)
+ {
+ childLinks.RemoveAll(o => o.thisParamID == parameterGUID && o.linkedEntityID == childEntity.shortGUID && o.linkedParamID == childParameterGUID);
+ }
+ public void RemoveParameterLink(ShortGuid parameterGUID, ShortGuid childEntityGUID, ShortGuid childParameterGUID)
+ {
+ childLinks.RemoveAll(o => o.thisParamID == parameterGUID && o.linkedEntityID == childEntityGUID && o.linkedParamID == childParameterGUID);
+ }
+
+ /* Utility: Find all links in to this entity (pass in the composite this entity is within) */
+ public List GetParentLinks(Composite containedComposite)
+ {
+ List connections = new List();
+ containedComposite.GetEntities().ForEach(ent => {
+ ent.childLinks.ForEach(link =>
+ {
+ if (link.linkedEntityID == shortGUID)
+ {
+ connections.Add(new EntityConnector()
+ {
+ ID = link.ID,
+ thisParamID = link.linkedParamID,
+ linkedParamID = link.thisParamID,
+ linkedEntityID = ent.shortGUID
+ });
+ }
+ });
+ });
+ return connections;
+ }
+
+ /* Utility: Returns true if this entity has any links IN or OUT (pass in the composite this entity is within) */
+ public bool HasLinks(Composite containedComposite)
+ {
+ if (childLinks.Count != 0)
+ return true;
+
+ List entities = containedComposite.GetEntities();
+ for (int i = 0; i < entities.Count; i++)
+ {
+ for (int x = 0; x < entities[i].childLinks.Count; x++)
+ {
+ if (entities[i].childLinks[x].linkedEntityID == shortGUID)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /* Utility: Remove all child links out from the given parameter */
+ public void RemoveAllParameterLinksOut(string parameter)
+ {
+ ShortGuid parameter_id = ShortGuidUtils.Generate(parameter);
+ childLinks.RemoveAll(o => o.thisParamID == parameter_id);
+ }
+ public void RemoveAllParameterLinksOut()
+ {
+ childLinks.Clear();
+ }
+
+ /* Utility: Remove all child links in to the given parameter */
+ public void RemoveAllParameterLinksIn(string parameter, Composite comp)
+ {
+ ShortGuid parameter_id = ShortGuidUtils.Generate(parameter);
+ List links_in = GetParentLinks(comp);
+ foreach (EntityConnector link in links_in)
+ {
+ if (link.linkedParamID != parameter_id) continue;
+ Entity ent = comp.GetEntityByID(link.linkedEntityID);
+ if (ent == null) continue;
+ ent.childLinks.RemoveAll(o => o.ID == link.ID);
+ }
+ }
+ public void RemoveAllParameterLinksIn(Composite comp)
+ {
+ List links_in = GetParentLinks(comp);
+ foreach (EntityConnector link in links_in)
+ {
+ Entity ent = comp.GetEntityByID(link.linkedEntityID);
+ if (ent == null) continue;
+ ent.childLinks.RemoveAll(o => o.ID == link.ID);
+ }
+ }
+
+ /* Utility: Remove all child links in to and out of the given parameter */
+ public void RemoveAllParameterLinks(string parameter, Composite comp)
+ {
+ RemoveAllParameterLinksIn(parameter, comp);
+ RemoveAllParameterLinksOut(parameter);
+ }
+
+ /* Utility: Remove all child links in to and out of the given parameter */
+ public void RemoveAllParameterLinks(Composite comp)
+ {
+ RemoveAllParameterLinksIn(comp);
+ RemoveAllParameterLinksOut();
}
}
}
@@ -279,8 +428,8 @@ public ResourceReference AddResource(ResourceType type)
if (rr == null)
{
rr = new ResourceReference(type);
- rr.resourceID = shortGUID;
- switch (rr.entryType)
+ rr.resource_id = type == ResourceType.DYNAMIC_PHYSICS_SYSTEM ? ShortGuidUtils.Generate("DYNAMIC_PHYSICS_SYSTEM") : shortGUID;
+ switch (rr.resource_type)
{
case ResourceType.DYNAMIC_PHYSICS_SYSTEM:
case ResourceType.RENDERABLE_INSTANCE:
@@ -293,10 +442,17 @@ public ResourceReference AddResource(ResourceType type)
return rr;
}
- /* Find a resource reference of type */
- public ResourceReference GetResource(ResourceType type)
+ /* Find a resource reference of type on the entity - will also check the "resource" parameter second if alsoCheckParameter is true */
+ public ResourceReference GetResource(ResourceType type, bool alsoCheckParameter = false)
{
- return resources.FirstOrDefault(o => o.entryType == type);
+ ResourceReference resource = resources.FirstOrDefault(o => o.resource_type == type);
+ if (alsoCheckParameter && resource == null)
+ {
+ Parameter resourceParam = GetParameter("resource");
+ if (resourceParam != null && resourceParam.content != null && resourceParam.content.dataType == DataType.RESOURCE)
+ resource = ((cResource)resourceParam.content).GetResource(ResourceType.COLLISION_MAPPING);
+ }
+ return resource;
}
public override string ToString()
@@ -310,13 +466,13 @@ public class ProxyEntity : Entity
public ProxyEntity() : base(EntityVariant.PROXY) { }
public ProxyEntity(ShortGuid shortGUID) : base(shortGUID, EntityVariant.PROXY) { }
- public ProxyEntity(List hierarchy = null, ShortGuid targetType = new ShortGuid(), bool autoGenerateParameters = false) : base(EntityVariant.PROXY)
+ public ProxyEntity(ShortGuid[] hierarchy = null, ShortGuid targetType = new ShortGuid(), bool autoGenerateParameters = false) : base(EntityVariant.PROXY)
{
this.function = targetType;
if (hierarchy != null) this.proxy.path = hierarchy;
if (autoGenerateParameters) EntityUtils.ApplyDefaults(this);
}
- public ProxyEntity(ShortGuid shortGUID, List hierarchy = null, ShortGuid targetType = new ShortGuid(), bool autoGenerateParameters = false) : base(shortGUID, EntityVariant.PROXY)
+ public ProxyEntity(ShortGuid shortGUID, ShortGuid[] hierarchy = null, ShortGuid targetType = new ShortGuid(), bool autoGenerateParameters = false) : base(shortGUID, EntityVariant.PROXY)
{
this.shortGUID = shortGUID;
this.function = targetType;
@@ -333,11 +489,11 @@ public class AliasEntity : Entity
public AliasEntity() : base(EntityVariant.ALIAS) { }
public AliasEntity(ShortGuid shortGUID) : base(shortGUID, EntityVariant.ALIAS) { }
- public AliasEntity(List hierarchy = null) : base(EntityVariant.ALIAS)
+ public AliasEntity(ShortGuid[] hierarchy = null) : base(EntityVariant.ALIAS)
{
if (hierarchy != null) this.alias.path = hierarchy;
}
- public AliasEntity(ShortGuid shortGUID, List hierarchy = null) : base(shortGUID, EntityVariant.ALIAS)
+ public AliasEntity(ShortGuid shortGUID, ShortGuid[] hierarchy = null) : base(shortGUID, EntityVariant.ALIAS)
{
this.shortGUID = shortGUID;
if (hierarchy != null) this.alias.path = hierarchy;
@@ -450,18 +606,65 @@ public Event(ShortGuid start, ShortGuid end)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EntityConnector
{
- public EntityConnector(ShortGuid childEntityID, ShortGuid parentParam, ShortGuid childParam)
+ public EntityConnector(ShortGuid linkedEntity, ShortGuid param, ShortGuid linkedParam)
+ {
+ ID = ShortGuidUtils.GenerateRandom();
+
+ thisParamID = param;
+ linkedParamID = linkedParam;
+ linkedEntityID = linkedEntity;
+ }
+ public EntityConnector(Entity linkedEntity, string param, string linkedParam)
{
- connectionID = ShortGuidUtils.GenerateRandom();
- parentParamID = parentParam;
- childParamID = childParam;
- childID = childEntityID;
+ ID = ShortGuidUtils.GenerateRandom();
+
+ thisParamID = ShortGuidUtils.Generate(param);
+ linkedParamID = ShortGuidUtils.Generate(linkedParam);
+ linkedEntityID = linkedEntity.shortGUID;
}
- public ShortGuid connectionID; //The unique ID for this connection
- public ShortGuid parentParamID; //The ID of the parameter we're providing out
- public ShortGuid childParamID; //The ID of the parameter we're providing into the child
- public ShortGuid childID; //The ID of the entity we're linking to to provide the value for
+ public ShortGuid ID; //The unique ID for this connection
+
+ public ShortGuid thisParamID; //The ID of the parameter
+ public ShortGuid linkedParamID; //The ID of the parameter we're connecting to
+ public ShortGuid linkedEntityID; //The ID of the entity we're connecting to that has the linked parameter
+
+ public Entity GetEntity(Composite comp)
+ {
+ return comp.GetEntityByID(linkedEntityID);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is EntityConnector connector &&
+ EqualityComparer.Default.Equals(ID, connector.ID) &&
+ EqualityComparer.Default.Equals(thisParamID, connector.thisParamID) &&
+ EqualityComparer.Default.Equals(linkedParamID, connector.linkedParamID) &&
+ EqualityComparer.Default.Equals(linkedEntityID, connector.linkedEntityID);
+ }
+
+ public override int GetHashCode()
+ {
+ int hashCode = -1516234163;
+ hashCode = hashCode * -1521134295 + ID.GetHashCode();
+ hashCode = hashCode * -1521134295 + thisParamID.GetHashCode();
+ hashCode = hashCode * -1521134295 + linkedParamID.GetHashCode();
+ hashCode = hashCode * -1521134295 + linkedEntityID.GetHashCode();
+ return hashCode;
+ }
+
+ public static bool operator ==(EntityConnector x, EntityConnector y)
+ {
+ if (x.ID != y.ID) return false;
+ if (x.thisParamID != y.thisParamID) return false;
+ if (x.linkedParamID != y.linkedParamID) return false;
+ if (x.linkedEntityID != y.linkedEntityID) return false;
+ return true;
+ }
+ public static bool operator !=(EntityConnector x, EntityConnector y)
+ {
+ return !(x == y);
+ }
}
///
@@ -475,21 +678,19 @@ public EntityConnector(ShortGuid childEntityID, ShortGuid parentParam, ShortGuid
public class EntityPath
{
public EntityPath() { }
- public EntityPath(List _path)
+ public EntityPath(ShortGuid[] _path)
{
- path = _path;
-
- if (path[path.Count - 1] != ShortGuid.Invalid)
- path.Add(ShortGuid.Invalid);
+ path = (ShortGuid[])_path.Clone();
+ EnsureFinalIsEmpty();
}
- public List path = new List();
+ public ShortGuid[] path = new ShortGuid[0];
public static bool operator ==(EntityPath x, EntityPath y)
{
if (ReferenceEquals(x, null)) return ReferenceEquals(y, null);
if (ReferenceEquals(y, null)) return ReferenceEquals(x, null);
- if (x.path.Count != y.path.Count) return false;
- for (int i = 0; i < x.path.Count; i++)
+ if (x.path.Length != y.path.Length) return false;
+ for (int i = 0; i < x.path.Length; i++)
{
if (x.path[i].ToByteString() != y.path[i].ToByteString())
return false;
@@ -503,23 +704,32 @@ public EntityPath(List _path)
public override bool Equals(object obj)
{
- return obj is EntityPath hierarchy &&
- EqualityComparer>.Default.Equals(this.path, hierarchy.path);
+ return obj is EntityPath path &&
+ EqualityComparer.Default.Equals(this.path, path.path);
}
public override int GetHashCode()
{
- return 218564712 + EqualityComparer>.Default.GetHashCode(path);
+ return -1757656154 + EqualityComparer.Default.GetHashCode(path);
+ }
+
+ private void EnsureFinalIsEmpty()
+ {
+ if (path.Length == 0 || path[path.Length - 1] != ShortGuid.Invalid)
+ {
+ Array.Resize(ref path, path.Length + 1);
+ path[path.Length - 1] = ShortGuid.Invalid;
+ }
}
/* Get this path as a string */
public string GetAsString()
{
string val = "";
- for (int i = 0; i < path.Count; i++)
+ for (int i = 0; i < path.Length; i++)
{
val += path[i].ToByteString();
- if (i != path.Count - 1) val += " -> ";
+ if (i != path.Length - 1) val += " -> ";
}
return val;
}
@@ -532,7 +742,7 @@ public string GetAsString(Commands commands, Composite composite, bool withIDs =
public UInt32 ToUInt32()
{
UInt32 val = 0;
- for (int i = 0; i < path.Count; i++) val += path[i].ToUInt32();
+ for (int i = 0; i < path.Length; i++) val += path[i].ToUInt32();
return val;
}
@@ -567,15 +777,13 @@ public Entity GetPointedEntity(Commands commands, Composite startComposite, out
/* Get the ID of the entity that this path points to */
public ShortGuid GetPointedEntityID()
{
- path.Reverse();
ShortGuid id = ShortGuid.Invalid;
- for (int i = 0; i < path.Count; i++)
+ for (int i = path.Length - 1; i >= 0; i--)
{
if (path[i] == ShortGuid.Invalid) continue;
id = path[i];
break;
}
- path.Reverse();
return id;
}
@@ -588,15 +796,16 @@ public bool IsPathValid(Commands commands, Composite composite)
/* Generate the checksum used identify the path */
public ShortGuid GeneratePathHash()
{
- if (path.Count == 0) return ShortGuid.Invalid;
- if (path[path.Count - 1] != ShortGuid.Invalid) path.Add(ShortGuid.Invalid);
+ if (path.Length == 0) return ShortGuid.Invalid;
+ EnsureFinalIsEmpty();
+ //TODO: invert loop rather than array
path.Reverse();
ShortGuid checksumGenerated = path[0];
- for (int i = 0; i < path.Count; i++)
+ for (int i = 0; i < path.Length; i++)
{
checksumGenerated = checksumGenerated.Combine(path[i + 1]);
- if (i == path.Count - 2) break;
+ if (i == path.Length - 2) break;
}
path.Reverse();
@@ -604,24 +813,105 @@ public ShortGuid GeneratePathHash()
}
/* Generate the instance ID used to identify the instanced composite we're executed in */
- public ShortGuid GenerateInstance()
+ public ShortGuid GenerateCompositeInstanceID(bool hasInternalEntityID = true) //Set this to false the final value in the path is not an entity ID within the composite
{
- //TODO: This hijacks the usual use for this class, need to tidy it up
- ShortGuid entityID = GetPointedEntityID();
- path.Insert(0, ShortGuid.InitialiserBase);
- path.Remove(entityID);
- path.Reverse();
- ShortGuid instanceGenerated = path[0];
- for (int i = 0; i < path.Count; i++)
+ return path.GenerateCompositeInstanceID(hasInternalEntityID);
+ }
+
+ /* Generate a zone ID (use this when the EntityHandle points to a Zone entity) */
+ public ShortGuid GenerateZoneID()
+ {
+ return new ShortGuid(0 + GenerateCompositeInstanceID().ToUInt32() + GetPointedEntityID().ToUInt32() + 1);
+ }
+
+ /* Add the next entity GUID along the path */
+ public void AddNextStep(Entity entity)
+ {
+ AddNextStep(entity.shortGUID);
+ }
+ public void AddNextStep(ShortGuid guid)
+ {
+ if (path.Length > 0 && path[path.Length - 1] == ShortGuid.Invalid)
{
- if (i == path.Count - 1) break;
- instanceGenerated = path[i + 1].Combine(instanceGenerated);
+ path[path.Length - 1] = guid;
+ }
+ else
+ {
+ Array.Resize(ref path, path.Length + 1);
+ path[path.Length - 1] = guid;
+ }
+ EnsureFinalIsEmpty();
+ }
+
+ /* Remove the last entity GUID along the path */
+ public void GoBackOneStep()
+ {
+ if (path.Length > 0 && path[path.Length - 1] == ShortGuid.Invalid)
+ {
+ if (path.Length > 1)
+ {
+ path[path.Length - 2] = ShortGuid.Invalid;
+ Array.Resize(ref path, path.Length - 1);
+ }
+ }
+ else if (path.Length > 0)
+ {
+ path[path.Length - 1] = ShortGuid.Invalid;
+ }
+ else
+ {
+ EnsureFinalIsEmpty();
+ }
+ }
+
+ /* Updates this path to have the path to another entity prepended to it */
+ //public void PrependPath(EntityPath otherPath)
+ //{
+ // int length = otherPath.path[otherPath.path.Count - 1] == ShortGuid.Invalid ? otherPath.path.Count - 2 : otherPath.path.Count - 1;
+ // for (int i = 0; i < length; i++)
+ // path.Insert(i, otherPath.path[i]);
+ //}
+ }
+
+ public static class PathUtils
+ {
+ /* Generate the instance ID used to identify the instanced composite we're executed in */
+ public static ShortGuid GenerateCompositeInstanceID(this ShortGuid[] path, bool hasInternalEntityID = true) //Set this to false the final value in the path is not an entity ID within the composite
+ {
+ bool hasTrailingInvalid = (path.Length > 0 && path[path.Length - 1] == ShortGuid.Invalid);
+ ShortGuid[] values = new ShortGuid[hasInternalEntityID ? (hasTrailingInvalid ? path.Length - 1 : path.Length) : (hasTrailingInvalid ? path.Length : path.Length + 1)];
+ values[values.Length - 1] = ShortGuid.InitialiserBase;
+ int x = 0;
+ for (int i = values.Length - 2; i >= 0; i--)
+ {
+ values[i] = path[x];
+ x++;
+ }
+ ShortGuid instanceGenerated = values[0];
+ for (int i = 0; i < values.Length; i++)
+ {
+ if (i == values.Length - 1) break;
+ instanceGenerated = values[i + 1].Combine(instanceGenerated);
+ }
+ return instanceGenerated;
+ }
+ public static ShortGuid GenerateCompositeInstanceID(this List path, bool hasInternalEntityID = true) //Set this to false the final value in the path is not an entity ID within the composite
+ {
+ bool hasTrailingInvalid = (path.Count > 0 && path[path.Count - 1] == ShortGuid.Invalid);
+ ShortGuid[] values = new ShortGuid[hasInternalEntityID ? (hasTrailingInvalid ? path.Count - 1 : path.Count) : (hasTrailingInvalid ? path.Count : path.Count + 1)];
+ values[values.Length - 1] = ShortGuid.InitialiserBase;
+ int x = 0;
+ for (int i = values.Length - 2; i >= 0; i--)
+ {
+ values[i] = path[x];
+ x++;
+ }
+ ShortGuid instanceGenerated = values[0];
+ for (int i = 0; i < values.Length; i++)
+ {
+ if (i == values.Length - 1) break;
+ instanceGenerated = values[i + 1].Combine(instanceGenerated);
}
- path.Reverse();
- path.RemoveAt(0);
- path.RemoveAll(o => o == ShortGuid.Invalid);
- path.Add(entityID);
- path.Add(ShortGuid.Invalid);
return instanceGenerated;
}
}
@@ -638,7 +928,8 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
{
EntityPath e = new EntityPath();
List vals = reader.Value.ToString().Split(new[] { " -> " }, StringSplitOptions.None).ToList();
- for (int i = 0; i < vals.Count; i++) e.path.Add(new ShortGuid(vals[i]));
+ e.path = new ShortGuid[vals.Count];
+ for (int i = 0; i < vals.Count; i++) e.path[i] = new ShortGuid(vals[i]);
return e;
}
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ParameterData.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ParameterData.cs
index 52f12d5..99319c7 100644
--- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ParameterData.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ParameterData.cs
@@ -161,7 +161,9 @@ public cTransform(Vector3 position, Vector3 rotation)
if (x == null) return y;
if (y == null) return x;
- return new cTransform(x.position + y.position, x.rotation + y.rotation);
+
+
+ return new cTransform(x.position + y.position, x.rotation.AddEulerAngles(y.rotation));
}
public override string ToString()
@@ -268,8 +270,8 @@ public ResourceReference AddResource(ResourceType type)
if (rr == null)
{
rr = new ResourceReference(type);
- rr.resourceID = shortGUID;
- switch (rr.entryType)
+ rr.resource_id = shortGUID;
+ switch (rr.resource_type)
{
case ResourceType.DYNAMIC_PHYSICS_SYSTEM:
case ResourceType.RENDERABLE_INSTANCE:
@@ -285,7 +287,7 @@ public ResourceReference AddResource(ResourceType type)
/* Find a resource reference of type */
public ResourceReference GetResource(ResourceType type)
{
- return value.FirstOrDefault(o => o.entryType == type);
+ return value.FirstOrDefault(o => o.resource_type == type);
}
}
[Serializable]
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ResourceReference.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ResourceReference.cs
index 8ac36d7..8edee9d 100644
--- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ResourceReference.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ResourceReference.cs
@@ -27,7 +27,7 @@ public ResourceReference(ResourceType type)
index = 0;
break;
}
- entryType = type;
+ resource_type = type;
}
public static bool operator ==(ResourceReference x, ResourceReference y)
@@ -37,11 +37,11 @@ public ResourceReference(ResourceType type)
if (x.position != y.position) return false;
if (x.rotation != y.rotation) return false;
- if (x.resourceID != y.resourceID) return false;
- if (x.entryType != y.entryType) return false;
+ if (x.resource_id != y.resource_id) return false;
+ if (x.resource_type != y.resource_type) return false;
if (x.index != y.index) return false;
if (x.count != y.count) return false;
- if (x.collisionID != y.collisionID) return false;
+ if (x.entityID != y.entityID) return false;
return true;
}
@@ -60,11 +60,11 @@ public override bool Equals(object obj)
return obj is ResourceReference reference &&
EqualityComparer.Default.Equals(position, reference.position) &&
EqualityComparer.Default.Equals(rotation, reference.rotation) &&
- EqualityComparer.Default.Equals(resourceID, reference.resourceID) &&
- entryType == reference.entryType &&
+ EqualityComparer.Default.Equals(resource_id, reference.resource_id) &&
+ resource_type == reference.resource_type &&
index == reference.index &&
count == reference.count &&
- EqualityComparer.Default.Equals(collisionID, reference.collisionID);
+ EqualityComparer.Default.Equals(entityID, reference.entityID);
}
public override int GetHashCode()
@@ -72,23 +72,23 @@ public override int GetHashCode()
int hashCode = -1286985782;
hashCode = hashCode * -1521134295 + position.GetHashCode();
hashCode = hashCode * -1521134295 + rotation.GetHashCode();
- hashCode = hashCode * -1521134295 + resourceID.GetHashCode();
- hashCode = hashCode * -1521134295 + entryType.GetHashCode();
+ hashCode = hashCode * -1521134295 + resource_id.GetHashCode();
+ hashCode = hashCode * -1521134295 + resource_type.GetHashCode();
hashCode = hashCode * -1521134295 + index.GetHashCode();
hashCode = hashCode * -1521134295 + count.GetHashCode();
- hashCode = hashCode * -1521134295 + collisionID.GetHashCode();
+ hashCode = hashCode * -1521134295 + entityID.GetHashCode();
return hashCode;
}
public Vector3 position = new Vector3(0, 0, 0);
public Vector3 rotation = new Vector3(0, 0, 0);
- public ShortGuid resourceID; //TODO: we could deprecate this, and just write it knowing what we know with our object structure
- public ResourceType entryType;
+ public ShortGuid resource_id; //this can be translated to a string sometimes, like DYNAMIC_PHYSICS_SYSTEM
+ public ResourceType resource_type;
public int index = -1;
public int count = 1;
- public ShortGuid collisionID = new ShortGuid("FF-FF-FF-FF");
+ public ShortGuid entityID = new ShortGuid("FF-FF-FF-FF");
}
}
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ShortGuid.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ShortGuid.cs
index 5522269..8b45e62 100644
--- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ShortGuid.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ShortGuid.cs
@@ -17,7 +17,8 @@ namespace CATHODE.Scripting
public struct ShortGuid : IComparable
{
public static readonly ShortGuid Invalid = new ShortGuid(0);
- public static readonly ShortGuid InitialiserBase = new ShortGuid("FE-5B-F0-4A");
+ public static readonly ShortGuid InitialiserBase = new ShortGuid(1257266174); //"FE-5B-F0-4A"
+ public static readonly ShortGuid Max = new ShortGuid(4294967295); //"FF-FF-FF-FF"
public bool IsInvalid => val == Invalid.val;
@@ -36,13 +37,11 @@ public ShortGuid(uint num)
val = num;
}
- [Obsolete("For performance reasons, it is recommended to initialse ShortGuid as an unsigned integer.")]
public ShortGuid(byte[] id)
{
val = BitConverter.ToUInt32(id, 0);
}
- [Obsolete("For performance reasons, it is recommended to initialse ShortGuid as an unsigned integer.")]
public ShortGuid(string id)
{
System.String[] arr = id.Split('-');
@@ -67,13 +66,11 @@ public override bool Equals(object obj)
return !(x.val == y.val);
}
- [Obsolete("For performance reasons, it is recommended to compare as ShortGuid or integer.")]
public static bool operator ==(ShortGuid x, string y)
{
return x.ToByteString() == y;
}
- [Obsolete("For performance reasons, it is recommended to compare as ShortGuid or integer.")]
public static bool operator !=(ShortGuid x, string y)
{
return x.ToByteString() != y;
@@ -114,13 +111,11 @@ public uint ToUInt32()
return val;
}
- [Obsolete("For performance reasons, it is recommended to use ToUInt32.")]
public string ToByteString()
{
return BitConverter.ToString(BitConverter.GetBytes(val));
}
- [Obsolete("For performance reasons, it is recommended to use ToUInt32.")]
public byte[] ToBytes()
{
return BitConverter.GetBytes(val);
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/TypeEnums.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/TypeEnums.cs
index fa4251f..ed6f369 100644
--- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/TypeEnums.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/TypeEnums.cs
@@ -130,7 +130,7 @@ public enum FunctionType
Character,
CharacterAttachmentNode,
CharacterCommand,
- CharacterMonitor,
+ //CharacterMonitor,
CharacterShivaArms,
CharacterTypeMonitor,
Checkpoint,
@@ -515,7 +515,7 @@ public enum FunctionType
NavMeshWalkablePlatform,
NetPlayerCounter,
NetworkedTimer,
- NetworkProxy,
+ //NetworkProxy,
NonInteractiveWater,
NonPersistentBool,
NonPersistentInt,
@@ -684,7 +684,7 @@ public enum FunctionType
SetBool,
SetColour,
SetEnum,
- SetEnumString,
+ //SetEnumString,
SetFloat,
SetGamepadAxes,
SetGameplayTips,
@@ -830,7 +830,7 @@ public enum FunctionType
VariableBool,
VariableColour,
VariableEnum,
- VariableEnumString,
+ //VariableEnumString,
VariableFilterObject,
VariableFlashScreenColour,
VariableFloat,
@@ -891,13 +891,13 @@ public enum FunctionType
/* Resource reference types */
public enum ResourceType
{
- COLLISION_MAPPING, //Links to data in COLLISION.MAP
- DYNAMIC_PHYSICS_SYSTEM, //Links to data in PHYSICS.MAP
- EXCLUSIVE_MASTER_STATE_RESOURCE, // ?? -> this seems to define some sort of change of NAV_MESH state using EXCLUSIVE_MASTER_RESOURCE_INDICES
- NAV_MESH_BARRIER_RESOURCE,
- RENDERABLE_INSTANCE, //Links to data in REDS.BIN
- TRAVERSAL_SEGMENT,
- ANIMATED_MODEL, //Links to data in ENVIRONMENT_ANIMATION.DAT
+ COLLISION_MAPPING, // Links to data in COLLISION.MAP
+ DYNAMIC_PHYSICS_SYSTEM, // Links to data in PHYSICS.MAP
+ EXCLUSIVE_MASTER_STATE_RESOURCE, // Written to RESOURCES.BIN and then index referenced in EXCLUSIVE_MASTER_RESOURCE_INDICES
+ NAV_MESH_BARRIER_RESOURCE, // ?? -> perhaps linking to PATH_BARRIER_RESOURCES?
+ RENDERABLE_INSTANCE, // Links to data in REDS.BIN
+ TRAVERSAL_SEGMENT, // Perhaps links to STATE_x/TRAVERSAL, but we don't bother writing any entries here as it's only populated in the unused AUTOGENERATION by CAGE
+ ANIMATED_MODEL, // Links to data in ENVIRONMENT_ANIMATION.DAT
// Any below this point are referenced in code, but not used in the vanilla game's CommandsPAKs
CATHODE_COVER_SEGMENT,
@@ -1105,6 +1105,10 @@ public enum CustomEndTables
// Doing this will cause issues with backwards compatibility.
ENTITY_NAMES,
SHORT_GUIDS,
+ COMPOSITE_PURGE_STATES,
+ COMPOSITE_MODIFICATION_INFO,
+ COMPOSITE_FLOWGRAPHS,
+ COMPOSITE_FLOWGRAPH_COMPATIBILITY_INFO,
//Add new entries here
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CommandsUtils.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CommandsUtils.cs
index 369b198..a484fb7 100644
--- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CommandsUtils.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CommandsUtils.cs
@@ -1,7 +1,9 @@
using CATHODE.Scripting.Internal;
+using CathodeLib;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Numerics;
using System.Text;
using System.Threading.Tasks;
@@ -10,14 +12,111 @@ namespace CATHODE.Scripting
//Helpful lookup tables for various Cathode Commands types
public static class CommandsUtils
{
+ //NOTE: This list is exposed publicly, because it is up to your app to manage it.
+ public static CompositePurgeTable PurgedComposites => _purged;
+ private static CompositePurgeTable _purged;
+
+ public static Commands LinkedCommands => _commands;
+ private static Commands _commands;
+
static CommandsUtils()
{
+ _purged = new CompositePurgeTable();
+
SetupFunctionTypeLUT();
SetupDataTypeLUT();
SetupObjectTypeLUT();
SetupResourceEntryTypeLUT();
}
+ /* Optionally, link a Commands file which can be used to save purge states to */
+ public static void LinkCommands(Commands commands)
+ {
+ if (_commands != null)
+ {
+ _commands.OnLoadSuccess -= LoadPurgeStates;
+ _commands.OnSaveSuccess -= SavePurgeStates;
+ }
+
+ _commands = commands;
+ if (_commands == null) return;
+
+ _commands.OnLoadSuccess += LoadPurgeStates;
+ _commands.OnSaveSuccess += SavePurgeStates;
+
+ LoadPurgeStates(_commands.Filepath);
+ }
+
+ /* Pull non-vanilla entity names from the CommandsPAK */
+ private static void LoadPurgeStates(string filepath)
+ {
+ _purged = (CompositePurgeTable)CustomTable.ReadTable(filepath, CustomEndTables.COMPOSITE_PURGE_STATES);
+ if (_purged == null) _purged = new CompositePurgeTable();
+ Console.WriteLine("Registered " + _purged.purged.Count + " pre-purged composites!");
+ }
+
+ /* Write non-vanilla entity names to the CommandsPAK */
+ private static void SavePurgeStates(string filepath)
+ {
+ CustomTable.WriteTable(filepath, CustomEndTables.COMPOSITE_PURGE_STATES, _purged);
+ Console.WriteLine("Stored " + _purged.purged.Count + " pre-purged composites!");
+ }
+
+ /* Gets the composite that contains the entity */
+ public static Composite GetContainedComposite(this Entity entity)
+ {
+ if (_commands == null)
+ throw (new Exception("Please link your Commands object to CommandsUtils using CommandsUtils.LinkCommands before calling this function"));
+
+ for (int i = 0; i < _commands.Entries.Count; i++)
+ {
+ switch (entity.variant)
+ {
+ case EntityVariant.FUNCTION:
+ for (int x = 0; x < _commands.Entries[i].functions.Count; x++)
+ {
+ if (_commands.Entries[i].functions[x].shortGUID == entity.shortGUID)
+ {
+ if (_commands.Entries[i].functions[x] == entity)
+ return _commands.Entries[i];
+ }
+ }
+ break;
+ case EntityVariant.VARIABLE:
+ for (int x = 0; x < _commands.Entries[i].variables.Count; x++)
+ {
+ if (_commands.Entries[i].variables[x].shortGUID == entity.shortGUID)
+ {
+ if (_commands.Entries[i].variables[x] == entity)
+ return _commands.Entries[i];
+ }
+ }
+ break;
+ case EntityVariant.PROXY:
+ for (int x = 0; x < _commands.Entries[i].proxies.Count; x++)
+ {
+ if (_commands.Entries[i].proxies[x].shortGUID == entity.shortGUID)
+ {
+ if (_commands.Entries[i].proxies[x] == entity)
+ return _commands.Entries[i];
+ }
+ }
+ break;
+ case EntityVariant.ALIAS:
+ for (int x = 0; x < _commands.Entries[i].aliases.Count; x++)
+ {
+ if (_commands.Entries[i].aliases[x].shortGUID == entity.shortGUID)
+ {
+ if (_commands.Entries[i].aliases[x] == entity)
+ return _commands.Entries[i];
+ }
+ }
+ break;
+ }
+ }
+ return null;
+ }
+
#region FUNCTION_TYPE_UTILS
/* Function Types */
private static Dictionary _functionTypeLUT = new Dictionary();
@@ -42,6 +141,10 @@ public static FunctionType GetFunctionType(byte[] tag)
{
return GetFunctionType(new ShortGuid(tag));
}
+ public static FunctionType GetFunctionType(FunctionEntity ent)
+ {
+ return GetFunctionType(ent.function);
+ }
public static FunctionType GetFunctionType(ShortGuid tag)
{
SetupFunctionTypeLUT();
@@ -160,19 +263,17 @@ public static ShortGuid GetResourceEntryTypeGUID(ResourceType type)
#region HELPER_FUNCS
/* Resolve an entity hierarchy */
- public static Entity ResolveHierarchy(Commands commands, Composite composite, List hierarchy, out Composite containedComposite, out string asString, bool includeShortGuids = true)
+ public static Entity ResolveHierarchy(Commands commands, Composite composite, ShortGuid[] hierarchy, out Composite containedComposite, out string asString, bool includeShortGuids = true)
{
- if (hierarchy.Count == 0)
+ if (hierarchy.Length == 0)
{
containedComposite = null;
asString = "";
return null;
}
- List hierarchyCopy = new List();
- for (int x = 0; x < hierarchy.Count; x++)
- hierarchyCopy.Add(new ShortGuid(hierarchy[x].ToUInt32()));
-
+ List hierarchyCopy = hierarchy.ToList();
+
Composite currentFlowgraphToSearch = composite;
if (currentFlowgraphToSearch == null || currentFlowgraphToSearch.GetEntityByID(hierarchyCopy[0]) == null)
{
@@ -189,19 +290,19 @@ public static Entity ResolveHierarchy(Commands commands, Composite composite, Li
hierarchyCopy.RemoveAt(0);
}
}
-
+
Entity entity = null;
string hierarchyString = "";
for (int i = 0; i < hierarchyCopy.Count; i++)
{
entity = currentFlowgraphToSearch.GetEntityByID(hierarchyCopy[i]);
-
+
if (entity == null) break;
- if (includeShortGuids) hierarchyString += "[" + entity.shortGUID + "] ";
+ if (includeShortGuids) hierarchyString += "[" + entity.shortGUID.ToByteString() + "] ";
hierarchyString += EntityUtils.GetName(currentFlowgraphToSearch.shortGUID, entity.shortGUID);
if (i >= hierarchyCopy.Count - 2) break; //Last is always 00-00-00-00
hierarchyString += " -> ";
-
+
if (entity.variant == EntityVariant.FUNCTION)
{
Composite flowRef = commands.GetComposite(((FunctionEntity)entity).function);
@@ -221,65 +322,54 @@ public static Entity ResolveHierarchy(Commands commands, Composite composite, Li
return entity;
}
- /* Generate all possible hierarchies for an entity */
- private static List> _hierarchies = new List>();
- public static List GenerateHierarchies(Commands commands, Composite composite, Entity entity)
+ /* Calculate an instanced entity's worldspace position & rotation */
+ public static (Vector3, Quaternion) CalculateInstancedPosition(EntityPath hierarchy)
{
- List hierarchies = new List();
- _hierarchies.Clear();
-
- GenerateHierarchiesRecursive(commands, null, commands.EntryPoints[0], composite, new List());
-
- for (int i = 0; i < _hierarchies.Count; i++)
+ cTransform globalTransform = new cTransform();
+ Composite comp = _commands.EntryPoints[0];
+ for (int x = 0; x < hierarchy.path.Length; x++)
{
- _hierarchies[i].Add(entity.shortGUID);
- hierarchies.Add(new EntityPath(_hierarchies[i]));
- }
+ FunctionEntity compInst = comp.functions.FirstOrDefault(o => o.shortGUID == hierarchy.path[x]);
+ if (compInst == null)
+ break;
- return hierarchies;
- }
- private static void GenerateHierarchiesRecursive(Commands commands, Entity ent, Composite comp, Composite target, List hierarchy)
- {
- if (ent != null)
- hierarchy.Add(ent.shortGUID);
+ Parameter positionParam = compInst.GetParameter("position");
+ if (positionParam != null && positionParam.content != null && positionParam.content.dataType == DataType.TRANSFORM)
+ globalTransform += (cTransform)positionParam.content;
- if (comp.shortGUID == target.shortGUID)
- {
- _hierarchies.Add(hierarchy);
- return;
+ comp = _commands.GetComposite(compInst.function);
+ if (comp == null)
+ break;
}
-
- Parallel.For(0, comp.functions.Count, i =>
- {
- Composite next = commands.GetComposite(comp.functions[i].function);
- if (next != null) GenerateHierarchiesRecursive(commands, comp.functions[i], next, target, new List(hierarchy.ConvertAll(x => x)));
- });
+ return (globalTransform.position, Quaternion.CreateFromYawPitchRoll(globalTransform.rotation.Y * (float)Math.PI / 180.0f, globalTransform.rotation.X * (float)Math.PI / 180.0f, globalTransform.rotation.Z * (float)Math.PI / 180.0f));
}
/* CA's CAGE doesn't properly tidy up hierarchies pointing to deleted entities - so we can do that to save confusion */
- public static void PurgeDeadLinks(Commands commands, Composite composite)
+ public static bool PurgeDeadLinks(Commands commands, Composite composite, bool force = false)
{
+ if (!force && LinkedCommands == commands && _purged.purged.Contains(composite.shortGUID))
+ {
+ //Console.WriteLine("Skipping purge, as this composite is listed within the purged table.");
+ return false;
+ }
+
int originalUnknownCount = 0;
int originalProxyCount = 0;
- int newProxyCount = 0;
int originalAliasCount = 0;
- int newAliasCount = 0;
- int originalTriggerCount = 0;
int newTriggerCount = 0;
- int originalAnimCount = 0;
+ int originalTriggerCount = 0;
int newAnimCount = 0;
- int originalLinkCount = 0;
+ int originalAnimCount = 0;
int newLinkCount = 0;
+ int originalLinkCount = 0;
int originalFuncCount = 0;
- int newFuncCount = 0;
//Clear functions
List functionsPurged = new List();
for (int i = 0; i < composite.functions.Count; i++)
if (CommandsUtils.FunctionTypeExists(composite.functions[i].function) || commands.GetComposite(composite.functions[i].function) != null)
functionsPurged.Add(composite.functions[i]);
- originalFuncCount += composite.functions.Count;
- newFuncCount += functionsPurged.Count;
+ originalFuncCount = composite.functions.Count;
composite.functions = functionsPurged;
//Clear aliases
@@ -287,8 +377,7 @@ public static void PurgeDeadLinks(Commands commands, Composite composite)
for (int i = 0; i < composite.aliases.Count; i++)
if (ResolveHierarchy(commands, composite, composite.aliases[i].alias.path, out Composite flowTemp, out string hierarchy) != null)
aliasesPurged.Add(composite.aliases[i]);
- originalAliasCount += composite.aliases.Count;
- newAliasCount += aliasesPurged.Count;
+ originalAliasCount = composite.aliases.Count;
composite.aliases = aliasesPurged;
//Clear proxies
@@ -296,8 +385,7 @@ public static void PurgeDeadLinks(Commands commands, Composite composite)
for (int i = 0; i < composite.proxies.Count; i++)
if (ResolveHierarchy(commands, composite, composite.proxies[i].proxy.path, out Composite flowTemp, out string hierarchy) != null)
proxyPurged.Add(composite.proxies[i]);
- originalProxyCount += composite.proxies.Count;
- newProxyCount += proxyPurged.Count;
+ originalProxyCount = composite.proxies.Count;
composite.proxies = proxyPurged;
//Clear TriggerSequence and CAGEAnimation entities
@@ -340,7 +428,7 @@ public static void PurgeDeadLinks(Commands commands, Composite composite)
{
List childLinksPurged = new List();
for (int x = 0; x < entities[i].childLinks.Count; x++)
- if (composite.GetEntityByID(entities[i].childLinks[x].childID) != null)
+ if (composite.GetEntityByID(entities[i].childLinks[x].linkedEntityID) != null)
childLinksPurged.Add(entities[i].childLinks[x]);
originalLinkCount += entities[i].childLinks.Count;
newLinkCount += childLinksPurged.Count;
@@ -348,22 +436,27 @@ public static void PurgeDeadLinks(Commands commands, Composite composite)
}
if (originalUnknownCount +
- (originalFuncCount - newFuncCount) +
- (originalProxyCount - newProxyCount) +
- (originalAliasCount - newAliasCount) +
+ (originalFuncCount - composite.functions.Count) +
+ (originalProxyCount - composite.proxies.Count) +
+ (originalAliasCount - composite.aliases.Count) +
(originalTriggerCount - newTriggerCount) +
(originalAnimCount - newAnimCount) +
(originalLinkCount - newLinkCount) == 0)
- return;
+ {
+ //Console.WriteLine("Purge found nothing to clear up.");
+ return true;
+ }
+
Console.WriteLine(
"Purged all dead hierarchies and entities in " + composite.name + "!" +
"\n - " + originalUnknownCount + " unknown entities" +
- "\n - " + (originalFuncCount - newFuncCount) + " functions (of " + originalFuncCount + ")" +
- "\n - " + (originalProxyCount - newProxyCount) + " proxies (of " + originalProxyCount + ")" +
- "\n - " + (originalAliasCount - newAliasCount) + " aliases (of " + originalAliasCount + ")" +
+ "\n - " + (originalFuncCount - composite.functions.Count) + " functions (of " + originalFuncCount + ")" +
+ "\n - " + (originalProxyCount - composite.proxies.Count) + " proxies (of " + originalProxyCount + ")" +
+ "\n - " + (originalAliasCount - composite.aliases.Count) + " aliases (of " + originalAliasCount + ")" +
"\n - " + (originalTriggerCount - newTriggerCount) + " triggers (of " + originalTriggerCount + ")" +
"\n - " + (originalAnimCount - newAnimCount) + " anim connections (of " + originalAnimCount + ")" +
"\n - " + (originalLinkCount - newLinkCount) + " entity links (of " + originalLinkCount + ")");
+ return true;
}
#endregion
}
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CompositeUtils.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CompositeUtils.cs
index af5d0c9..b12bf57 100644
--- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CompositeUtils.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CompositeUtils.cs
@@ -1,7 +1,13 @@
-using CATHODE.Scripting;
+using CATHODE;
+using CATHODE.Scripting;
+using CATHODE.Scripting.Internal;
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization.Json;
+using System.Security.Cryptography;
using System.Text;
#if UNITY_EDITOR || UNITY_STANDALONE
using UnityEngine;
@@ -9,10 +15,16 @@
namespace CathodeLib
{
- //Has a store of all composite paths in the vanilla game: used for prettifying the all-caps Windows strings
public static class CompositeUtils
{
- private static Dictionary pathLookup;
+ //Has a store of all composite paths in the vanilla game: can be used for prettifying the all-caps Windows strings
+ private static Dictionary _pathLookup;
+
+ //Has a store of Composite modification info for the currently linked Commands
+ private static CompositeModificationInfoTable _modificationInfo;
+
+ public static Commands LinkedCommands => _commands;
+ private static Commands _commands;
static CompositeUtils()
{
@@ -22,16 +34,50 @@ static CompositeUtils()
BinaryReader reader = new BinaryReader(new MemoryStream(CathodeLib.Properties.Resources.composite_paths));
#endif
int compositeCount = reader.ReadInt32();
- pathLookup = new Dictionary(compositeCount);
+ _pathLookup = new Dictionary(compositeCount);
for (int i = 0; i < compositeCount; i++)
- pathLookup.Add(Utilities.Consume(reader), reader.ReadString());
+ _pathLookup.Add(Utilities.Consume(reader), reader.ReadString());
+
+ _modificationInfo = new CompositeModificationInfoTable();
+ }
+
+ public static void LinkCommands(Commands commands)
+ {
+ if (_commands != null)
+ {
+ _commands.OnLoadSuccess -= LoadModificationInfo;
+ _commands.OnSaveSuccess -= SaveModificationInfo;
+ }
+
+ _commands = commands;
+ if (_commands == null) return;
+
+ _commands.OnLoadSuccess += LoadModificationInfo;
+ _commands.OnSaveSuccess += SaveModificationInfo;
+
+ LoadModificationInfo(_commands.Filepath);
+ }
+
+ private static void LoadModificationInfo(string filepath)
+ {
+ _modificationInfo = (CompositeModificationInfoTable)CustomTable.ReadTable(filepath, CustomEndTables.COMPOSITE_MODIFICATION_INFO);
+ if (_modificationInfo == null) _modificationInfo = new CompositeModificationInfoTable();
+ Console.WriteLine("Loaded modification info for " + _modificationInfo.modification_info.Count + " composites!");
+ }
+
+ private static void SaveModificationInfo(string filepath)
+ {
+ CustomTable.WriteTable(filepath, CustomEndTables.COMPOSITE_MODIFICATION_INFO, _modificationInfo);
+ Console.WriteLine("Saved modification info for " + _modificationInfo.modification_info.Count + " composites!");
}
+ /* Gets a pretty Composite name */
public static string GetFullPath(ShortGuid guid)
{
- return pathLookup.ContainsKey(guid) ? pathLookup[guid] : "";
+ return _pathLookup.ContainsKey(guid) ? _pathLookup[guid] : "";
}
+ /* Gets a pretty Composite name, including trimming direct paths */
public static string GetPrettyPath(ShortGuid guid)
{
string fullPath = GetFullPath(guid);
@@ -46,5 +92,57 @@ public static string GetPrettyPath(ShortGuid guid)
}
return fullPath;
}
+
+ /* Set/update the modification metadata for a composite */
+ public static void SetModificationInfo(CompositeModificationInfoTable.ModificationInfo info)
+ {
+ _modificationInfo.modification_info.RemoveAll(o => o.composite_id == info.composite_id);
+ _modificationInfo.modification_info.Add(info);
+ }
+
+ /* Get the modification metadata for a composite (if it exists) */
+ public static CompositeModificationInfoTable.ModificationInfo GetModificationInfo(Composite composite)
+ {
+ return _modificationInfo.modification_info.FirstOrDefault(o => o.composite_id == composite.shortGUID);
+ }
+
+ /* Generate a checksum for a Composite object */
+ public static byte[] GenerateChecksum(Composite composite)
+ {
+ int size = Marshal.SizeOf(composite);
+ byte[] arr = new byte[size];
+ IntPtr ptr = Marshal.AllocHGlobal(size);
+ try
+ {
+ Marshal.StructureToPtr(composite, ptr, true);
+ Marshal.Copy(ptr, arr, 0, size);
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(ptr);
+ }
+
+ using (SHA256 sha256 = SHA256.Create())
+ {
+ byte[] hash = sha256.ComputeHash(arr);
+ return hash;
+ }
+ }
+
+ /* Remove all links between Entities within the Composite */
+ public static void ClearAllLinks(Composite composite)
+ {
+ composite.GetEntities().ForEach(o => o.childLinks.Clear());
+ }
+
+ /* Count the number of links in the Composite */
+ public static int CountLinks(Composite composite)
+ {
+ int count = 0;
+ List entities = composite.GetEntities();
+ foreach (Entity ent in entities)
+ count += ent.childLinks.Count;
+ return count;
+ }
}
}
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CustomTable.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CustomTable.cs
index c0399d1..780ed07 100644
--- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CustomTable.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CustomTable.cs
@@ -2,8 +2,9 @@
using CATHODE.Scripting.Internal;
using System;
using System.Collections.Generic;
+using System.Drawing;
using System.IO;
-using System.Text;
+using System.Linq;
namespace CathodeLib
{
@@ -60,6 +61,18 @@ public static void WriteTable(string filepath, CustomEndTables table, Table cont
case CustomEndTables.SHORT_GUIDS:
((GuidNameTable)toWrite[tableType]).Write(writer);
break;
+ case CustomEndTables.COMPOSITE_PURGE_STATES:
+ ((CompositePurgeTable)toWrite[tableType]).Write(writer);
+ break;
+ case CustomEndTables.COMPOSITE_MODIFICATION_INFO:
+ ((CompositeModificationInfoTable)toWrite[tableType]).Write(writer);
+ break;
+ case CustomEndTables.COMPOSITE_FLOWGRAPHS:
+ ((CompositeFlowgraphsTable)toWrite[tableType]).Write(writer);
+ break;
+ case CustomEndTables.COMPOSITE_FLOWGRAPH_COMPATIBILITY_INFO:
+ ((CompositeFlowgraphCompatibilityTable)toWrite[tableType]).Write(writer);
+ break;
}
}
}
@@ -103,6 +116,18 @@ public static Table ReadTable(string filepath, CustomEndTables table)
case CustomEndTables.SHORT_GUIDS:
data = new GuidNameTable(reader);
break;
+ case CustomEndTables.COMPOSITE_PURGE_STATES:
+ data = new CompositePurgeTable(reader);
+ break;
+ case CustomEndTables.COMPOSITE_MODIFICATION_INFO:
+ data = new CompositeModificationInfoTable(reader);
+ break;
+ case CustomEndTables.COMPOSITE_FLOWGRAPHS:
+ data = new CompositeFlowgraphsTable(reader);
+ break;
+ case CustomEndTables.COMPOSITE_FLOWGRAPH_COMPATIBILITY_INFO:
+ data = new CompositeFlowgraphCompatibilityTable(reader);
+ break;
}
}
return data;
@@ -125,7 +150,7 @@ public Table(BinaryReader reader)
public CustomEndTables type = CustomEndTables.NUMBER_OF_END_TABLES;
- protected virtual void Read(BinaryReader reader)
+ public virtual void Read(BinaryReader reader)
{
}
@@ -146,7 +171,7 @@ public EntityNameTable(BinaryReader reader = null) : base(reader)
public Dictionary> names;
- protected override void Read(BinaryReader reader)
+ public override void Read(BinaryReader reader)
{
if (reader == null)
{
@@ -194,7 +219,7 @@ public GuidNameTable(BinaryReader reader = null) : base(reader)
public Dictionary cache;
public Dictionary cacheReversed;
- protected override void Read(BinaryReader reader)
+ public override void Read(BinaryReader reader)
{
if (reader == null)
{
@@ -228,4 +253,278 @@ public override void Write(BinaryWriter writer)
}
}
}
+ public class CompositePurgeTable : CustomTable.Table
+ {
+ public CompositePurgeTable(BinaryReader reader = null) : base(reader)
+ {
+ type = CustomEndTables.COMPOSITE_PURGE_STATES;
+ }
+
+ public List purged;
+
+ public override void Read(BinaryReader reader)
+ {
+ if (reader == null)
+ {
+ purged = new List();
+ return;
+ }
+
+ int count = reader.ReadInt32();
+ purged = new List(count);
+ for (int i = 0; i < count; i++)
+ {
+ ShortGuid compositeID = Utilities.Consume(reader);
+ purged.Add(compositeID);
+ }
+ }
+
+ public override void Write(BinaryWriter writer)
+ {
+ writer.Write(purged.Count);
+ for (int i = 0; i < purged.Count; i++)
+ {
+ Utilities.Write(writer, purged[i]);
+ }
+ }
+ }
+ public class CompositeModificationInfoTable : CustomTable.Table
+ {
+ public CompositeModificationInfoTable(BinaryReader reader = null) : base(reader)
+ {
+ type = CustomEndTables.COMPOSITE_MODIFICATION_INFO;
+ }
+
+ public List modification_info;
+
+ public override void Read(BinaryReader reader)
+ {
+ if (reader == null)
+ {
+ modification_info = new List();
+ return;
+ }
+
+ int count = reader.ReadInt32();
+ modification_info = new List(count);
+ for (int i = 0; i < count; i++)
+ {
+ ModificationInfo info = new ModificationInfo();
+ info.composite_id = Utilities.Consume(reader);
+ info.editor_version = reader.ReadInt32();
+ info.modification_date = reader.ReadInt32();
+ modification_info.Add(info);
+ }
+ }
+
+ public override void Write(BinaryWriter writer)
+ {
+ writer.Write(modification_info.Count);
+ for (int i = 0; i < modification_info.Count; i++)
+ {
+ Utilities.Write(writer, modification_info[i].composite_id);
+ writer.Write(modification_info[i].editor_version);
+ writer.Write(modification_info[i].modification_date);
+ }
+ }
+
+ public class ModificationInfo
+ {
+ public ShortGuid composite_id;
+ public int editor_version; //use this to store a unique identifier for whatever tool version modified the composite
+ public int modification_date; //unix timecode
+ }
+ }
+ public class CompositeFlowgraphsTable : CustomTable.Table //NOTE TO SELF: use this same class for reading/writing the default data stored in the script editor
+ {
+ public CompositeFlowgraphsTable(BinaryReader reader = null) : base(reader)
+ {
+ type = CustomEndTables.COMPOSITE_FLOWGRAPHS;
+ }
+
+ public List flowgraphs;
+
+ public override void Read(BinaryReader reader)
+ {
+ flowgraphs = new List();
+ if (reader == null)
+ return;
+
+ int count = reader.ReadInt32();
+ if (count == 0)
+ return;
+
+ byte version = reader.ReadByte();
+ if (version != FlowgraphMeta.VERSION)
+ {
+ //Add compatibility here when required
+ }
+ for (int i = 0; i < count; i++)
+ {
+ FlowgraphMeta flowgraph = new FlowgraphMeta();
+
+ flowgraph.CompositeGUID = Utilities.Consume(reader);
+ flowgraph.Name = reader.ReadString();
+
+ flowgraph.CanvasPosition = new PointF(reader.ReadSingle(), reader.ReadSingle());
+ flowgraph.CanvasScale = reader.ReadSingle();
+
+ flowgraph.UsesShortenedNames = reader.ReadBoolean();
+ flowgraph.IsUnfinished = reader.ReadBoolean();
+ reader.BaseStream.Position += 8; //reserved
+
+ int nodeMetaCount = reader.ReadInt32();
+ for (int x = 0; x < nodeMetaCount; x++)
+ {
+ FlowgraphMeta.NodeMeta node = new FlowgraphMeta.NodeMeta();
+ node.EntityGUID = Utilities.Consume(reader);
+ node.NodeID = reader.ReadInt32();
+
+ node.Position = new Point(reader.ReadInt32(), reader.ReadInt32());
+
+ int inCount = reader.ReadInt32();
+ node.PinsIn = Utilities.ConsumeArray(reader, inCount).ToList();
+ int outCount = reader.ReadInt32();
+ node.PinsOut = Utilities.ConsumeArray(reader, outCount).ToList();
+
+ int connectionCount = reader.ReadInt32();
+ for (int z = 0; z < connectionCount; z++)
+ {
+ FlowgraphMeta.NodeMeta.ConnectionMeta connection = new FlowgraphMeta.NodeMeta.ConnectionMeta();
+ connection.ParameterGUID = Utilities.Consume(reader);
+ connection.ConnectedEntityGUID = Utilities.Consume(reader);
+ connection.ConnectedParameterGUID = Utilities.Consume(reader);
+ connection.ConnectedNodeID = reader.ReadInt32();
+ node.Connections.Add(connection);
+ }
+
+ flowgraph.Nodes.Add(node);
+ }
+ flowgraphs.Add(flowgraph);
+ }
+ }
+
+ public override void Write(BinaryWriter writer)
+ {
+ writer.Write(flowgraphs.Count);
+ writer.Write(FlowgraphMeta.VERSION);
+ for (int i = 0; i < flowgraphs.Count; i++)
+ {
+ Utilities.Write(writer, flowgraphs[i].CompositeGUID);
+ writer.Write(flowgraphs[i].Name);
+
+ writer.Write(flowgraphs[i].CanvasPosition.X);
+ writer.Write(flowgraphs[i].CanvasPosition.Y);
+ writer.Write(flowgraphs[i].CanvasScale);
+
+ writer.Write(flowgraphs[i].UsesShortenedNames);
+ writer.Write(flowgraphs[i].IsUnfinished);
+ writer.Write(new byte[8]); //reserved
+
+ writer.Write(flowgraphs[i].Nodes.Count);
+ for (int x = 0; x < flowgraphs[i].Nodes.Count; x++)
+ {
+ Utilities.Write(writer, flowgraphs[i].Nodes[x].EntityGUID);
+ writer.Write(flowgraphs[i].Nodes[x].NodeID);
+
+ writer.Write(flowgraphs[i].Nodes[x].Position.X);
+ writer.Write(flowgraphs[i].Nodes[x].Position.Y);
+
+ writer.Write(flowgraphs[i].Nodes[x].PinsIn.Count);
+ Utilities.Write(writer, flowgraphs[i].Nodes[x].PinsIn);
+ writer.Write(flowgraphs[i].Nodes[x].PinsOut.Count);
+ Utilities.Write(writer, flowgraphs[i].Nodes[x].PinsOut);
+
+ writer.Write(flowgraphs[i].Nodes[x].Connections.Count);
+ for (int z = 0; z < flowgraphs[i].Nodes[x].Connections.Count; z++)
+ {
+ Utilities.Write(writer, flowgraphs[i].Nodes[x].Connections[z].ParameterGUID);
+ Utilities.Write(writer, flowgraphs[i].Nodes[x].Connections[z].ConnectedEntityGUID);
+ Utilities.Write(writer, flowgraphs[i].Nodes[x].Connections[z].ConnectedParameterGUID);
+ writer.Write(flowgraphs[i].Nodes[x].Connections[z].ConnectedNodeID);
+ }
+ }
+ }
+ }
+
+ public class FlowgraphMeta
+ {
+ public const byte VERSION = 1;
+ public bool UsesShortenedNames = false;
+ public bool IsUnfinished = false;
+
+ public ShortGuid CompositeGUID;
+ public string Name;
+
+ public PointF CanvasPosition;
+ public float CanvasScale;
+
+ public List Nodes = new List();
+
+ public class NodeMeta
+ {
+ public ShortGuid EntityGUID;
+ public int NodeID;
+
+ public Point Position;
+
+ public List PinsIn = new List();
+ public List PinsOut = new List();
+
+ public List Connections = new List(); //NOTE: This is connections OUT of this node
+
+ public class ConnectionMeta
+ {
+ public ShortGuid ParameterGUID;
+ public ShortGuid ConnectedEntityGUID;
+ public ShortGuid ConnectedParameterGUID;
+ public int ConnectedNodeID;
+ }
+ }
+ }
+ }
+ public class CompositeFlowgraphCompatibilityTable : CustomTable.Table
+ {
+ public CompositeFlowgraphCompatibilityTable(BinaryReader reader = null) : base(reader)
+ {
+ type = CustomEndTables.COMPOSITE_FLOWGRAPH_COMPATIBILITY_INFO;
+ }
+
+ public List compatibility_info;
+
+ public override void Read(BinaryReader reader)
+ {
+ if (reader == null)
+ {
+ compatibility_info = new List();
+ return;
+ }
+
+ int count = reader.ReadInt32();
+ compatibility_info = new List(count);
+ for (int i = 0; i < count; i++)
+ {
+ CompatibilityInfo info = new CompatibilityInfo();
+ info.composite_id = Utilities.Consume(reader);
+ info.flowgraphs_supported = reader.ReadBoolean();
+ compatibility_info.Add(info);
+ }
+ }
+
+ public override void Write(BinaryWriter writer)
+ {
+ writer.Write(compatibility_info.Count);
+ for (int i = 0; i < compatibility_info.Count; i++)
+ {
+ Utilities.Write(writer, compatibility_info[i].composite_id);
+ writer.Write(compatibility_info[i].flowgraphs_supported);
+ }
+ }
+
+ public class CompatibilityInfo
+ {
+ public ShortGuid composite_id;
+ public bool flowgraphs_supported;
+ }
+ }
}
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/EntityUtils.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/EntityUtils.cs
index af4121b..0a75d36 100644
--- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/EntityUtils.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/EntityUtils.cs
@@ -36,6 +36,20 @@ static EntityUtils()
reader.Close();
}
+ //For testing
+ public static List GetAllVanillaNames()
+ {
+ List names = new List();
+ foreach (var entry in _vanilla.names)
+ {
+ foreach (var entry2 in entry.Value)
+ {
+ names.Add(entry2.Value);
+ }
+ }
+ return names;
+ }
+
/* Optionally, link a Commands file which can be used to save custom entity names to */
public static void LinkCommands(Commands commands)
{
@@ -131,7 +145,7 @@ private static void ApplyDefaults(Entity entity, FunctionType currentType, bool
/* Gets the function this function inherits from - you can keep calling this down to EntityMethodInterface */
public static FunctionType GetBaseFunction(FunctionEntity entity)
{
- return GetBaseFunction(CommandsUtils.GetFunctionType(entity.function));
+ return GetBaseFunction(CommandsUtils.GetFunctionType(entity));
}
public static FunctionType GetBaseFunction(FunctionType type)
{
@@ -158,15 +172,11 @@ private static FunctionType GetBaseFunctionInternal(FunctionType type)
{
switch (type)
{
- //These types have no inheritance - perhaps they are unused?
- case FunctionType.CharacterMonitor:
+ //These are best guesses
case FunctionType.WEAPON_DidHitSomethingFilter:
- case FunctionType.VariableEnumString:
- case FunctionType.SetEnumString:
- case FunctionType.NetworkProxy:
+ return FunctionType.ScriptInterface;
case FunctionType.DebugPositionMarker:
- default:
- throw new Exception();
+ return FunctionType.SensorInterface;
//This is as far as we go, but it actually inherits from EntityResourceInterface
case FunctionType.EntityMethodInterface:
@@ -1797,6 +1807,7 @@ private static FunctionType GetBaseFunctionInternal(FunctionType type)
case FunctionType.ZoneLoaded:
return FunctionType.ScriptInterface;
}
+ throw new Exception("Unhandled function type");
}
/* Applies all default parameter data to a Function entity (DESTRUCTIVE!) */
@@ -7669,9 +7680,9 @@ private static void ApplyDefaultsInternal(Entity entity, FunctionType type)
case FunctionType.BlendLowResFrame:
entity.AddParameter("blend_value", new cFloat(0.0f), ParameterVariant.PARAMETER); //float
break;
- case FunctionType.CharacterMonitor:
- entity.AddParameter("character", new cFloat(), ParameterVariant.INPUT); //ResourceID
- break;
+ //case FunctionType.CharacterMonitor:
+ // entity.AddParameter("character", new cFloat(), ParameterVariant.INPUT); //ResourceID
+ // break;
case FunctionType.AreaHitMonitor:
entity.AddParameter("on_flamer_hit", new cFloat(), ParameterVariant.TARGET); //
entity.AddParameter("on_shotgun_hit", new cFloat(), ParameterVariant.TARGET); //
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/ShortGuidUtils.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/ShortGuidUtils.cs
index 1212c8a..ca0cbbb 100644
--- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/ShortGuidUtils.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/ShortGuidUtils.cs
@@ -49,13 +49,12 @@ public static void LinkCommands(Commands commands)
}
/* Generate a ShortGuid to interface with the Cathode scripting system */
- public static ShortGuid Generate(string value)
+ public static ShortGuid Generate(string value, bool cache = true)
{
- return Generate(value, true);
- }
- private static ShortGuid Generate(string value, bool cache = true)
- {
- if (_vanilla.cache.ContainsKey(value)) return _vanilla.cache[value];
+ if (_vanilla.cache.ContainsKey(value))
+ return _vanilla.cache[value];
+ if (_custom.cache.ContainsKey(value))
+ return _custom.cache[value];
SHA1Managed sha1 = new SHA1Managed();
byte[] hash1 = sha1.ComputeHash(Encoding.UTF8.GetBytes(value));
@@ -136,9 +135,14 @@ private static void Cache(ShortGuid guid, string value, bool isVanilla = false)
}
else
{
+ //TODO: need to fix this for BSPNOSTROMO_RIPLEY_PATCH (?)
if (_custom.cache.ContainsKey(value)) return;
_custom.cache.Add(value, guid);
- _custom.cacheReversed.Add(guid, value);
+ try
+ {
+ _custom.cacheReversed.Add(guid, value);
+ }
+ catch { }
}
}
diff --git a/CathodeLib/Scripts/CATHODE/EnvironmentMaps.cs b/CathodeLib/Scripts/CATHODE/EnvironmentMaps.cs
index 2fc6506..fd75b73 100644
--- a/CathodeLib/Scripts/CATHODE/EnvironmentMaps.cs
+++ b/CathodeLib/Scripts/CATHODE/EnvironmentMaps.cs
@@ -1,6 +1,7 @@
-using CathodeLib;
+using CathodeLib;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Runtime.InteropServices;
namespace CATHODE
@@ -9,10 +10,11 @@ namespace CATHODE
public class EnvironmentMaps : CathodeFile
{
public List Entries = new List();
- public static new Implementation Implementation = Implementation.LOAD | Implementation.SAVE;
+ public static new Implementation Implementation = Implementation.LOAD | Implementation.SAVE | Implementation.CREATE;
public EnvironmentMaps(string path) : base(path) { }
- private int _unknownValue = 12; //TODO: need to figure out what this val is to be able to create file from scratch
+ //This is the number of environment maps in the level. We should never reference an index higher than this.
+ public int EnvironmentMapCount = 0;
#region FILE_IO
override protected bool LoadInternal()
@@ -21,32 +23,34 @@ override protected bool LoadInternal()
{
reader.BaseStream.Position += 8;
int entryCount = reader.ReadInt32();
- _unknownValue = reader.ReadInt32();
for (int i = 0; i < entryCount; i++)
{
Mapping entry = new Mapping();
- entry.EnvMapIndex = reader.ReadInt32();
entry.MoverIndex = reader.ReadInt32();
+ entry.EnvMapIndex = reader.ReadInt32();
Entries.Add(entry);
}
+ EnvironmentMapCount = reader.ReadInt32();
}
return true;
}
override protected bool SaveInternal()
{
+ List orderedEntries = Entries.OrderBy(o => o.MoverIndex).ToList();
+
using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(_filepath)))
{
writer.BaseStream.SetLength(0);
Utilities.WriteString("envm", writer);
writer.Write(1);
writer.Write(Entries.Count);
- writer.Write(_unknownValue); //TODO: what is this value? need to know for making new files.
- for (int i = 0; i < Entries.Count; i++)
+ for (int i = 0; i < orderedEntries.Count; i++)
{
- writer.Write(Entries[i].EnvMapIndex);
- writer.Write(Entries[i].MoverIndex);
+ writer.Write(orderedEntries[i].MoverIndex);
+ writer.Write(orderedEntries[i].EnvMapIndex);
}
+ writer.Write(EnvironmentMapCount);
}
return true;
}
diff --git a/CathodeLib/Scripts/CATHODE/Lights.cs b/CathodeLib/Scripts/CATHODE/Lights.cs
index 08882de..0b0a12e 100644
--- a/CathodeLib/Scripts/CATHODE/Lights.cs
+++ b/CathodeLib/Scripts/CATHODE/Lights.cs
@@ -1,4 +1,4 @@
-using CathodeLib;
+using CathodeLib;
using System;
using System.Collections.Generic;
using System.IO;
@@ -9,7 +9,20 @@ namespace CATHODE.EXPERIMENTAL
/* DATA/ENV/PRODUCTION/x/WORLD/LIGHTS.BIN */
public class Lights : CathodeFile
{
- public List Entries = new List();
+ //NOTE: we can read this file in/out, but we don't really know the data yet.
+
+ public List Indexes = new List();
+ public List Values = new List();
+
+ public int UnknownValue0;
+ public int UnknownValue1;
+ public int UnknownValue2;
+ public int UnknownValue3;
+ public int UnknownValue4;
+ public int UnknownValue5;
+ public int UnknownValue6;
+ public int UnknownValue7;
+
public static new Implementation Implementation = Implementation.NONE;
public Lights(string path) : base(path) { }
@@ -22,26 +35,65 @@ override protected bool LoadInternal()
int entryCount = reader.ReadInt32();
for (int i = 0; i < entryCount; i++)
{
- Light entry = new Light();
- entry.MoverIndex = reader.ReadInt32();
- Entries.Add(entry);
+ Indexes.Add(reader.ReadInt32()); //I think this is MVR index
}
- for (int i = 0; i < entryCount; i++)
+ int nextCount = reader.ReadInt16();
+
+ //Assertions
+ if (nextCount != (entryCount * 2) - 1)
{
- //TODO: Really not sure on almost all of this structure yet
- Entries[i].unk1 = reader.ReadSingle();
- Entries[i].unk2 = reader.ReadSingle();
- Entries[i].unk3 = reader.ReadSingle();
- Entries[i].unk4 = reader.ReadSingle();
- Entries[i].unk5 = reader.ReadSingle();
- Entries[i].unk6 = reader.ReadSingle();
- Entries[i].OffsetOrIndex = reader.ReadInt32();
- Entries[i].LightIndex0 = reader.ReadInt16();
- Entries[i].unk7 = reader.ReadInt16();
- Entries[i].LightIndex1 = reader.ReadInt16();
- Entries[i].unk8 = reader.ReadInt16();
+ string gdsfdsf = "";
+ }
+
+ for (int i = 0; i < nextCount; i++)
+ {
+ short[] array = new short[18];
+ for (int x = 0; x < 18; x++)
+ array[x] = reader.ReadInt16();
+ Values.Add(array);
+
+ //Assertions
+ if (array[17] != 0)
+ {
+ string sdsdfd = "";
+ }
+ if (array[16] != 0 && array[16] != 1)
+ {
+ string sdsdfd = "";
+ }
+ if (array[15] < 0)
+ {
+ string sdsdfd = "";
+ }
+ if (array[14] < 0)
+ {
+ //i think this is an index
+ string sdsdfd = "";
+ }
+ }
+
+ //Additional unknowns
+ UnknownValue0 = reader.ReadChar();
+ UnknownValue1 = reader.ReadInt32();
+ UnknownValue2 = reader.ReadInt32();
+ UnknownValue3 = reader.ReadInt32();
+ UnknownValue4 = reader.ReadInt32();
+ UnknownValue5 = reader.ReadInt32();
+ UnknownValue6 = reader.ReadInt32();
+ UnknownValue7 = reader.ReadInt16();
+
+ //Assertions
+ if (UnknownValue0 != 0 ||
+ UnknownValue1 != 0 ||
+ UnknownValue2 != 0 ||
+ UnknownValue3 != 0 ||
+ UnknownValue4 != 0 ||
+ UnknownValue5 != 0 ||
+ UnknownValue6 != 0 ||
+ UnknownValue7 != 0)
+ {
+ string dfdf = "";
}
- //TODO: we also leave some data behind here
}
return true;
}
@@ -50,54 +102,40 @@ override protected bool SaveInternal()
{
using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(_filepath)))
{
- //writer.BaseStream.SetLength(0);
+ //TODO: this data is ordered by array entry -4 and then -3 i think
+
+ writer.BaseStream.SetLength(0);
Utilities.WriteString("ligt", writer);
writer.Write(4);
- writer.Write(Entries.Count);
- for (int i = 0; i < Entries.Count; i++)
- {
- writer.Write((Int32)Entries[i].MoverIndex);
- }
- for (int i = 0; i < Entries.Count; i++)
+ writer.Write(Indexes.Count);
+ for (int i = 0; i < Indexes.Count; i++)
+ writer.Write(Indexes[i]);
+ writer.Write((Int16)Values.Count);
+ for (int i = 0; i < Values.Count; i++)
{
- writer.Write(Entries[i].unk1);
- writer.Write(Entries[i].unk2);
- writer.Write(Entries[i].unk3);
- writer.Write(Entries[i].unk4);
- writer.Write(Entries[i].unk5);
- writer.Write(Entries[i].unk6);
- writer.Write((Int32)Entries[i].OffsetOrIndex);
- writer.Write((Int16)Entries[i].LightIndex0);
- writer.Write((Int16)Entries[i].unk7);
- writer.Write((Int16)Entries[i].LightIndex1);
- writer.Write((Int16)Entries[i].unk8);
+ if (Values[i].Length != 18)
+ throw new Exception("Entry was of unexpected length.");
+
+ for (int x = 0; x < Values[i].Length; x++)
+ writer.Write((Int16)Values[i][x]);
}
- //TODO: another block here i don't know
- //writer.Write(new byte[27]); //it seems like you have a 27-byte buffer at the end?
+
+ writer.Write((char)UnknownValue0);
+ writer.Write(UnknownValue1);
+ writer.Write(UnknownValue2);
+ writer.Write(UnknownValue3);
+ writer.Write(UnknownValue4);
+ writer.Write(UnknownValue5);
+ writer.Write(UnknownValue6);
+ writer.Write((Int16)UnknownValue7);
+
}
return true;
}
#endregion
#region STRUCTURES
- public class Light
- {
- public int MoverIndex; //Index of the mover in the MODELS.MVR file
-
- public float unk1;
- public float unk2;
- public float unk3;
- public float unk4;
- public float unk5;
- public float unk6;
-
- public int OffsetOrIndex;
-
- public int LightIndex0;
- public int unk7;
- public int LightIndex1;
- public int unk8;
- };
+
#endregion
}
}
\ No newline at end of file
diff --git a/CathodeLib/Scripts/CATHODE/Movers.cs b/CathodeLib/Scripts/CATHODE/Movers.cs
index ada7240..8ff8e7d 100644
--- a/CathodeLib/Scripts/CATHODE/Movers.cs
+++ b/CathodeLib/Scripts/CATHODE/Movers.cs
@@ -23,17 +23,26 @@ public Movers(string path) : base(path) { }
private List _writeList = new List();
+ ~Movers()
+ {
+ Entries.Clear();
+ _writeList.Clear();
+ }
+
#region FILE_IO
override protected bool LoadInternal()
{
+ //note: first 12 always renderable but not linked to commands -> they are always the same models across every level. is it the content of GLOBAL?
+
using (BinaryReader reader = new BinaryReader(File.OpenRead(_filepath)))
{
reader.BaseStream.Position += 4;
int entryCount = reader.ReadInt32();
- _entryCountUnk = reader.ReadInt32(); //a count of something - not sure what
+ _entryCountUnk = reader.ReadInt32(); //a count of something - not sure what. not sure if it's actually used by the game
reader.BaseStream.Position += 20;
Entries = new List(Utilities.ConsumeArray(reader, entryCount));
}
+
_writeList.AddRange(Entries);
return true;
}
@@ -188,42 +197,57 @@ RenderableElementSet is always paired with a MOVER_DESCRIPTOR (see RenderableSce
public Matrix4x4 transform;
//64
- public GPU_CONSTANTS gpuConstants;
+ public GPU_CONSTANTS gpu_constants;
//144
public UInt64 fogsphere_val1; // 0xa0 in RenderableFogSphereInstance
public UInt64 fogsphere_val2; // 0xa8 in RenderableFogSphereInstance
//160
- public RENDER_CONSTANTS renderConstants;
+ public RENDER_CONSTANTS render_constants;
+
//244
- public UInt32 renderableElementIndex; //reds.bin index
- public UInt32 renderableElementCount; //reds.bin count
+ public UInt32 renderable_element_index; //reds.bin index
+ public UInt32 renderable_element_count; //reds.bin count
- public UInt32 resourcesIndex;
+ public int resource_index; //This is the index value from Resources.bin
//256
public Vector3 Unknowns5_;
public UInt32 visibility; // pulled from iOS dump - should be visibility var?
//272
- public CommandsEntityReference entity; //The entity in the Commands file
+ public EntityHandle entity; //The entity in the Commands file
- //280
- public Int32 environmentMapIndex; //environment_map.bin index - converted to short in code
+ //280
+ public Int32 environment_map_index = -1; //environment_map.bin index - converted to short in code
//284
public float emissive_val1; //emissive surface val1
public float emissive_val2; //emissive surface val2
public float emissive_val3; //emissive surface val3
//296
- public ShortGuid zoneID; //zone id? RenderableScene::create_instance, RenderableScene::initialize
- public ShortGuid zoneActivator; //zone activator? RenderableScene::create_instance, RenderableScene::initialize
+
+ //If primary zone ID or secondary zone ID are zero, they are not applied to a zone. it seems like the game hacks this by setting the primary id to 1 to add it to a sort of "global zone", for entities that are spawned but not in a zone.
+ public ShortGuid primary_zone_id;
+ public ShortGuid secondary_zone_id;
+
//304
public UInt32 Unknowns61_; //uVar3 in reserve_light_light_master_sets, val of LightMasterSet, or an incrementer
- public UInt16 Unknown17_; // TODO: It is -1 most of the time, but some times it isn't.
+
+
+ public UInt16 Unknown17_; // TODO: flags? always "65535" on BSP_LV426 1 and 2
+
+
//310
public UInt16 instanceTypeFlags; //ushort - used for bitwise flags depending on mover RENDERABLE_INSTANCE::Type. Environment types seem to use first bit to decide if its position comes from MVR.
//312
public UInt32 Unknowns70_;
public UInt32 Unknowns71_;
//320
+
+ ~MOVER_DESCRIPTOR()
+ {
+ gpu_constants = null;
+ render_constants = null;
+ entity = null;
+ }
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
@@ -247,24 +271,29 @@ public class GPU_CONSTANTS //As best I can tell, this is 80 bytes long
//64
- public Vector4 LightColour;
- public Vector4 MaterialTint;
+ public Vector4 LightColour = new Vector4(1,1,1,1);
+ public Vector4 MaterialTint = new Vector4(1,1,1,1);
//96
- public float lightVolumeIntensity; //todo: idk if this is right, but editing this value seems to increase/decrease the brightness of the light volume meshes
- public float particleIntensity; //0 = black particle
- public float particleSystemOffset; //todo: not sure entirely, but increasing this value seems to apply an offset to particle systems
+ public float lightVolumeIntensity = 1; //todo: idk if this is right, but editing this value seems to increase/decrease the brightness of the light volume meshes
+ public float particleIntensity = 1; //0 = black particle
+ public float particleSystemOffset = 0; //todo: not sure entirely, but increasing this value seems to apply an offset to particle systems
//108
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] blankSpace1;
//116
- public float lightRadius;
- public Vector2 textureTile; //x = horizontal tile, y = vertical tile
+ public float lightRadius = 0;
+ public Vector2 textureTile = new Vector2(1,1); //x = horizontal tile, y = vertical tile
//128
- public float UnknownValue1_;
- public float UnknownValue2_;
- public float UnknownValue3_;
- public float UnknownValue4_;
+ public float UnknownValue1_ = 1;
+ public float UnknownValue2_ = 1;
+ public float UnknownValue3_ = 0;
+ public float UnknownValue4_ = 0;
//144
+
+ ~GPU_CONSTANTS()
+ {
+ blankSpace1 = null;
+ }
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
@@ -281,10 +310,10 @@ used in
*/
//160
- public Vector4 Unknown3_;
+ public Vector4 Unknown3_ = new Vector4(0,0,0,0);
//176
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
- public UInt32[] Unknowns2_;
+ public UInt32[] Unknowns2_;
//184
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public Vector3[] UnknownMinMax_; // NOTE: Sometimes I see 'nan's here too.
@@ -292,6 +321,13 @@ used in
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)]
public byte[] blankSpace3;
//244
+
+ ~RENDER_CONSTANTS()
+ {
+ Unknowns2_ = null;
+ UnknownMinMax_ = null;
+ blankSpace3 = null;
+ }
}
#endregion
}
diff --git a/CathodeLib/Scripts/CATHODE/PAK2.cs b/CathodeLib/Scripts/CATHODE/PAK2.cs
index 27a1a6f..61a1b60 100644
--- a/CathodeLib/Scripts/CATHODE/PAK2.cs
+++ b/CathodeLib/Scripts/CATHODE/PAK2.cs
@@ -12,6 +12,11 @@ public class PAK2 : CathodeFile
public static new Implementation Implementation = Implementation.CREATE | Implementation.LOAD | Implementation.SAVE;
public PAK2(string path) : base(path) { }
+ ~PAK2()
+ {
+ Entries.Clear();
+ }
+
#region FILE_IO
override protected bool LoadInternal()
{
@@ -85,6 +90,11 @@ override protected bool SaveInternal()
#region STRUCTURES
public class File
{
+ ~File()
+ {
+ Content = null;
+ }
+
public string Filename = "";
public byte[] Content;
}
diff --git a/CathodeLib/Scripts/CATHODE/PathBarrierResources.cs b/CathodeLib/Scripts/CATHODE/PathBarrierResources.cs
index 1e07ff4..fc5a888 100644
--- a/CathodeLib/Scripts/CATHODE/PathBarrierResources.cs
+++ b/CathodeLib/Scripts/CATHODE/PathBarrierResources.cs
@@ -45,7 +45,7 @@ override protected bool SaveInternal()
{
reader.Write((Int32)Entries[i].resourcesBinIndex);
reader.Write((Int32)(i + 1));
- reader.Write((Int16)Entries[i].unk1);
+ reader.Write((Int16)Entries[i].unk1);
reader.Write((Int16)Entries[i].unk2);
}
}
@@ -57,7 +57,7 @@ override protected bool SaveInternal()
public class Entry
{
public int resourcesBinIndex;
- public int unk1;
+ public int unk1; //todo: perhaps this is a ShortGuid instance thing?
public int unk2;
}
#endregion
diff --git a/CathodeLib/Scripts/CATHODE/PhysicsMaps.cs b/CathodeLib/Scripts/CATHODE/PhysicsMaps.cs
index d193378..56f652a 100644
--- a/CathodeLib/Scripts/CATHODE/PhysicsMaps.cs
+++ b/CathodeLib/Scripts/CATHODE/PhysicsMaps.cs
@@ -1,10 +1,9 @@
-using System.IO;
-using System.Runtime.InteropServices;
+using System.IO;
using CathodeLib;
using System.Collections.Generic;
using CATHODE.Scripting;
using System;
-using System.Linq;
+
#if UNITY_EDITOR || UNITY_STANDALONE_WIN
using UnityEngine;
#else
@@ -34,12 +33,35 @@ override protected bool LoadInternal()
Entry entry = new Entry();
entry.physics_system_index = reader.ReadInt32();
reader.BaseStream.Position += 4;
- entry.resource_type = Utilities.Consume(reader);
+ entry.resource_type = Utilities.Consume(reader);
+
+ if (entry.resource_type != ShortGuidUtils.Generate("DYNAMIC_PHYSICS_SYSTEM"))
+ throw new Exception("Unexpected resource type! Expected DYNAMIC_PHYSICS_SYSTEM.");
+
entry.composite_instance_id = Utilities.Consume(reader);
- entry.entity = Utilities.Consume(reader);
- entry.Row0 = Utilities.Consume(reader);
- entry.Row1 = Utilities.Consume(reader);
- entry.Row2 = Utilities.Consume(reader);
+ entry.entity = Utilities.Consume(reader);
+
+ Vector4 Row0 = Utilities.Consume(reader);
+ Vector4 Row1 = Utilities.Consume(reader);
+ Vector4 Row2 = Utilities.Consume(reader);
+ double[,] matrix = new double[,]
+ {
+ {Row0.X, Row0.Y, Row0.Z, Row0.W},
+ {Row1.X, Row1.Y, Row1.Z, Row1.W},
+ {Row2.X, Row2.Y, Row2.Z, Row2.W},
+ };
+
+ entry.Position = new Vector3(
+ (float)matrix[0, 3],
+ (float)matrix[1, 3],
+ (float)matrix[2, 3]
+ );
+ entry.Rotation = Quaternion.CreateFromRotationMatrix(new Matrix4x4(
+ (float)matrix[0, 0], (float)matrix[0, 1], (float)matrix[0, 2], 0,
+ (float)matrix[1, 0], (float)matrix[1, 1], (float)matrix[1, 2], 0,
+ (float)matrix[2, 0], (float)matrix[2, 1], (float)matrix[2, 2], 0,
+ 0, 0, 0, 1
+ ));
reader.BaseStream.Position += 8;
Entries.Add(entry);
@@ -50,6 +72,8 @@ override protected bool LoadInternal()
override protected bool SaveInternal()
{
+ //Entries = Entries.OrderBy(o => o.physics_system_index).ToList();
+
using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(_filepath)))
{
writer.BaseStream.SetLength(0);
@@ -62,9 +86,15 @@ override protected bool SaveInternal()
Utilities.Write(writer, Entries[i].resource_type);
Utilities.Write(writer, Entries[i].composite_instance_id);
Utilities.Write(writer, Entries[i].entity);
- Utilities.Write(writer, Entries[i].Row0);
- Utilities.Write(writer, Entries[i].Row1);
- Utilities.Write(writer, Entries[i].Row2);
+
+ Matrix4x4 rotationMatrix4x4 = Matrix4x4.CreateFromQuaternion(Entries[i].Rotation);
+ Vector4 Row0 = new Vector4(rotationMatrix4x4.M11, rotationMatrix4x4.M12, rotationMatrix4x4.M13, Entries[i].Position.X);
+ Vector4 Row1 = new Vector4(rotationMatrix4x4.M21, rotationMatrix4x4.M22, rotationMatrix4x4.M23, Entries[i].Position.Y);
+ Vector4 Row2 = new Vector4(rotationMatrix4x4.M31, rotationMatrix4x4.M32, rotationMatrix4x4.M33, Entries[i].Position.Z);
+
+ Utilities.Write(writer, Row0);
+ Utilities.Write(writer, Row1);
+ Utilities.Write(writer, Row2);
writer.Write(new byte[8]);
}
}
@@ -76,7 +106,7 @@ override protected bool SaveInternal()
public class Entry
{
//Should match system_index on the PhysicsSystem entity.
- public int physics_system_index;
+ public int physics_system_index; //TODO: is this the havok index? collision.map points to havok indexes, so would make sense
//DYNAMIC_PHYSICS_SYSTEM
public ShortGuid resource_type;
@@ -86,11 +116,11 @@ public class Entry
public ShortGuid composite_instance_id;
//This is the entity ID and instance ID for the actual instanced composite entity (basically, a step down from the instance above).
- public CommandsEntityReference entity;
+ public EntityHandle entity;
- public Vector4 Row0; // NOTE: This is a 3x4 matrix, seems to have rotation data on the leftmost 3x3 matrix, and position
- public Vector4 Row1; // on the rightmost 3x1 matrix.
- public Vector4 Row2;
+ //This is the worldspace position of the composite instance
+ public Vector3 Position;
+ public Quaternion Rotation;
};
#endregion
}
diff --git a/CathodeLib/Scripts/CATHODE/Resources.cs b/CathodeLib/Scripts/CATHODE/Resources.cs
index 4b18865..dd415ca 100644
--- a/CathodeLib/Scripts/CATHODE/Resources.cs
+++ b/CathodeLib/Scripts/CATHODE/Resources.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using CATHODE.Scripting;
using CathodeLib;
+using static CATHODE.Resources;
namespace CATHODE
{
@@ -24,46 +26,60 @@ override protected bool LoadInternal()
int entryCount = reader.ReadInt32();
reader.BaseStream.Position += 4;
+ Resource[] entries = new Resource[entryCount];
for (int i = 0; i < entryCount; i++)
{
Resource resource = new Resource();
-
- //TODO: I don't think this is as it seems... the composite_instance_id value often translates to a ShortGuid string, frequently Door/AnimatedModel/Light/DYNAMIC_PHYSICS_SYSTEM...
- // ... and notably the number of entries that translate to DYNAMIC_PHYSICS_SYSTEM match the number of entries in PHYSICS.MAP (which defines the systems)
-
- resource.Entity = Utilities.Consume(reader);
- resource.IndexFromMVREntry = reader.ReadInt32();
- Entries.Add(resource);
+ resource.composite_instance_id = Utilities.Consume(reader);
+ resource.resource_id = Utilities.Consume(reader); //this is the id that's used in commands.pak, frequently translates to Door/AnimatedModel/Light/DYNAMIC_PHYSICS_SYSTEM
+ int index = reader.ReadInt32();
+ entries[index] = resource;
}
+ Entries = entries.ToList();
}
return true;
}
override protected bool SaveInternal()
{
+ List orderedEntries = Entries.OrderBy(o => o.composite_instance_id).ThenBy(o => o.resource_id).ToList();
+
using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(_filepath)))
{
writer.BaseStream.SetLength(0);
writer.Write(new byte[4] { 0xCC, 0xBA, 0xED, 0xFE });
writer.Write((Int32)1);
- writer.Write(Entries.Count);
+ writer.Write(orderedEntries.Count);
writer.Write((Int32)0);
- for (int i = 0; i < Entries.Count; i++)
+ for (int i = 0; i < orderedEntries.Count; i++)
{
- Utilities.Write(writer, Entries[i].Entity);
- writer.Write(Entries[i].IndexFromMVREntry);
+ Utilities.Write(writer, orderedEntries[i].composite_instance_id);
+ Utilities.Write(writer, orderedEntries[i].resource_id);
+ writer.Write(Entries.IndexOf(orderedEntries[i]));
}
}
return true;
}
#endregion
+ public void AddUniqueResource(ShortGuid composite_instance_id, ShortGuid resource_id)
+ {
+ if (Entries.FirstOrDefault(o => o.composite_instance_id == composite_instance_id && o.resource_id == resource_id) != null)
+ return;
+
+ Entries.Add(new Resource()
+ {
+ composite_instance_id = composite_instance_id,
+ resource_id = resource_id
+ });
+ }
+
#region STRUCTURES
public class Resource
{
- public CommandsEntityReference Entity;
- public int IndexFromMVREntry; // NOTE: This is an entry index in this file itself.
+ public ShortGuid composite_instance_id;
+ public ShortGuid resource_id;
};
#endregion
}
diff --git a/CathodeLib/Scripts/CATHODE/Traversals.cs b/CathodeLib/Scripts/CATHODE/Traversals.cs
new file mode 100644
index 0000000..7000b7c
--- /dev/null
+++ b/CathodeLib/Scripts/CATHODE/Traversals.cs
@@ -0,0 +1,97 @@
+using CATHODE.Scripting;
+using CathodeLib;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+#if UNITY_EDITOR || UNITY_STANDALONE_WIN
+using UnityEngine;
+#else
+using System.Numerics;
+using System.Runtime.InteropServices;
+#endif
+
+namespace CATHODE.EXPERIMENTAL
+{
+ /* DATA/ENV/PRODUCTION/x/WORLD/COLLISION.BIN */
+ public class Traversals : CathodeFile
+ {
+ //NOTE: Not bothering finishing reversing this one as it's not actually used.
+ // The TRAVERSAL file is only populated in the AUTOGENERATION folder which isn't used by the game at runtime.
+ // Have included write support though to write the empty file.
+
+ /*public*/ private List Entries = new List();
+ public static new Implementation Implementation = Implementation.SAVE;
+ public Traversals(string path) : base(path) { }
+
+ private char[] _magic = new char[4] { 't', 'r', 'a', 'v' };
+ private int _version = 2;
+
+ #region FILE_IO
+ override protected bool LoadInternal()
+ {
+ using (BinaryReader reader = new BinaryReader(File.OpenRead(_filepath)))
+ {
+ char[] magic = reader.ReadChars(4);
+ if (!magic.SequenceEqual(_magic)) throw new Exception();
+ int version = reader.ReadInt32();
+ if (version != _version) throw new Exception();
+
+ int entryCount = reader.ReadInt16();
+ for (int i = 0; i < entryCount; i++)
+ {
+ Entries.Add(Utilities.Consume(reader));
+ }
+
+ //note: there is more data left behind here.
+ }
+ return true;
+ }
+
+ override protected bool SaveInternal()
+ {
+ using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(_filepath)))
+ {
+ writer.BaseStream.SetLength(0);
+ writer.Write(_magic);
+ writer.Write(_version);
+
+ /*
+ writer.Write((Int16)Entries.Count);
+ for (int i = 0; i < Entries.Count; i++)
+ {
+
+ }
+ */
+
+ writer.Write((Int16)0);
+ writer.Write((Int16)1);
+ writer.Write((Int16)1);
+ writer.Write((Int16)0);
+ writer.Write((Int16)0);
+ writer.Write((Int16)0);
+ writer.Write((Int16)0);
+ writer.Write((Int16)0);
+ writer.Write(16256);
+ }
+ return true;
+ }
+ #endregion
+
+ #region STRUCTURES
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public class Entry
+ {
+ public short EntryIndex;
+ public int UnknownID; // NOTE: It is the same value for all entries in 'sci_hub'. Only seen in this file.
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
+ public byte[] Unknowns_; //5
+ public ShortGuid NodeID; // NOTE: This is found in 'commands.pak' and it is a "Traversal" node for the Alien indicates animation.
+ // Seen in 'resources.bin', 'nav_mesh', 'traversal' and 'commands.pak'.
+ public ShortGuid ResourcesBINID; // NOTE: Seen in 'resources.bin', 'nav_mesh', 'traversal'. MATT: is this instance id?
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
+ public Vector3[] Ps; //20
+ };
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/CathodeLib/Scripts/Level.cs b/CathodeLib/Scripts/Level.cs
index 6af3141..bcb9e91 100644
--- a/CathodeLib/Scripts/Level.cs
+++ b/CathodeLib/Scripts/Level.cs
@@ -52,6 +52,7 @@ public class State
{
public int Index;
public NavigationMesh NavMesh;
+ public Traversals Traversals;
}
public List StateResources = new List();
@@ -81,7 +82,7 @@ public Level(string path, Global global)
RenderableElements = new RenderableElements(path + "/WORLD/REDS.BIN");
Movers = new Movers(path + "/WORLD/MODELS.MVR");
Commands = new Commands(path + "/WORLD/COMMANDS.PAK");
- //Resources = new Resources(path + "/WORLD/RESOURCES.BIN");
+ Resources = new Resources(path + "/WORLD/RESOURCES.BIN");
//PhysicsMaps = new PhysicsMaps(path + "/WORLD/PHYSICS.MAP");
EnvironmentMaps = new EnvironmentMaps(path + "/WORLD/ENVIRONMENTMAP.BIN");
//CollisionMaps = new CollisionMaps(path + "/WORLD/COLLISION.MAP");
@@ -104,24 +105,31 @@ public Level(string path, Global global)
// - SND NETWORK FILES
/* WORLD STATE RESOURCES */
- int stateCount = 1;
+ int stateCount = 1; // we always implicitly have one state (the default state: state zero)
using (BinaryReader reader = new BinaryReader(File.OpenRead(path + "/WORLD/EXCLUSIVE_MASTER_RESOURCE_INDICES")))
{
- reader.BaseStream.Position = 4;
- stateCount += reader.ReadInt32();
- //TODO: this file also contains indices for each state which seem to be of some relevence, although EXCLUSIVE_MASTER_RESOURCE doesn't point to indices?
+ reader.BaseStream.Position = 4; // version: 1
+ int states = reader.ReadInt32(); // number of changeable states
+ stateCount += states;
+ for (int x = 0; x < states; x++)
+ {
+ int resourceIndex = reader.ReadInt32();
+ Resources.Resource resource = Resources.Entries[resourceIndex]; //TODO: this gives you the instance of the ExclusiveMaster entity - use it
+ }
}
for (int i = 0; i < stateCount; i++)
{
+ string statePath = path + "/WORLD/STATE_" + i + "/";
+
State state = new State() { Index = i };
- state.NavMesh = new NavigationMesh(path + "/WORLD/STATE_" + i + "/NAV_MESH");
+ state.NavMesh = new NavigationMesh(statePath + "NAV_MESH");
+ state.Traversals = new Traversals(statePath + "TRAVERSAL");
// WORLD STATE RESOURCES TODO:
// - ASSAULT_POSITIONS
// - COVER
// - CRAWL_SPACE_SPOTTING_POSITIONS
// - SPOTTING_POSITIONS
- // - TRAVERSAL
}
/* TEXT */
@@ -164,14 +172,17 @@ public void Save()
/* UPDATE MOVER INDEXES */
//Get links to mover entries as actual objects
+ /*
List lightMovers = new List();
for (int i = 0; i < Lights.Entries.Count; i++)
lightMovers.Add(Movers.GetAtWriteIndex(Lights.Entries[i].MoverIndex));
+ */
List envMapMovers = new List();
for (int i = 0; i < EnvironmentMaps.Entries.Count; i++)
envMapMovers.Add(Movers.GetAtWriteIndex(EnvironmentMaps.Entries[i].MoverIndex));
Movers.Save();
+ /*
//Update mover indexes for light refs
List lights = new List();
for (int i = 0; i < Lights.Entries.Count; i++)
@@ -181,6 +192,7 @@ public void Save()
}
Lights.Entries = lights;
Lights.Save();
+ */
//Update mover indexes for envmap refs
List envMaps = new List();
@@ -306,9 +318,9 @@ public static List GetLevels(string gameDirectory, bool swapNostromoForP
int length = file.Length - extraLength;
if (length <= 0) continue;
- string mapName = file.Substring(0, length);
+ string mapName = file.Substring(0, length).ToUpper();
if (swapNostromoForPatch && (mapName == "DLC/BSPNOSTROMO_RIPLEY" || mapName == "DLC/BSPNOSTROMO_TWOTEAMS")) mapName += "_PATCH";
- mapList.Add(mapName);
+ mapList.Add(mapName.ToUpper());
}
return mapList;
}
diff --git a/CathodeLib/Scripts/Utilities.cs b/CathodeLib/Scripts/Utilities.cs
index 239811e..392000c 100644
--- a/CathodeLib/Scripts/Utilities.cs
+++ b/CathodeLib/Scripts/Utilities.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Numerics;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
@@ -315,6 +316,45 @@ public class PAKContent
}
}
+ public static class MathsUtils
+ {
+ public static (decimal, decimal, decimal) ToYawPitchRoll(this Quaternion q)
+ {
+ decimal yaw = Convert.ToDecimal(Math.Atan2(2 * (q.Y * q.W + q.X * q.Z), 1 - 2 * (q.Y * q.Y + q.X * q.X)) * (180 / Math.PI));
+ decimal pitch = Convert.ToDecimal(Math.Asin(2 * (q.X * q.W - q.Z * q.Y)) * (180 / Math.PI));
+ decimal roll = Convert.ToDecimal(Math.Atan2(2 * (q.Z * q.W + q.X * q.Y), 1 - 2 * (q.X * q.X + q.Z * q.Z)) * (180 / Math.PI));
+
+ return (yaw, pitch, roll);
+ }
+
+ public static Vector3 AddEulerAngles(this Vector3 euler1, Vector3 euler2)
+ {
+ Vector3 result = euler1 + euler2;
+ result = NormalizeEulerAngles(result);
+ return result;
+ }
+ private static Vector3 NormalizeEulerAngles(Vector3 angles)
+ {
+ angles.X = NormalizeAngle(angles.X);
+ angles.Y = NormalizeAngle(angles.Y);
+ angles.Z = NormalizeAngle(angles.Z);
+ return angles;
+ }
+ private static float NormalizeAngle(float angle)
+ {
+ angle = angle % 360;
+ if (angle > 180)
+ {
+ angle -= 360;
+ }
+ if (angle < -180)
+ {
+ angle += 360;
+ }
+ return angle;
+ }
+ }
+
public static class BigEndianUtils
{
public static Int64 ReadInt64(BinaryReader Reader, bool bigEndian = true)
@@ -425,16 +465,43 @@ public OffsetPair(long _go, int _ec)
/// The instance ID identifies the hierarchy the composite was created at. This can be generated with GenerateInstance.
///
[StructLayout(LayoutKind.Sequential, Pack = 1)]
- public class CommandsEntityReference
+ public class EntityHandle
{
public ShortGuid entity_id = ShortGuid.Invalid; //The ID of the entity within its written composite
public ShortGuid composite_instance_id = ShortGuid.Invalid; //The instance of the composite this entity is in when created via hierarchy
- public CommandsEntityReference() { }
- public CommandsEntityReference(EntityPath hierarchy)
+ public EntityHandle() { }
+ public EntityHandle(EntityPath hierarchy)
{
entity_id = hierarchy.GetPointedEntityID();
- composite_instance_id = hierarchy.GenerateInstance();
+ composite_instance_id = hierarchy.GenerateCompositeInstanceID();
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (!(obj is EntityHandle)) return false;
+ return (EntityHandle)obj == this;
+ }
+
+ public static bool operator ==(EntityHandle x, EntityHandle y)
+ {
+ if (ReferenceEquals(x, null)) return ReferenceEquals(y, null);
+ if (ReferenceEquals(y, null)) return ReferenceEquals(x, null);
+ if (x.entity_id != y.entity_id) return false;
+ if (x.composite_instance_id != y.composite_instance_id) return false;
+ return true;
+ }
+ public static bool operator !=(EntityHandle x, EntityHandle y)
+ {
+ return !(x == y);
+ }
+
+ public override int GetHashCode()
+ {
+ int hashCode = -539839184;
+ hashCode = hashCode * -1521134295 + entity_id.GetHashCode();
+ hashCode = hashCode * -1521134295 + composite_instance_id.GetHashCode();
+ return hashCode;
}
}