diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs index 64834f6..d5419ac 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs @@ -4714,7 +4714,7 @@ namespace CodeWalker.GameFiles public byte TypeId { get { return _Data.iType; } set { _Data.iType = value; } } - public ScenarioType Type { get; set; } + public ScenarioTypeRef? Type { get; set; } public byte ModelSetId { get { return _Data.ModelSetId; } set { _Data.ModelSetId = value; } } public AmbientModelSet ModelSet { get; set; } @@ -5298,7 +5298,7 @@ namespace CodeWalker.GameFiles public Vector3 Position { get { return _Data.Position; } set { _Data.Position = value; } } public MetaHash Unk1 { get { return _Data.Unk_2602393771; } set { _Data.Unk_2602393771 = value; } } public MetaHash TypeHash { get { return _Data.ScenarioType; } set { _Data.ScenarioType = value; } } - public ScenarioType Type { get; set; } + public ScenarioTypeRef? Type { get; set; } public bool NotFirst { get { return _Data.Unk_407126079_NotFirst == 1; } set { _Data.Unk_407126079_NotFirst = (byte)(value ? 1 : 0); } } public bool NotLast { get { return _Data.Unk_1308720135_NotLast == 1; } set { _Data.Unk_1308720135_NotLast = (byte)(value ? 1 : 0); } } diff --git a/CodeWalker.Core/World/Scenarios.cs b/CodeWalker.Core/World/Scenarios.cs index f25b475..5b13191 100644 --- a/CodeWalker.Core/World/Scenarios.cs +++ b/CodeWalker.Core/World/Scenarios.cs @@ -208,13 +208,23 @@ namespace CodeWalker.World if (tpind < typhashes.Length) { var hash = typhashes[tpind]; - scp.Type = types.GetScenarioType(hash); - if (scp.Type != null) + var st = types.GetScenarioType(hash); + if (st != null) { - isveh = scp.Type.IsVehicle; + scp.Type = new ScenarioTypeRef(st); } else - { } + { + var stg = types.GetScenarioTypeGroup(hash); + if (stg != null) + { + scp.Type = new ScenarioTypeRef(stg); + } + else + { } + } + + isveh = scp.Type.Value.IsVehicle; } else { } @@ -282,13 +292,23 @@ namespace CodeWalker.World if ((hash != 0) && (hash != 493038497)) { bool isveh = false; - spn.Type = types.GetScenarioType(hash); - if (spn.Type != null) + var st = types.GetScenarioType(hash); + if (st != null) { - isveh = spn.Type.IsVehicle; + spn.Type = new ScenarioTypeRef(st); } else - { } + { + var stg = types.GetScenarioTypeGroup(hash); + if (stg != null) + { + spn.Type = new ScenarioTypeRef(stg); + } + else + { } + } + + isveh = spn.Type?.IsVehicle ?? false; if (isveh) { } else @@ -1067,10 +1087,10 @@ namespace CodeWalker.World int interiorid = 0; int groupid = 0; int imapid = 0; - if ((mp.Type != null) && (!typeNames.TryGetValue(mp.Type.NameHash, out typeid))) + if ((mp.Type != null) && (!typeNames.TryGetValue(mp.Type.Value.NameHash, out typeid))) { typeid = typeNames.Count; - typeNames[mp.Type.NameHash] = typeid; + typeNames[mp.Type.Value.NameHash] = typeid; } if (mp.ModelSet != null) { @@ -1159,10 +1179,10 @@ namespace CodeWalker.World int interiorid = 0; int groupid = 0; int imapid = 0; - if ((mp.Type != null) && (!typeNames.TryGetValue(mp.Type.NameHash, out typeid))) + if ((mp.Type != null) && (!typeNames.TryGetValue(mp.Type.Value.NameHash, out typeid))) { typeid = typeNames.Count; - typeNames[mp.Type.NameHash] = typeid; + typeNames[mp.Type.Value.NameHash] = typeid; } if (mp.ModelSet != null) { @@ -1502,6 +1522,7 @@ namespace CodeWalker.World private object SyncRoot = new object(); //keep this thread-safe.. technically shouldn't be necessary, but best to be safe private Dictionary Types { get; set; } + private Dictionary TypeGroups { get; set; } private Dictionary PropSets { get; set; } private Dictionary PedModelSets { get; set; } private Dictionary VehicleModelSets { get; set; } @@ -1514,6 +1535,7 @@ namespace CodeWalker.World lock (SyncRoot) { Types = LoadTypes(gfc, "common:\\data\\ai\\scenarios.meta"); + TypeGroups = LoadTypeGroups(gfc, "common:\\data\\ai\\scenarios.meta"); PropSets = LoadModelSets(gfc, "common:\\data\\ai\\propsets.meta"); PedModelSets = LoadModelSets(gfc, "common:\\data\\ai\\ambientpedmodelsets.meta"); VehicleModelSets = LoadModelSets(gfc, "common:\\data\\ai\\vehiclemodelsets.meta"); @@ -1594,6 +1616,40 @@ namespace CodeWalker.World return types; } + private Dictionary LoadTypeGroups(GameFileCache gfc, string filename) + { + Dictionary types = new Dictionary(); + + var xml = LoadXml(gfc, filename); + + if ((xml == null) || (xml.DocumentElement == null)) + { + return types; + } + + var typesxml = xml.DocumentElement; + var items = typesxml.SelectNodes("ScenarioTypeGroups/Item"); + + foreach (XmlNode item in items) + { + ScenarioTypeGroup group = new ScenarioTypeGroup(); + + group.Load(item); + if (!string.IsNullOrEmpty(group.NameLower)) + { + JenkIndex.Ensure(group.NameLower); + uint hash = JenkHash.GenHash(group.NameLower); + types[hash] = group; + } + else + { } + } + + JenkIndex.Ensure("none"); + + return types; + } + private Dictionary LoadModelSets(GameFileCache gfc, string filename) { Dictionary sets = new Dictionary(); @@ -1666,6 +1722,16 @@ namespace CodeWalker.World return st; } } + public ScenarioTypeGroup GetScenarioTypeGroup(uint hash) + { + lock (SyncRoot) + { + if (TypeGroups == null) return null; + ScenarioTypeGroup tg; + TypeGroups.TryGetValue(hash, out tg); + return tg; + } + } public AmbientModelSet GetPropSet(uint hash) { lock (SyncRoot) @@ -1715,6 +1781,14 @@ namespace CodeWalker.World return Types.Values.ToArray(); } } + public ScenarioTypeGroup[] GetScenarioTypeGroups() + { + lock (SyncRoot) + { + if (TypeGroups == null) return null; + return TypeGroups.Values.ToArray(); + } + } public AmbientModelSet[] GetPropSets() { lock (SyncRoot) @@ -1750,6 +1824,69 @@ namespace CodeWalker.World } + /// + /// Represents a scenario type that may either be a or a . + /// Used with CScenarioChainingNode and CScenarioPoint. + /// + [TypeConverter(typeof(ExpandableObjectConverter))] public struct ScenarioTypeRef + { + public string Name => IsGroup ? Group.Name : Type.Name; + public string NameLower => IsGroup ? Group.NameLower : Type.NameLower; + public MetaHash NameHash => IsGroup ? Group.NameHash : Type.NameHash; + public bool IsVehicle => IsGroup ? false : Type.IsVehicle; // groups don't support vehicle infos, so always false + public string VehicleModelSet => IsGroup ? null : Type.VehicleModelSet; + public MetaHash VehicleModelSetHash => IsGroup ? 0 : Type.VehicleModelSetHash; + + public bool IsGroup { get; } + public ScenarioType Type { get; } + public ScenarioTypeGroup Group { get; } + + public ScenarioTypeRef(ScenarioTypeRef typeRef) + { + IsGroup = typeRef.IsGroup; + Type = typeRef.Type; + Group = typeRef.Group; + } + + public ScenarioTypeRef(ScenarioType type) + { + IsGroup = false; + Type = type; + Group = null; + } + + public ScenarioTypeRef(ScenarioTypeGroup group) + { + IsGroup = true; + Type = null; + Group = group; + } + + public override string ToString() + { + return Name; + } + + public override bool Equals(object obj) + { + return obj is ScenarioTypeRef other && other == this; + } + + public override int GetHashCode() + { + return NameHash.GetHashCode(); + } + + public static bool operator ==(ScenarioTypeRef a, ScenarioTypeRef b) + { + return a.NameHash == b.NameHash; + } + + public static bool operator !=(ScenarioTypeRef a, ScenarioTypeRef b) + { + return a.NameHash != b.NameHash; + } + } [TypeConverter(typeof(ExpandableObjectConverter))] public class ScenarioType { @@ -1795,6 +1932,29 @@ namespace CodeWalker.World } + [TypeConverter(typeof(ExpandableObjectConverter))] public class ScenarioTypeGroup + { + public string OuterXml { get; set; } + public string Name { get; set; } + public string NameLower { get; set; } + public MetaHash NameHash { get; set; } + + + public void Load(XmlNode node) + { + OuterXml = node.OuterXml; + Name = Xml.GetChildInnerText(node, "Name"); + NameLower = Name.ToLowerInvariant(); + NameHash = JenkHash.GenHash(NameLower); + } + + public override string ToString() + { + return Name; + } + } + + [TypeConverter(typeof(ExpandableObjectConverter))] public class AmbientModelSet { diff --git a/Project/Panels/EditScenarioNodePanel.cs b/Project/Panels/EditScenarioNodePanel.cs index 756bd04..2bb49ca 100644 --- a/Project/Panels/EditScenarioNodePanel.cs +++ b/Project/Panels/EditScenarioNodePanel.cs @@ -133,6 +133,9 @@ namespace CodeWalker.Project.Panels var stypes = types.GetScenarioTypes(); if (stypes == null) return; + var stgroups = types.GetScenarioTypeGroups(); + if (stgroups == null) return; + var pmsets = types.GetPedModelSets(); if (pmsets == null) return; @@ -147,9 +150,17 @@ namespace CodeWalker.Project.Panels ScenarioChainNodeTypeComboBox.Items.Add(""); foreach (var stype in stypes) { - ScenarioPointTypeComboBox.Items.Add(stype); - ScenarioClusterPointTypeComboBox.Items.Add(stype); - ScenarioChainNodeTypeComboBox.Items.Add(stype); + ScenarioTypeRef? typeRef = new ScenarioTypeRef(stype); + ScenarioPointTypeComboBox.Items.Add(typeRef); + ScenarioClusterPointTypeComboBox.Items.Add(typeRef); + ScenarioChainNodeTypeComboBox.Items.Add(typeRef); + } + foreach (var stgroup in stgroups) + { + ScenarioTypeRef? typeRef = new ScenarioTypeRef(stgroup); + ScenarioPointTypeComboBox.Items.Add(typeRef); + ScenarioClusterPointTypeComboBox.Items.Add(typeRef); + ScenarioChainNodeTypeComboBox.Items.Add(typeRef); } ScenarioPointModelSetComboBox.Items.Clear(); @@ -827,7 +838,7 @@ namespace CodeWalker.Project.Panels if (populatingui) return; if (CurrentScenarioNode == null) return; if (CurrentScenarioNode.MyPoint == null) return; - ScenarioType stype = ScenarioPointTypeComboBox.SelectedItem as ScenarioType; + ScenarioTypeRef? stype = ScenarioPointTypeComboBox.SelectedItem as ScenarioTypeRef?; lock (ProjectForm.ProjectSyncRoot) { if (CurrentScenarioNode.MyPoint.Type != stype) @@ -1711,7 +1722,7 @@ namespace CodeWalker.Project.Panels if (populatingui) return; if (CurrentScenarioNode == null) return; if (CurrentScenarioNode.ChainingNode == null) return; - ScenarioType stype = ScenarioChainNodeTypeComboBox.SelectedItem as ScenarioType; + ScenarioTypeRef? stype = ScenarioChainNodeTypeComboBox.SelectedItem as ScenarioTypeRef?; lock (ProjectForm.ProjectSyncRoot) { if (CurrentScenarioNode.ChainingNode.Type != stype) @@ -2133,7 +2144,7 @@ namespace CodeWalker.Project.Panels if (populatingui) return; if (CurrentScenarioNode == null) return; if (CurrentScenarioNode.ClusterMyPoint == null) return; - ScenarioType stype = ScenarioClusterPointTypeComboBox.SelectedItem as ScenarioType; + ScenarioTypeRef? stype = ScenarioClusterPointTypeComboBox.SelectedItem as ScenarioTypeRef?; lock (ProjectForm.ProjectSyncRoot) { if (CurrentScenarioNode.ClusterMyPoint.Type != stype) diff --git a/Project/ProjectForm.cs b/Project/ProjectForm.cs index 6a3ddde..31129f6 100644 --- a/Project/ProjectForm.cs +++ b/Project/ProjectForm.cs @@ -4266,7 +4266,7 @@ namespace CodeWalker.Project var action = Unk_3609807418.Move; var navMode = Unk_3971773454.Direct; var navSpeed = Unk_941086046.Unk_00_3279574318; - var stype = defaulttype; + var stype = new ScenarioTypeRef(defaulttype); var modelset = defaultmodelset; var flags = defaultflags; var ok = true; @@ -4298,7 +4298,23 @@ namespace CodeWalker.Project if (vals.Length > 6) { var sthash = JenkHash.GenHash(vals[6].Trim().ToLowerInvariant()); - stype = stypes?.GetScenarioType(sthash) ?? defaulttype; + var st = stypes?.GetScenarioType(sthash); + if (st != null) + { + stype = new ScenarioTypeRef(st); + } + else + { + var stg = stypes?.GetScenarioTypeGroup(sthash); + if (stg != null) + { + stype = new ScenarioTypeRef(stg); + } + else + { + stype = new ScenarioTypeRef(defaulttype); + } + } } if (vals.Length > 7) { @@ -4326,7 +4342,7 @@ namespace CodeWalker.Project thisnode.ChainingNode.ScenarioNode = thisnode; thisnode.ChainingNode.Chain = chain; thisnode.ChainingNode.Type = stype; - thisnode.ChainingNode.TypeHash = stype?.NameHash ?? 0; + thisnode.ChainingNode.TypeHash = stype.NameHash; thisnode.ChainingNode.NotLast = (i < (lines.Length - 1)); thisnode.ChainingNode.NotFirst = (lastnode != null);