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