mirror of
https://mirror.ghproxy.com/https://github.com/dexyfex/CodeWalker
synced 2024-09-12 12:17:24 +08:00
5614 lines
202 KiB
C#
5614 lines
202 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using TC = System.ComponentModel.TypeConverterAttribute;
|
|
using EXP = System.ComponentModel.ExpandableObjectConverter;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Xml;
|
|
|
|
namespace CodeWalker.GameFiles
|
|
{
|
|
[TC(typeof(EXP))] public class MrfFile : GameFile, PackedFile
|
|
{
|
|
public byte[] RawFileData { get; set; }
|
|
public uint Magic { get; set; } = 0x45566F4D; // 'MoVE'
|
|
public uint Version { get; set; } = 2;
|
|
public uint HeaderUnk1 { get; set; } = 0;
|
|
public uint HeaderUnk2 { get; set; } = 0;
|
|
public uint HeaderUnk3 { get; set; } = 0;
|
|
public uint DataLength { get; set; } // doesn't include the header (Magic to Unk1_Items) nor UnkBytes
|
|
public uint UnkBytesCount { get; set; }
|
|
public uint Unk1_Count { get; set; }
|
|
public uint MoveNetworkTriggerCount { get; set; }
|
|
public uint MoveNetworkFlagCount { get; set; }
|
|
|
|
public MrfHeaderUnk1[] Unk1_Items { get; set; }
|
|
public MrfMoveNetworkBit[] MoveNetworkTriggers { get; set; }
|
|
public MrfMoveNetworkBit[] MoveNetworkFlags { get; set; }
|
|
public byte[] UnkBytes { get; set; }
|
|
|
|
public MrfNode[] AllNodes { get; set; }
|
|
public MrfNodeStateBase RootState { get; set; }
|
|
|
|
// DOT graphs to visualize the move network
|
|
public string DebugTreeGraph { get; set; }
|
|
public string DebugStateGraph { get; set; }
|
|
|
|
public MrfFile() : base(null, GameFileType.Mrf)
|
|
{
|
|
}
|
|
|
|
public MrfFile(RpfFileEntry entry) : base(entry, GameFileType.Mrf)
|
|
{
|
|
RpfFileEntry = entry;
|
|
}
|
|
|
|
public void Load(byte[] data, RpfFileEntry entry)
|
|
{
|
|
RawFileData = data;
|
|
if (entry != null)
|
|
{
|
|
RpfFileEntry = entry;
|
|
Name = entry.Name;
|
|
}
|
|
|
|
using (MemoryStream ms = new MemoryStream(data))
|
|
{
|
|
DataReader r = new DataReader(ms, Endianess.LittleEndian);
|
|
|
|
Read(r);
|
|
};
|
|
}
|
|
|
|
public byte[] Save()
|
|
{
|
|
MemoryStream s = new MemoryStream();
|
|
DataWriter w = new DataWriter(s);
|
|
NoOpDataWriter nw = new NoOpDataWriter();
|
|
|
|
Write(nw, updateOffsets: true); // first pass to calculate relative offsets
|
|
DataLength = (uint)(nw.Length - 32 - (Unk1_Items?.Sum(i => i.Size) ?? 0) - UnkBytesCount);
|
|
Write(w, updateOffsets: false); // now write the MRF
|
|
|
|
var buf = new byte[s.Length];
|
|
s.Position = 0;
|
|
s.Read(buf, 0, buf.Length);
|
|
return buf;
|
|
}
|
|
|
|
private void Write(DataWriter w, bool updateOffsets)
|
|
{
|
|
if (Magic != 0x45566F4D || Version != 2 || HeaderUnk1 != 0 || HeaderUnk2 != 0)
|
|
throw new Exception("Failed to write MRF, header is invalid!");
|
|
|
|
w.Write(Magic);
|
|
w.Write(Version);
|
|
w.Write(HeaderUnk1);
|
|
w.Write(HeaderUnk2);
|
|
w.Write(HeaderUnk3);
|
|
w.Write(DataLength);
|
|
w.Write(UnkBytesCount);
|
|
|
|
// Unused in final game
|
|
w.Write(Unk1_Count);
|
|
if (Unk1_Count > 0)
|
|
{
|
|
foreach (var entry in Unk1_Items)
|
|
{
|
|
w.Write(entry.Size);
|
|
w.Write(entry.Bytes);
|
|
}
|
|
}
|
|
|
|
w.Write(MoveNetworkTriggerCount);
|
|
if (MoveNetworkTriggerCount > 0)
|
|
{
|
|
foreach (var entry in MoveNetworkTriggers)
|
|
{
|
|
w.Write(entry.Name);
|
|
w.Write(entry.BitPosition);
|
|
}
|
|
}
|
|
|
|
w.Write(MoveNetworkFlagCount);
|
|
if (MoveNetworkFlagCount > 0)
|
|
{
|
|
foreach (var entry in MoveNetworkFlags)
|
|
{
|
|
w.Write(entry.Name);
|
|
w.Write(entry.BitPosition);
|
|
}
|
|
}
|
|
|
|
if (AllNodes != null)
|
|
{
|
|
foreach (var node in AllNodes)
|
|
{
|
|
if (updateOffsets) node.FileOffset = (int)w.Position;
|
|
node.Write(w);
|
|
if (updateOffsets) node.FileDataSize = (int)(w.Position - node.FileOffset);
|
|
}
|
|
|
|
if (updateOffsets)
|
|
{
|
|
foreach (var node in AllNodes)
|
|
{
|
|
node.UpdateRelativeOffsets();
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < UnkBytesCount; i++)
|
|
w.Write(UnkBytes[i]);
|
|
}
|
|
|
|
private void Read(DataReader r)
|
|
{
|
|
Magic = r.ReadUInt32(); // Should be 'MoVE'
|
|
Version = r.ReadUInt32(); // GTA5 = 2, RDR3 = 11
|
|
HeaderUnk1 = r.ReadUInt32(); // Should be 0
|
|
HeaderUnk2 = r.ReadUInt32();
|
|
HeaderUnk3 = r.ReadUInt32(); // Should be 0
|
|
DataLength = r.ReadUInt32();
|
|
UnkBytesCount = r.ReadUInt32();
|
|
|
|
if (Magic != 0x45566F4D || Version != 2 || HeaderUnk1 != 0 || HeaderUnk2 != 0)
|
|
throw new Exception("Failed to read MRF, header is invalid!");
|
|
|
|
// Unused in final game
|
|
Unk1_Count = r.ReadUInt32();
|
|
if (Unk1_Count > 0)
|
|
{
|
|
Unk1_Items = new MrfHeaderUnk1[Unk1_Count];
|
|
|
|
for (int i = 0; i < Unk1_Count; i++)
|
|
Unk1_Items[i] = new MrfHeaderUnk1(r);
|
|
}
|
|
|
|
MoveNetworkTriggerCount = r.ReadUInt32();
|
|
if (MoveNetworkTriggerCount > 0)
|
|
{
|
|
MoveNetworkTriggers = new MrfMoveNetworkBit[MoveNetworkTriggerCount];
|
|
|
|
for (int i = 0; i < MoveNetworkTriggerCount; i++)
|
|
MoveNetworkTriggers[i] = new MrfMoveNetworkBit(r);
|
|
}
|
|
|
|
MoveNetworkFlagCount = r.ReadUInt32();
|
|
if (MoveNetworkFlagCount > 0)
|
|
{
|
|
MoveNetworkFlags = new MrfMoveNetworkBit[MoveNetworkFlagCount];
|
|
|
|
for (int i = 0; i < MoveNetworkFlagCount; i++)
|
|
MoveNetworkFlags[i] = new MrfMoveNetworkBit(r);
|
|
}
|
|
|
|
var nodes = new List<MrfNode>();
|
|
|
|
while (true)
|
|
{
|
|
var index = nodes.Count;
|
|
|
|
var node = ReadNode(r);
|
|
|
|
if (node == null) break;
|
|
|
|
node.FileIndex = index;
|
|
nodes.Add(node);
|
|
}
|
|
|
|
AllNodes = nodes.ToArray();
|
|
|
|
if (UnkBytesCount != 0)
|
|
{
|
|
UnkBytes = new byte[UnkBytesCount];
|
|
|
|
for (int i = 0; i < UnkBytesCount; i++)
|
|
UnkBytes[i] = r.ReadByte();
|
|
}
|
|
|
|
RootState = AllNodes.Length > 0 ? (MrfNodeStateBase)AllNodes[0] : null; // the first node is always a state or state machine node (not inlined state machine)
|
|
ResolveRelativeOffsets();
|
|
|
|
DebugTreeGraph = DumpTreeGraph();
|
|
DebugStateGraph = DumpStateGraph();
|
|
|
|
if (r.Length != (DataLength + 32 + (Unk1_Items?.Sum(i => i.Size) ?? 0) + UnkBytesCount))
|
|
{ } // no hits
|
|
|
|
if (r.Position != r.Length)
|
|
throw new Exception($"Failed to read MRF ({r.Position} / {r.Length})");
|
|
}
|
|
|
|
private MrfNode ReadNode(DataReader r)
|
|
{
|
|
var startPos = r.Position;
|
|
var nodeType = (MrfNodeType)r.ReadUInt16();
|
|
r.Position = startPos;
|
|
|
|
if (nodeType <= MrfNodeType.None || nodeType >= MrfNodeType.Max)
|
|
{
|
|
if (r.Position != r.Length)//should only be at EOF
|
|
{ }
|
|
return null;
|
|
}
|
|
|
|
var node = CreateNode(nodeType);
|
|
node.FileOffset = (int)startPos;
|
|
node.Read(r);
|
|
node.FileDataSize = (int)(r.Position - node.FileOffset);
|
|
|
|
return node;
|
|
}
|
|
|
|
public static MrfNode CreateNode(MrfNodeType infoType)
|
|
{
|
|
switch (infoType)
|
|
{
|
|
case MrfNodeType.StateMachine:
|
|
return new MrfNodeStateMachine();
|
|
case MrfNodeType.Tail:
|
|
return new MrfNodeTail();
|
|
case MrfNodeType.InlinedStateMachine:
|
|
return new MrfNodeInlinedStateMachine();
|
|
case MrfNodeType.Animation:
|
|
return new MrfNodeAnimation();
|
|
case MrfNodeType.Blend:
|
|
return new MrfNodeBlend();
|
|
case MrfNodeType.AddSubtract:
|
|
return new MrfNodeAddSubtract();
|
|
case MrfNodeType.Filter:
|
|
return new MrfNodeFilter();
|
|
case MrfNodeType.Mirror:
|
|
return new MrfNodeMirror();
|
|
case MrfNodeType.Frame:
|
|
return new MrfNodeFrame();
|
|
case MrfNodeType.Ik:
|
|
return new MrfNodeIk();
|
|
case MrfNodeType.BlendN:
|
|
return new MrfNodeBlendN();
|
|
case MrfNodeType.Clip:
|
|
return new MrfNodeClip();
|
|
case MrfNodeType.Pm:
|
|
return new MrfNodePm();
|
|
case MrfNodeType.Extrapolate:
|
|
return new MrfNodeExtrapolate();
|
|
case MrfNodeType.Expression:
|
|
return new MrfNodeExpression();
|
|
case MrfNodeType.Capture:
|
|
return new MrfNodeCapture();
|
|
case MrfNodeType.Proxy:
|
|
return new MrfNodeProxy();
|
|
case MrfNodeType.AddN:
|
|
return new MrfNodeAddN();
|
|
case MrfNodeType.Identity:
|
|
return new MrfNodeIdentity();
|
|
case MrfNodeType.Merge:
|
|
return new MrfNodeMerge();
|
|
case MrfNodeType.Pose:
|
|
return new MrfNodePose();
|
|
case MrfNodeType.MergeN:
|
|
return new MrfNodeMergeN();
|
|
case MrfNodeType.State:
|
|
return new MrfNodeState();
|
|
case MrfNodeType.Invalid:
|
|
return new MrfNodeInvalid();
|
|
case MrfNodeType.JointLimit:
|
|
return new MrfNodeJointLimit();
|
|
case MrfNodeType.SubNetwork:
|
|
return new MrfNodeSubNetwork();
|
|
case MrfNodeType.Reference:
|
|
return new MrfNodeReference();
|
|
}
|
|
|
|
throw new Exception($"A handler for ({infoType}) mrf node type is not valid");
|
|
}
|
|
|
|
private void ResolveRelativeOffsets()
|
|
{
|
|
foreach (var n in AllNodes)
|
|
{
|
|
n.ResolveRelativeOffsets(this);
|
|
}
|
|
}
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.WriteItemArray(sb, MoveNetworkTriggers?.Where(t => !t.IsEndMarker).ToArray(), indent, "MoveNetworkTriggers");
|
|
MrfXml.WriteItemArray(sb, MoveNetworkFlags?.Where(t => !t.IsEndMarker).ToArray(), indent, "MoveNetworkFlags");
|
|
MrfXml.WriteNode(sb, indent, "RootState", RootState);
|
|
MrfXml.WriteItemArray(sb, Unk1_Items, indent, "Unk1");
|
|
MrfXml.WriteRawArray(sb, UnkBytes, indent, "UnkBytes", "", MrfXml.FormatHexByte, 16);
|
|
}
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
var triggers = XmlMeta.ReadItemArray<MrfMoveNetworkBit>(node, "MoveNetworkTriggers");
|
|
var flags = XmlMeta.ReadItemArray<MrfMoveNetworkBit>(node, "MoveNetworkFlags");
|
|
MoveNetworkTriggers = SortMoveNetworkBitsArray(triggers);
|
|
MoveNetworkFlags = SortMoveNetworkBitsArray(flags);
|
|
RootState = (MrfNodeStateBase)XmlMrf.ReadChildNode(node, "RootState");
|
|
Unk1_Items = XmlMeta.ReadItemArrayNullable<MrfHeaderUnk1>(node, "Unk1");
|
|
UnkBytes = Xml.GetChildRawByteArrayNullable(node, "UnkBytes");
|
|
MoveNetworkTriggerCount = (uint)(MoveNetworkTriggers?.Length ?? 0);
|
|
MoveNetworkFlagCount = (uint)(MoveNetworkFlags?.Length ?? 0);
|
|
Unk1_Count = (uint)(Unk1_Items?.Length ?? 0);
|
|
UnkBytesCount = (uint)(UnkBytes?.Length ?? 0);
|
|
|
|
AllNodes = BuildNodesArray(RootState);
|
|
|
|
// At this point the TargetStates of most transitions have been resolved by MrfNodeStateBase.ResolveXmlTargetStatesInTransitions
|
|
// but there is one transition in onfoothuman.mrf with a target state not in the parent StateMachine (not really sure why,
|
|
// it points to a state in a sibling tree), so ResolveXmlTargetStatesInTransitions can't find it.
|
|
// The source state is node hash_76D78558 and the transition target state is hash_1836C818.
|
|
// Iterate all transitions and try to resolve them again if target state is still null to solve this edge case.
|
|
var stateNodes = AllNodes.OfType<MrfNodeStateBase>();
|
|
foreach (var state in stateNodes)
|
|
{
|
|
if (state.Transitions == null) continue;
|
|
|
|
foreach (var t in state.Transitions)
|
|
{
|
|
if (t.TargetState != null) continue;
|
|
|
|
t.TargetState = stateNodes.FirstOrDefault(n => n.Name == t.XmlTargetStateName);
|
|
}
|
|
}
|
|
|
|
DebugTreeGraph = DumpTreeGraph();
|
|
DebugStateGraph = DumpStateGraph();
|
|
}
|
|
|
|
private static MrfNode[] BuildNodesArray(MrfNodeStateBase root)
|
|
{
|
|
var nodes = new List<MrfNode>();
|
|
AddRecursive(root);
|
|
return nodes.ToArray();
|
|
|
|
void AddRecursive(MrfNode node)
|
|
{
|
|
nodes.Add(node);
|
|
|
|
IEnumerable<MrfNode> children = null;
|
|
if (node is MrfNodeStateMachine sm)
|
|
{
|
|
children = sm.States.Select(s => s.State);
|
|
}
|
|
else if (node is MrfNodeInlinedStateMachine ism)
|
|
{
|
|
children = ism.States.Select(s => s.State);
|
|
}
|
|
else if (node is MrfNodeState ns)
|
|
{
|
|
children = ns.GetChildren(excludeTailNodes: false);
|
|
}
|
|
|
|
if (children != null)
|
|
{
|
|
foreach (var c in children.OrderBy(s => s is MrfNodeTail ? ushort.MaxValue : s.NodeIndex)) // NodeTail is placed after other nodes, their NodeIndex is ignored
|
|
{
|
|
AddRecursive(c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public MrfNode FindNodeAtFileOffset(int fileOffset)
|
|
{
|
|
foreach (var n in AllNodes)
|
|
{
|
|
if (n.FileOffset == fileOffset) return n;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public MrfMoveNetworkBit? FindMoveNetworkTriggerForBit(int bitPosition)
|
|
{
|
|
return FindMoveNetworkBitByBitPosition(MoveNetworkTriggers, bitPosition);
|
|
}
|
|
public MrfMoveNetworkBit? FindMoveNetworkFlagForBit(int bitPosition)
|
|
{
|
|
return FindMoveNetworkBitByBitPosition(MoveNetworkFlags, bitPosition);
|
|
}
|
|
public static MrfMoveNetworkBit? FindMoveNetworkBitByBitPosition(MrfMoveNetworkBit[] bits, int bitPosition)
|
|
{
|
|
if (bits == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
foreach (var flag in bits)
|
|
{
|
|
if (!flag.IsEndMarker && flag.BitPosition == bitPosition) return flag;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// MoveNetworkTriggers and MoveNetworkFlags getters by name for reference of how the arrays should be sorted in buckets
|
|
public MrfMoveNetworkBit? FindMoveNetworkTriggerByName(MetaHash name)
|
|
{
|
|
return FindMoveNetworkBitByName(MoveNetworkTriggers, name);
|
|
}
|
|
public MrfMoveNetworkBit? FindMoveNetworkFlagByName(MetaHash name)
|
|
{
|
|
return FindMoveNetworkBitByName(MoveNetworkFlags, name);
|
|
}
|
|
public static MrfMoveNetworkBit? FindMoveNetworkBitByName(MrfMoveNetworkBit[] bits, MetaHash name)
|
|
{
|
|
if (bits == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
for (int i = (int)(name.Hash % bits.Length); ; i = (i + 1) % bits.Length)
|
|
{
|
|
var b = bits[i];
|
|
if (b.IsEndMarker) break;
|
|
if (b.Name == name) return b;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static MrfMoveNetworkBit[] SortMoveNetworkBitsArray(MrfMoveNetworkBit[] bits)
|
|
{
|
|
if (bits == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var bitsSorted = new MrfMoveNetworkBit[bits.Length + 1]; // +1 for the end marker
|
|
|
|
bits = bits.OrderBy(b => b.BitPosition).ToArray();
|
|
for (int i = 0; i < bits.Length; i++)
|
|
{
|
|
var sortedIdx = bits[i].Name % bitsSorted.Length;
|
|
while (bitsSorted[sortedIdx].Name != 0)
|
|
{
|
|
sortedIdx = (sortedIdx + 1) % bitsSorted.Length;
|
|
}
|
|
bitsSorted[sortedIdx] = bits[i];
|
|
}
|
|
|
|
// place the end marker in the only empty slot left
|
|
for (int i = 0; i < bitsSorted.Length; i++)
|
|
{
|
|
if (bitsSorted[i].Name == 0)
|
|
{
|
|
bitsSorted[i] = MrfMoveNetworkBit.EndMarker;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bitsSorted;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dump a DOT graph with the whole node hierarchy as a tree.
|
|
/// </summary>
|
|
public string DumpTreeGraph()
|
|
{
|
|
using (var w = new StringWriter())
|
|
{
|
|
w.WriteLine($@"digraph ""{Name}"" {{");
|
|
w.WriteLine($@" label=""{Name}""");
|
|
w.WriteLine($@" labelloc=""t""");
|
|
w.WriteLine($@" concentrate=true");
|
|
w.WriteLine($@" rankdir=""LR""");
|
|
w.WriteLine($@" graph[fontname = ""Consolas""];");
|
|
w.WriteLine($@" edge[fontname = ""Consolas""];");
|
|
w.WriteLine($@" node[fontname = ""Consolas""];");
|
|
w.WriteLine();
|
|
|
|
w.WriteLine(" root [label=\"root\"];");
|
|
|
|
// nodes
|
|
foreach (var n in AllNodes)
|
|
{
|
|
var id = n.FileOffset;
|
|
var label = $"{n.NodeType} '{n.Name}'";
|
|
w.WriteLine(" n{0} [label=\"{1}\"];", id, label);
|
|
}
|
|
w.WriteLine();
|
|
|
|
// edges
|
|
w.WriteLine(" n{0} -> root [color = black]", RootState.FileOffset);
|
|
foreach (var n in AllNodes)
|
|
{
|
|
MrfStateTransition[] transitions = null;
|
|
MrfNode initial = null;
|
|
if (n is MrfNodeStateBase sb)
|
|
{
|
|
initial = sb.InitialNode;
|
|
transitions = sb.Transitions;
|
|
}
|
|
|
|
|
|
if (n is MrfNodeInlinedStateMachine im)
|
|
{
|
|
w.WriteLine(" n{1} -> n{0} [color = black, xlabel=\"fallback\"]", n.FileOffset, im.FallbackNode.FileOffset);
|
|
}
|
|
|
|
if (n is MrfNodeWithChildBase f)
|
|
{
|
|
w.WriteLine(" n{1} -> n{0} [color = black, xlabel=\"child\"]", n.FileOffset, f.Child.FileOffset);
|
|
}
|
|
|
|
if (n is MrfNodePairBase p)
|
|
{
|
|
w.WriteLine(" n{1} -> n{0} [color = black, xlabel=\"#0\"]", n.FileOffset, p.Child0.FileOffset);
|
|
w.WriteLine(" n{1} -> n{0} [color = black, xlabel=\"#1\"]", n.FileOffset, p.Child1.FileOffset);
|
|
}
|
|
|
|
if (n is MrfNodeNBase nn && nn.Children != null)
|
|
{
|
|
for (int i = 0; i < nn.Children.Length; i++)
|
|
{
|
|
w.WriteLine(" n{1} -> n{0} [color = black, xlabel=\"#{2}\"]", n.FileOffset, nn.Children[i].FileOffset, i);
|
|
}
|
|
}
|
|
|
|
if (transitions != null)
|
|
{
|
|
foreach (var transition in transitions)
|
|
{
|
|
var conditions = transition.Conditions == null ? "[]" : "[" + string.Join(" & ", transition.Conditions.Select(c => c.ToExpressionString(this))) + "]";
|
|
var target = transition.TargetState;
|
|
w.WriteLine(" n{1} -> n{0} [color = black, xlabel=\"T {2}\"]", n.FileOffset, target.FileOffset, conditions);
|
|
}
|
|
}
|
|
|
|
if (initial != null)
|
|
{
|
|
w.WriteLine(" n{1} -> n{0} [color = black, xlabel=\"init\"]", n.FileOffset, initial.FileOffset);
|
|
}
|
|
}
|
|
|
|
// footer
|
|
w.WriteLine("}");
|
|
|
|
return w.ToString();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dump a DOT graph of the state machines where nodes are placed inside their corresponding state.
|
|
/// </summary>
|
|
public string DumpStateGraph()
|
|
{
|
|
using (var w = new StringWriter())
|
|
{
|
|
w.WriteLine($@"digraph ""{Name}"" {{");
|
|
w.WriteLine($@" label=""{Name}""");
|
|
w.WriteLine($@" labelloc=""t""");
|
|
w.WriteLine($@" concentrate=true");
|
|
w.WriteLine($@" compound=true");
|
|
w.WriteLine($@" rankdir=""LR""");
|
|
w.WriteLine($@" graph[fontname = ""Consolas""];");
|
|
w.WriteLine($@" edge[fontname = ""Consolas""];");
|
|
w.WriteLine($@" node[fontname = ""Consolas""];");
|
|
w.WriteLine();
|
|
|
|
DumpNode(RootState, w, null);
|
|
|
|
w.WriteLine(" root [label=\"root\",shape=\"diamond\"];");
|
|
w.WriteLine(" root -> S{0} [color = black][lhead=\"clusterS{0}\"]", RootState.FileOffset);
|
|
|
|
// footer
|
|
w.WriteLine("}");
|
|
|
|
return w.ToString();
|
|
}
|
|
}
|
|
|
|
private void DumpStateMachineSubGraph(MrfNodeStateMachine sm, TextWriter w)
|
|
{
|
|
// header
|
|
w.WriteLine($@"subgraph ""clusterS{sm.FileOffset}"" {{");
|
|
w.WriteLine($@" label=""State Machine '{sm.Name}'""");
|
|
w.WriteLine($@" labelloc=""t""");
|
|
w.WriteLine($@" concentrate=true");
|
|
w.WriteLine($@" rankdir=""RL""");
|
|
w.WriteLine($@" S{sm.FileOffset}[shape=""none""][style=""invis""][label=""""]"); // hidden node to be able to connect subgraphs
|
|
w.WriteLine();
|
|
|
|
if (sm.States != null)
|
|
{
|
|
foreach (var state in sm.States)
|
|
{
|
|
var stateNode = state.State;
|
|
if (stateNode is MrfNodeState ns) DumpStateSubGraph(ns, w);
|
|
if (stateNode is MrfNodeStateMachine nsm) DumpStateMachineSubGraph(nsm, w);
|
|
if (stateNode is MrfNodeInlinedStateMachine ism) DumpInlinedStateMachineSubGraph(ism, w);
|
|
}
|
|
|
|
foreach (var state in sm.States)
|
|
{
|
|
DumpStateTransitionsGraph(state.State, w);
|
|
}
|
|
}
|
|
|
|
w.WriteLine(" startS{0} [label=\"start\",shape=\"diamond\"];", sm.FileOffset);
|
|
w.WriteLine(" startS{0} -> S{1} [color = black][lhead=\"clusterS{1}\"]", sm.FileOffset, sm.InitialNode.FileOffset);
|
|
|
|
// footer
|
|
w.WriteLine("}");
|
|
}
|
|
|
|
private void DumpInlinedStateMachineSubGraph(MrfNodeInlinedStateMachine sm, TextWriter w)
|
|
{
|
|
// header
|
|
w.WriteLine($@"subgraph ""clusterS{sm.FileOffset}"" {{");
|
|
w.WriteLine($@" label=""Inlined State Machine '{sm.Name}'""");
|
|
w.WriteLine($@" labelloc=""t""");
|
|
w.WriteLine($@" concentrate=true");
|
|
w.WriteLine($@" rankdir=""RL""");
|
|
w.WriteLine($@" S{sm.FileOffset}[shape=""none""][style=""invis""][label=""""]"); // hidden node to be able to connect subgraphs
|
|
w.WriteLine();
|
|
|
|
if (sm.States != null)
|
|
{
|
|
foreach (var state in sm.States)
|
|
{
|
|
var stateNode = state.State;
|
|
if (stateNode is MrfNodeState ns) DumpStateSubGraph(ns, w);
|
|
if (stateNode is MrfNodeStateMachine nsm) DumpStateMachineSubGraph(nsm, w);
|
|
if (stateNode is MrfNodeInlinedStateMachine ism) DumpInlinedStateMachineSubGraph(ism, w);
|
|
}
|
|
|
|
foreach (var state in sm.States)
|
|
{
|
|
DumpStateTransitionsGraph(state.State, w);
|
|
}
|
|
}
|
|
|
|
w.WriteLine(" startS{0} [label=\"start\",shape=\"diamond\"];", sm.FileOffset);
|
|
w.WriteLine(" startS{0} -> S{1} [color = black][lhead=\"clusterS{1}\"]", sm.FileOffset, sm.InitialNode.FileOffset);
|
|
|
|
if (sm.FallbackNode != null)
|
|
{
|
|
var fn = sm.FallbackNode;
|
|
DumpNode(fn, w, null);
|
|
|
|
w.WriteLine(" fallbackS{0} [label=\"fallback\",shape=\"diamond\"];", sm.FileOffset);
|
|
if (fn is MrfNodeStateBase) w.WriteLine(" fallbackS{0} -> S{1} [color = black][lhead=\"clusterS{1}\"]", sm.FileOffset, fn.FileOffset);
|
|
else w.WriteLine(" fallbackS{0} -> n{1} [color = black]", sm.FileOffset, fn.FileOffset);
|
|
}
|
|
|
|
// footer
|
|
w.WriteLine("}");
|
|
}
|
|
|
|
private void DumpStateTransitionsGraph(MrfNodeStateBase from, TextWriter w)
|
|
{
|
|
var transitions = from.Transitions;
|
|
if (transitions != null)
|
|
{
|
|
int i = 0;
|
|
foreach (var transition in transitions)
|
|
{
|
|
var conditions = transition.Conditions == null ? "[]" : "[" + string.Join(" & ", transition.Conditions.Select(c => c.ToExpressionString(this))) + "]";
|
|
var target = transition.TargetState;
|
|
w.WriteLine(" S{0} -> S{1} [color = black, xlabel=\"T#{2} {3}\"][ltail=\"clusterS{0}\"][lhead=\"clusterS{1}\"]", from.FileOffset, target.FileOffset, i, conditions);
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DumpStateSubGraph(MrfNodeStateBase state, TextWriter w)
|
|
{
|
|
if (state.InitialNode == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// header
|
|
w.WriteLine($@"subgraph clusterS{state.FileOffset} {{");
|
|
w.WriteLine($@" label=""State '{state.Name}'""");
|
|
w.WriteLine($@" labelloc=""t""");
|
|
w.WriteLine($@" concentrate=true");
|
|
w.WriteLine($@" rankdir=""LR""");
|
|
w.WriteLine($@" S{state.FileOffset}[shape=""none""][style=""invis""][label=""""]"); // hidden node to be able to connect subgraphs
|
|
w.WriteLine();
|
|
|
|
var initial = state.InitialNode;
|
|
DumpNode(initial, w, null);
|
|
|
|
w.WriteLine(" outputS{0} [label=\"output\",shape=\"point\"];", state.FileOffset);
|
|
if (initial is MrfNodeStateBase) w.WriteLine(" S{0} -> outputS{1} [color = black][ltail=\"clusterS{0}\"]", initial.FileOffset, state.FileOffset);
|
|
else w.WriteLine(" n{0} -> outputS{1} [color = black]", initial.FileOffset, state.FileOffset);
|
|
|
|
// footer
|
|
w.WriteLine("}");
|
|
}
|
|
|
|
private void DumpNodeGraph(MrfNode n, TextWriter w, HashSet<MrfNode> visitedNodes)
|
|
{
|
|
if (!visitedNodes.Add(n))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var label = $"{n.NodeType} '{n.Name}'";
|
|
if (n is MrfNodeSubNetwork sub)
|
|
{
|
|
label += $"\\n'{sub.SubNetworkParameterName}'";
|
|
}
|
|
|
|
w.WriteLine(" n{0} [label=\"{1}\"];", n.FileOffset, label);
|
|
|
|
void writeConnection(MrfNode target, string connectionLabel)
|
|
{
|
|
if (target is MrfNodeStateBase) w.WriteLine(" S{1} -> n{0} [color = black, xlabel=\"{2}\"][ltail=\"clusterS{1}\"]", n.FileOffset, target.FileOffset, connectionLabel);
|
|
else w.WriteLine(" n{1} -> n{0} [color = black, xlabel=\"{2}\"]", n.FileOffset, target.FileOffset, connectionLabel);
|
|
}
|
|
|
|
if (n is MrfNodeInlinedStateMachine im)
|
|
{
|
|
DumpNode(im.FallbackNode, w, visitedNodes);
|
|
writeConnection(im.FallbackNode, "fallback");
|
|
}
|
|
|
|
if (n is MrfNodeWithChildBase f)
|
|
{
|
|
DumpNode(f.Child, w, visitedNodes);
|
|
writeConnection(f.Child, "");
|
|
}
|
|
|
|
if (n is MrfNodePairBase p)
|
|
{
|
|
DumpNode(p.Child0, w, visitedNodes);
|
|
DumpNode(p.Child1, w, visitedNodes);
|
|
writeConnection(p.Child0, "#0");
|
|
writeConnection(p.Child1, "#1");
|
|
}
|
|
|
|
if (n is MrfNodeNBase nn && nn.Children != null)
|
|
{
|
|
for (int i = 0; i < nn.Children.Length; i++)
|
|
{
|
|
DumpNode(nn.Children[i], w, visitedNodes);
|
|
writeConnection(nn.Children[i], $"#{i}");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DumpNode(MrfNode n, TextWriter w, HashSet<MrfNode> visitedNodes)
|
|
{
|
|
if (n is MrfNodeState ns) DumpStateSubGraph(ns, w);
|
|
else if (n is MrfNodeStateMachine nsm) DumpStateMachineSubGraph(nsm, w);
|
|
else if (n is MrfNodeInlinedStateMachine ism) DumpInlinedStateMachineSubGraph(ism, w);
|
|
else DumpNodeGraph(n, w, visitedNodes ?? new HashSet<MrfNode>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writer used to calculate where the nodes will be placed, so the relative offsets can be calculated before using the real writer.
|
|
/// </summary>
|
|
private class NoOpDataWriter : DataWriter
|
|
{
|
|
private long length;
|
|
private long position;
|
|
|
|
public override long Length => length;
|
|
public override long Position { get => position; set => position = value; }
|
|
|
|
public NoOpDataWriter() : base(null, Endianess.LittleEndian)
|
|
{
|
|
}
|
|
|
|
protected override void WriteToStream(byte[] value, bool ignoreEndianess = false)
|
|
{
|
|
position += value.Length;
|
|
length = Math.Max(length, position);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Unused node indexes by GTAV: 11, 12, 14, 16
|
|
// Exist in GTAV but not used in MRFs: 4, 8, 10, 17, 21, 22, 28, 29, 31, 32
|
|
public enum MrfNodeType : ushort
|
|
{
|
|
None = 0,
|
|
StateMachine = 1,
|
|
Tail = 2,
|
|
InlinedStateMachine = 3,
|
|
Animation = 4,
|
|
Blend = 5,
|
|
AddSubtract = 6,
|
|
Filter = 7,
|
|
Mirror = 8,
|
|
Frame = 9,
|
|
Ik = 10,
|
|
BlendN = 13,
|
|
Clip = 15,
|
|
Pm = 17,
|
|
Extrapolate = 18,
|
|
Expression = 19,
|
|
Capture = 20,
|
|
Proxy = 21,
|
|
AddN = 22,
|
|
Identity = 23,
|
|
Merge = 24,
|
|
Pose = 25,
|
|
MergeN = 26,
|
|
State = 27,
|
|
Invalid = 28,
|
|
JointLimit = 29,
|
|
SubNetwork = 30,
|
|
Reference = 31,
|
|
Max = 32
|
|
}
|
|
|
|
public enum MrfNodeParameterId : ushort // node parameter IDs are specific to the node type
|
|
{
|
|
// StateMachine
|
|
// none
|
|
|
|
// Tail
|
|
// none
|
|
|
|
// InlinedStateMachine
|
|
// none
|
|
|
|
// Animation
|
|
Animation_Animation = 0, // rage::crAnimation (only setter)
|
|
Animation_Unk1 = 1, // float
|
|
Animation_Unk2 = 2, // float
|
|
Animation_Unk3 = 3, // float
|
|
Animation_Unk4 = 4, // bool
|
|
Animation_Unk5 = 5, // bool
|
|
|
|
// Blend
|
|
Blend_Filter = 0, // rage::crFrameFilter
|
|
Blend_Weight = 1, // float
|
|
|
|
// AddSubtract
|
|
AddSubtract_Filter = 0, // rage::crFrameFilter
|
|
AddSubtract_Weight = 1, // float
|
|
|
|
// Filter
|
|
Filter_Filter = 0, // rage::crFrameFilter
|
|
|
|
// Mirror
|
|
Mirror_Filter = 0, // rage::crFrameFilter
|
|
|
|
// Frame
|
|
Frame_Frame = 0, // rage::crFrame
|
|
|
|
// Ik
|
|
// none
|
|
|
|
// BlendN
|
|
BlendN_Filter = 1, // rage::crFrameFilter
|
|
BlendN_ChildWeight = 2, // float (extra arg is the child index)
|
|
BlendN_ChildFilter = 3, // rage::crFrameFilter (extra arg is the child index)
|
|
|
|
// Clip
|
|
Clip_Clip = 0, // rage::crClip
|
|
Clip_Phase = 1, // float
|
|
Clip_Rate = 2, // float
|
|
Clip_Delta = 3, // float
|
|
Clip_Looped = 4, // bool
|
|
|
|
// Pm
|
|
Pm_Motion = 0, // rage::crpmMotion
|
|
Pm_Unk1 = 1, // float
|
|
Pm_Unk2 = 2, // float
|
|
Pm_Unk3 = 3, // float
|
|
Pm_Unk4 = 4, // float
|
|
|
|
// Extrapolate
|
|
Extrapolate_Damping = 0, // float
|
|
|
|
// Expression
|
|
Expression_Expression = 0, // rage::crExpressions
|
|
Expression_Weight = 1, // float
|
|
Expression_Variable = 2, // float (extra arg is the variable name)
|
|
|
|
// Capture
|
|
Capture_Frame = 0, // rage::crFrame
|
|
|
|
// Proxy
|
|
Proxy_Node = 0, // rage::crmtNode (the type resolver returns rage::crmtObserver but the getter/setter expect a rage::crmtNode, R* bug?)
|
|
|
|
// AddN
|
|
AddN_Filter = 1, // rage::crFrameFilter
|
|
AddN_ChildWeight = 2, // float (extra arg is the child index)
|
|
AddN_ChildFilter = 3, // rage::crFrameFilter (extra arg is the child index)
|
|
|
|
// Identity
|
|
// none
|
|
|
|
// Merge
|
|
Merge_Filter = 0, // rage::crFrameFilter
|
|
|
|
// Pose
|
|
Pose_IsNormalized = 0, // bool (getter hardcoded to true, setter does nothing)
|
|
|
|
// MergeN
|
|
MergeN_Filter = 1, // rage::crFrameFilter
|
|
MergeN_ChildWeight = 2, // float (extra arg is the child index)
|
|
MergeN_ChildFilter = 3, // rage::crFrameFilter (extra arg is the child index)
|
|
|
|
// State
|
|
// none
|
|
|
|
// Invalid
|
|
// none
|
|
|
|
// JointLimit
|
|
JointLimit_Filter = 0, // rage::crFrameFilter (only setter exists)
|
|
|
|
// SubNetwork
|
|
// none
|
|
|
|
// Reference
|
|
// none
|
|
}
|
|
|
|
public enum MrfNodeEventId : ushort // node event IDs are specific to the node type
|
|
{
|
|
// StateMachine
|
|
// none
|
|
|
|
// Tail
|
|
// none
|
|
|
|
// InlinedStateMachine
|
|
// none
|
|
|
|
// Animation
|
|
Animation_Unk0 = 0,
|
|
Animation_Unk1 = 1,
|
|
|
|
// Blend
|
|
// none
|
|
|
|
// AddSubtract
|
|
// none
|
|
|
|
// Filter
|
|
// none
|
|
|
|
// Mirror
|
|
// none
|
|
|
|
// Frame
|
|
// none
|
|
|
|
// Ik
|
|
// none
|
|
|
|
// BlendN
|
|
// none
|
|
|
|
// Clip
|
|
Clip_IterationFinished = 0, // triggered when a looped clip iteration finishes playing
|
|
Clip_Finished = 1, // triggered when a non-looped clip finishes playing
|
|
Clip_Unk2 = 2,
|
|
Clip_Unk3 = 3,
|
|
Clip_Unk4 = 4,
|
|
|
|
// Pm
|
|
Pm_Unk0 = 0,
|
|
Pm_Unk1 = 1,
|
|
|
|
// Extrapolate
|
|
// none
|
|
|
|
// Expression
|
|
// none
|
|
|
|
// Capture
|
|
// none
|
|
|
|
// Proxy
|
|
// none
|
|
|
|
// AddN
|
|
// none
|
|
|
|
// Identity
|
|
// none
|
|
|
|
// Merge
|
|
|
|
// Pose
|
|
// none
|
|
|
|
// MergeN
|
|
// none
|
|
|
|
// State
|
|
// none
|
|
|
|
// Invalid
|
|
// none
|
|
|
|
// JointLimit
|
|
// none
|
|
|
|
// SubNetwork
|
|
// none
|
|
|
|
// Reference
|
|
// none
|
|
}
|
|
|
|
#region mrf node abstractions
|
|
|
|
[TC(typeof(EXP))] public abstract class MrfNode : IMetaXmlItem
|
|
{
|
|
public MrfNodeType NodeType { get; set; }
|
|
public ushort NodeIndex { get; set; } //index in the parent state node
|
|
public MetaHash Name { get; set; }
|
|
|
|
public int FileIndex { get; set; } //index in the file
|
|
public int FileOffset { get; set; } //offset in the file
|
|
public int FileDataSize { get; set; } //number of bytes read from the file (this node only)
|
|
|
|
public MrfNode(MrfNodeType type)
|
|
{
|
|
NodeType = type;
|
|
}
|
|
|
|
public virtual void Read(DataReader r)
|
|
{
|
|
NodeType = (MrfNodeType)r.ReadUInt16();
|
|
NodeIndex = r.ReadUInt16();
|
|
Name = r.ReadUInt32();
|
|
}
|
|
|
|
public virtual void Write(DataWriter w)
|
|
{
|
|
w.Write((ushort)NodeType);
|
|
w.Write(NodeIndex);
|
|
w.Write(Name);
|
|
}
|
|
|
|
public virtual void ReadXml(XmlNode node)
|
|
{
|
|
Name = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Name"));
|
|
NodeIndex = (ushort)Xml.GetChildUIntAttribute(node, "NodeIndex");
|
|
}
|
|
|
|
public virtual void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.StringTag(sb, indent, "Name", MrfXml.HashString(Name));
|
|
MrfXml.ValueTag(sb, indent, "NodeIndex", NodeIndex.ToString());
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return /* FileIndex.ToString() + ":" + FileOffset.ToString() + "+" + FileDataSize.ToString() + ": " + */
|
|
NodeType.ToString() + " - " + NodeIndex.ToString() + " - " + Name.ToString();
|
|
}
|
|
|
|
public virtual void ResolveRelativeOffsets(MrfFile mrf)
|
|
{
|
|
}
|
|
|
|
public virtual void UpdateRelativeOffsets()
|
|
{
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public abstract class MrfNodeWithFlagsBase : MrfNode
|
|
{
|
|
public uint Flags { get; set; }
|
|
|
|
public MrfNodeWithFlagsBase(MrfNodeType type) : base(type) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
Flags = r.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(Flags);
|
|
}
|
|
|
|
public uint GetFlagsSubset(int bitOffset, uint mask)
|
|
{
|
|
return (Flags >> bitOffset) & mask;
|
|
}
|
|
|
|
public void SetFlagsSubset(int bitOffset, uint mask, uint value)
|
|
{
|
|
Flags = (Flags & ~(mask << bitOffset)) | ((value & mask) << bitOffset);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString() + " - " + Flags.ToString("X8");
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public abstract class MrfNodeStateBase : MrfNode
|
|
{
|
|
public int InitialNodeOffset { get; set; } // offset from the start of this field
|
|
public int InitialNodeFileOffset { get; set; }
|
|
public uint StateUnk3 { get; set; }
|
|
public bool HasEntryParameter { get; set; }
|
|
public bool HasExitParameter { get; set; }
|
|
public byte StateChildCount { get; set; } // for Node(Inlined)StateMachine the number of states, for NodeState the number of children excluding NodeTails
|
|
public byte TransitionCount { get; set; }
|
|
public MetaHash EntryParameterName { get; set; } // bool parameter set to true when the network enters this node
|
|
public MetaHash ExitParameterName { get; set; } // bool parameter set to true when the network leaves this node
|
|
public int TransitionsOffset { get; set; } // offset from the start of this field
|
|
public int TransitionsFileOffset { get; set; }
|
|
|
|
public MrfNode InitialNode { get; set; } // for Node(Inlined)StateMachine this is a NodeStateBase, for NodeState it can be any node
|
|
public MrfStateTransition[] Transitions { get; set; }
|
|
|
|
public MrfNodeStateBase(MrfNodeType type) : base(type) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
InitialNodeOffset = r.ReadInt32();
|
|
InitialNodeFileOffset = (int)(r.Position + InitialNodeOffset - 4);
|
|
StateUnk3 = r.ReadUInt32();
|
|
HasEntryParameter = r.ReadByte() != 0;
|
|
HasExitParameter = r.ReadByte() != 0;
|
|
StateChildCount = r.ReadByte();
|
|
TransitionCount = r.ReadByte();
|
|
EntryParameterName = r.ReadUInt32();
|
|
ExitParameterName = r.ReadUInt32();
|
|
TransitionsOffset = r.ReadInt32();
|
|
TransitionsFileOffset = (int)(r.Position + TransitionsOffset - 4);
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(InitialNodeOffset);
|
|
w.Write(StateUnk3);
|
|
w.Write((byte)(HasEntryParameter ? 1 : 0));
|
|
w.Write((byte)(HasExitParameter ? 1 : 0));
|
|
w.Write(StateChildCount);
|
|
w.Write(TransitionCount);
|
|
w.Write(EntryParameterName);
|
|
w.Write(ExitParameterName);
|
|
w.Write(TransitionsOffset);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
StateUnk3 = Xml.GetChildUIntAttribute(node, "StateUnk3");
|
|
EntryParameterName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "EntryParameterName"));
|
|
ExitParameterName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "ExitParameterName"));
|
|
HasEntryParameter = EntryParameterName != 0;
|
|
HasExitParameter = ExitParameterName != 0;
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
MrfXml.ValueTag(sb, indent, "StateUnk3", StateUnk3.ToString());
|
|
MrfXml.StringTag(sb, indent, "EntryParameterName", HasEntryParameter ? MrfXml.HashString(EntryParameterName) : null);
|
|
MrfXml.StringTag(sb, indent, "ExitParameterName", HasExitParameter ? MrfXml.HashString(ExitParameterName) : null);
|
|
}
|
|
|
|
public override void ResolveRelativeOffsets(MrfFile mrf)
|
|
{
|
|
base.ResolveRelativeOffsets(mrf);
|
|
|
|
var initNode = mrf.FindNodeAtFileOffset(InitialNodeFileOffset);
|
|
if (initNode == null)
|
|
{ } // no hits
|
|
|
|
if ((this is MrfNodeStateMachine || this is MrfNodeInlinedStateMachine) && !(initNode is MrfNodeStateBase))
|
|
{ } // no hits, state machines initial node is always a MrfNodeStateBase
|
|
|
|
InitialNode = initNode;
|
|
}
|
|
|
|
public override void UpdateRelativeOffsets()
|
|
{
|
|
base.UpdateRelativeOffsets();
|
|
|
|
InitialNodeFileOffset = InitialNode.FileOffset;
|
|
InitialNodeOffset = InitialNodeFileOffset - (FileOffset + 0x8);
|
|
}
|
|
|
|
protected void ResolveNodeOffsetsInTransitions(MrfStateTransition[] transitions, MrfFile mrf)
|
|
{
|
|
if (transitions == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var t in transitions)
|
|
{
|
|
var node = mrf.FindNodeAtFileOffset(t.TargetStateFileOffset);
|
|
if (node == null)
|
|
{ } // no hits
|
|
|
|
if (!(node is MrfNodeStateBase))
|
|
{ } // no hits
|
|
|
|
t.TargetState = (MrfNodeStateBase)node;
|
|
}
|
|
}
|
|
|
|
protected void ResolveNodeOffsetsInStates(MrfStateRef[] states, MrfFile mrf)
|
|
{
|
|
if (states == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var s in states)
|
|
{
|
|
var node = mrf.FindNodeAtFileOffset(s.StateFileOffset);
|
|
if (node == null)
|
|
{ } // no hits
|
|
|
|
if (!(node is MrfNodeStateBase))
|
|
{ } // no hits
|
|
|
|
s.State = (MrfNodeStateBase)node;
|
|
}
|
|
}
|
|
|
|
protected int UpdateNodeOffsetsInTransitions(MrfStateTransition[] transitions, int transitionsArrayOffset, bool offsetSetToZeroIfNoTransitions)
|
|
{
|
|
int offset = transitionsArrayOffset;
|
|
TransitionsFileOffset = offset;
|
|
TransitionsOffset = TransitionsFileOffset - (FileOffset + 0x1C);
|
|
if (transitions != null)
|
|
{
|
|
foreach (var transition in transitions)
|
|
{
|
|
transition.TargetStateFileOffset = transition.TargetState.FileOffset;
|
|
transition.TargetStateOffset = transition.TargetStateFileOffset - (offset + 0x14);
|
|
transition.CalculateDataSize();
|
|
offset += (int)transition.DataSize;
|
|
}
|
|
}
|
|
else if (offsetSetToZeroIfNoTransitions)
|
|
{
|
|
// Special case for some MrfNodeStateMachines with no transitions and MrfNodeInlinedStateMachines.
|
|
// Unlike MrfNodeState, when these don't have transtions the TransititionsOffset is 0.
|
|
// So we set it to 0 too to be able to compare Save() result byte by byte
|
|
TransitionsOffset = 0;
|
|
TransitionsFileOffset = FileOffset + 0x1C; // and keep FileOffset consistent with what Read() does
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
protected int UpdateNodeOffsetsInStates(MrfStateRef[] states, int statesArrayOffset)
|
|
{
|
|
int offset = statesArrayOffset;
|
|
if (states != null)
|
|
{
|
|
foreach (var state in states)
|
|
{
|
|
state.StateFileOffset = state.State.FileOffset;
|
|
state.StateOffset = state.StateFileOffset - (offset + 4);
|
|
offset += 8; // sizeof(MrfStructStateMachineStateRef)
|
|
}
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
protected void ResolveXmlTargetStatesInTransitions(MrfStateRef[] states)
|
|
{
|
|
if (states == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var state in states)
|
|
{
|
|
if (state.State.Transitions == null) continue;
|
|
|
|
foreach (var t in state.State.Transitions)
|
|
{
|
|
t.TargetState = states.FirstOrDefault(s => s.StateName == t.XmlTargetStateName)?.State;
|
|
if (t.TargetState == null)
|
|
{ } // only 1 hit in onfoothuman.mrf, solved at the end of MrfFile.ReadXml
|
|
}
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString() + " - " + Name.ToString()
|
|
+ " - Init:" + InitialNodeOffset.ToString()
|
|
+ " - CC:" + StateChildCount.ToString()
|
|
+ " - TC:" + TransitionCount.ToString()
|
|
+ " - " + StateUnk3.ToString()
|
|
+ " - OnEntry(" + HasEntryParameter.ToString() + "):" + EntryParameterName.ToString()
|
|
+ " - OnExit(" + HasExitParameter.ToString() + "):" + ExitParameterName.ToString()
|
|
+ " - TO:" + TransitionsOffset.ToString();
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public abstract class MrfNodePairBase : MrfNodeWithFlagsBase
|
|
{
|
|
public int Child0Offset { get; set; }
|
|
public int Child0FileOffset { get; set; }
|
|
public int Child1Offset { get; set; }
|
|
public int Child1FileOffset { get; set; }
|
|
|
|
public MrfNode Child0 { get; set; }
|
|
public MrfNode Child1 { get; set; }
|
|
|
|
public MrfNodePairBase(MrfNodeType type) : base(type) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
Child0Offset = r.ReadInt32();
|
|
Child0FileOffset = (int)(r.Position + Child0Offset - 4);
|
|
Child1Offset = r.ReadInt32();
|
|
Child1FileOffset = (int)(r.Position + Child1Offset - 4);
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
w.Write(Child0Offset);
|
|
w.Write(Child1Offset);
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
MrfXml.WriteNode(sb, indent, "Child0", Child0);
|
|
MrfXml.WriteNode(sb, indent, "Child1", Child1);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
Child0 = XmlMrf.ReadChildNode(node, "Child0");
|
|
Child1 = XmlMrf.ReadChildNode(node, "Child1");
|
|
}
|
|
|
|
public override void ResolveRelativeOffsets(MrfFile mrf)
|
|
{
|
|
base.ResolveRelativeOffsets(mrf);
|
|
|
|
var child0 = mrf.FindNodeAtFileOffset(Child0FileOffset);
|
|
if (child0 == null)
|
|
{ } // no hits
|
|
|
|
var child1 = mrf.FindNodeAtFileOffset(Child1FileOffset);
|
|
if (child1 == null)
|
|
{ } // no hits
|
|
|
|
Child0 = child0;
|
|
Child1 = child1;
|
|
}
|
|
|
|
public override void UpdateRelativeOffsets()
|
|
{
|
|
base.UpdateRelativeOffsets();
|
|
|
|
Child0FileOffset = Child0.FileOffset;
|
|
Child0Offset = Child0FileOffset - (FileOffset + 0xC + 0);
|
|
Child1FileOffset = Child1.FileOffset;
|
|
Child1Offset = Child1FileOffset - (FileOffset + 0xC + 4);
|
|
}
|
|
}
|
|
|
|
public enum MrfSynchronizerType
|
|
{
|
|
Phase = 0, // attaches a rage::mvSynchronizerPhase instance to the node
|
|
Tag = 1, // attaches a rage::mvSynchronizerTag instance to the node, sets the SynchronizerTagFlags field too
|
|
None = 2,
|
|
}
|
|
|
|
[Flags] public enum MrfSynchronizerTagFlags : uint
|
|
{
|
|
LeftFootHeel = 0x20, // adds rage::mvSynchronizerTag::sm_LeftFootHeel to a rage::mvSynchronizerTag instance attached to the node
|
|
RightFootHeel = 0x40, // same but with rage::mvSynchronizerTag::sm_RightFootHeel
|
|
}
|
|
|
|
public enum MrfValueType
|
|
{
|
|
None = 0,
|
|
Literal = 1, // specific value (in case of expressions, clips or filters, a dictionary/name hash pair)
|
|
Parameter = 2, // lookup value in the network parameters
|
|
}
|
|
|
|
public enum MrfInfluenceOverride
|
|
{
|
|
None = 0, // influence affected by weight (at least in NodeBlend case)
|
|
Zero = 1, // influence = 0.0
|
|
One = 2, // influence = 1.0
|
|
}
|
|
|
|
[TC(typeof(EXP))] public abstract class MrfNodePairWeightedBase : MrfNodePairBase
|
|
{
|
|
// rage::mvNodePairDef
|
|
|
|
public MrfSynchronizerTagFlags SynchronizerTagFlags { get; set; }
|
|
public float Weight { get; set; }
|
|
public MetaHash WeightParameterName { get; set; }
|
|
public MetaHash FrameFilterDictionaryName { get; set; }
|
|
public MetaHash FrameFilterName { get; set; }
|
|
public MetaHash FrameFilterParameterName { get; set; }
|
|
|
|
// flags getters and setters
|
|
public MrfValueType WeightType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(0, 3);
|
|
set => SetFlagsSubset(0, 3, (uint)value);
|
|
}
|
|
public MrfValueType FrameFilterType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(2, 3);
|
|
set => SetFlagsSubset(2, 3, (uint)value);
|
|
}
|
|
public bool UnkFlag6 // Transitional? RDR3's rage::mvNodePairDef::GetTransitionalFlagFrom(uint) reads these bits
|
|
{ // always 0
|
|
get => GetFlagsSubset(6, 1) != 0;
|
|
set => SetFlagsSubset(6, 1, value ? 1 : 0u);
|
|
}
|
|
public uint UnkFlag7 // Immutable? RDR3's rage::mvNodePairDef::GetImmutableFlagFrom(uint) reads these bits
|
|
{ // 0 or 1
|
|
get => GetFlagsSubset(7, 3);
|
|
set => SetFlagsSubset(7, 3, value);
|
|
}
|
|
public MrfInfluenceOverride Child0InfluenceOverride
|
|
{
|
|
get => (MrfInfluenceOverride)GetFlagsSubset(12, 3);
|
|
set => SetFlagsSubset(12, 3, (uint)value);
|
|
}
|
|
public MrfInfluenceOverride Child1InfluenceOverride
|
|
{
|
|
get => (MrfInfluenceOverride)GetFlagsSubset(14, 3);
|
|
set => SetFlagsSubset(14, 3, (uint)value);
|
|
}
|
|
public MrfSynchronizerType SynchronizerType
|
|
{
|
|
get => (MrfSynchronizerType)GetFlagsSubset(19, 3);
|
|
set => SetFlagsSubset(19, 3, (uint)value);
|
|
}
|
|
public uint UnkFlag21 // OutputParameterRuleSet? RDR3's rage::mvNodePairDef::GetOutputParameterRuleSetFrom(uint) reads these bits
|
|
{ // always 0
|
|
get => GetFlagsSubset(21, 3);
|
|
set => SetFlagsSubset(21, 3, value);
|
|
}
|
|
public uint UnkFlag23 // FirstFrameSyncOnly? RDR3's rage::mvNodePairDef::GetFirstFrameSyncOnlyFrom(uint) reads these bits
|
|
{ // always 0
|
|
get => GetFlagsSubset(23, 3);
|
|
set => SetFlagsSubset(23, 3, value);
|
|
}
|
|
public bool UnkFlag25 // SortTargets? RDR3's rage::mvNodePairDef::GetSortTargetsFrom(uint) reads these bits
|
|
{ // always 0
|
|
get => GetFlagsSubset(25, 1) != 0;
|
|
set => SetFlagsSubset(25, 1, value ? 1 : 0u);
|
|
}
|
|
public bool MergeBlend
|
|
{
|
|
get => GetFlagsSubset(31, 1) != 0;
|
|
set => SetFlagsSubset(31, 1, value ? 1 : 0u);
|
|
}
|
|
|
|
public MrfNodePairWeightedBase(MrfNodeType type) : base(type) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
SynchronizerTagFlags = (MrfSynchronizerTagFlags)r.ReadUInt32();
|
|
|
|
switch (WeightType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
Weight = r.ReadSingle();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
WeightParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
switch (FrameFilterType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
FrameFilterDictionaryName = r.ReadUInt32();
|
|
FrameFilterName = r.ReadUInt32();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
FrameFilterParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
w.Write((uint)SynchronizerTagFlags);
|
|
|
|
switch (WeightType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(Weight);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(WeightParameterName);
|
|
break;
|
|
}
|
|
|
|
switch (FrameFilterType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(FrameFilterDictionaryName);
|
|
w.Write(FrameFilterName);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(FrameFilterParameterName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
|
|
Child0InfluenceOverride = Xml.GetChildEnumInnerText<MrfInfluenceOverride>(node, "Child0InfluenceOverride");
|
|
Child1InfluenceOverride = Xml.GetChildEnumInnerText<MrfInfluenceOverride>(node, "Child1InfluenceOverride");
|
|
(WeightType, Weight, WeightParameterName) = XmlMrf.GetChildParameterizedFloat(node, "Weight");
|
|
(FrameFilterType, FrameFilterDictionaryName, FrameFilterName, FrameFilterParameterName) = XmlMrf.GetChildParameterizedAsset(node, "FrameFilter");
|
|
SynchronizerType = Xml.GetChildEnumInnerText<MrfSynchronizerType>(node, "SynchronizerType");
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
{
|
|
SynchronizerTagFlags = Xml.GetChildEnumInnerText<MrfSynchronizerTagFlags>(node, "SynchronizerTagFlags");
|
|
}
|
|
MergeBlend = Xml.GetChildBoolAttribute(node, "MergeBlend");
|
|
UnkFlag6 = Xml.GetChildBoolAttribute(node, "UnkFlag6");
|
|
UnkFlag7 = Xml.GetChildUIntAttribute(node, "UnkFlag7");
|
|
UnkFlag21 = Xml.GetChildUIntAttribute(node, "UnkFlag21");
|
|
UnkFlag23 = Xml.GetChildUIntAttribute(node, "UnkFlag23");
|
|
UnkFlag25 = Xml.GetChildBoolAttribute(node, "UnkFlag25");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
|
|
MrfXml.StringTag(sb, indent, "Child0InfluenceOverride", Child0InfluenceOverride.ToString());
|
|
MrfXml.StringTag(sb, indent, "Child1InfluenceOverride", Child1InfluenceOverride.ToString());
|
|
MrfXml.ParameterizedFloatTag(sb, indent, "Weight", WeightType, Weight, WeightParameterName);
|
|
MrfXml.ParameterizedAssetTag(sb, indent, "FrameFilter", FrameFilterType, FrameFilterDictionaryName, FrameFilterName, FrameFilterParameterName);
|
|
MrfXml.StringTag(sb, indent, "SynchronizerType", SynchronizerType.ToString());
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
{
|
|
MrfXml.StringTag(sb, indent, "SynchronizerTagFlags", SynchronizerTagFlags.ToString());
|
|
}
|
|
MrfXml.ValueTag(sb, indent, "MergeBlend", MergeBlend.ToString());
|
|
MrfXml.ValueTag(sb, indent, "UnkFlag6", UnkFlag6.ToString());
|
|
MrfXml.ValueTag(sb, indent, "UnkFlag7", UnkFlag7.ToString());
|
|
MrfXml.ValueTag(sb, indent, "UnkFlag21", UnkFlag21.ToString());
|
|
MrfXml.ValueTag(sb, indent, "UnkFlag23", UnkFlag23.ToString());
|
|
MrfXml.ValueTag(sb, indent, "UnkFlag25", UnkFlag25.ToString());
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public abstract class MrfNodeWithChildBase : MrfNodeWithFlagsBase
|
|
{
|
|
public int ChildOffset { get; set; }
|
|
public int ChildFileOffset { get; set; }
|
|
|
|
public MrfNode Child { get; set; }
|
|
|
|
public MrfNodeWithChildBase(MrfNodeType type) : base(type) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
ChildOffset = r.ReadInt32();
|
|
ChildFileOffset = (int)(r.Position + ChildOffset - 4);
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
w.Write(ChildOffset);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
Child = XmlMrf.ReadChildNode(node, "Child");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
MrfXml.WriteNode(sb, indent, "Child", Child);
|
|
}
|
|
|
|
public override void ResolveRelativeOffsets(MrfFile mrf)
|
|
{
|
|
base.ResolveRelativeOffsets(mrf);
|
|
|
|
var node = mrf.FindNodeAtFileOffset(ChildFileOffset);
|
|
if (node == null)
|
|
{ } // no hits
|
|
|
|
Child = node;
|
|
}
|
|
|
|
public override void UpdateRelativeOffsets()
|
|
{
|
|
base.UpdateRelativeOffsets();
|
|
|
|
ChildFileOffset = Child.FileOffset;
|
|
ChildOffset = ChildFileOffset - (FileOffset + 0xC);
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public abstract class MrfNodeWithChildAndFilterBase : MrfNodeWithChildBase
|
|
{
|
|
public MetaHash FrameFilterDictionaryName { get; set; }
|
|
public MetaHash FrameFilterName { get; set; }
|
|
public MetaHash FrameFilterParameterName { get; set; }
|
|
|
|
// flags getters and setters
|
|
public MrfValueType FrameFilterType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(0, 3);
|
|
set => SetFlagsSubset(0, 3, (uint)value);
|
|
}
|
|
|
|
public MrfNodeWithChildAndFilterBase(MrfNodeType type) : base(type) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
switch (FrameFilterType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
FrameFilterDictionaryName = r.ReadUInt32();
|
|
FrameFilterName = r.ReadUInt32();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
FrameFilterParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
switch (FrameFilterType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(FrameFilterDictionaryName);
|
|
w.Write(FrameFilterName);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(FrameFilterParameterName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
(FrameFilterType, FrameFilterDictionaryName, FrameFilterName, FrameFilterParameterName) = XmlMrf.GetChildParameterizedAsset(node, "FrameFilter");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
MrfXml.ParameterizedAssetTag(sb, indent, "FrameFilter", FrameFilterType, FrameFilterDictionaryName, FrameFilterName, FrameFilterParameterName);
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public abstract class MrfNodeNBase : MrfNodeWithFlagsBase
|
|
{
|
|
// rage::mvNodeNDef
|
|
|
|
public MrfSynchronizerTagFlags SynchronizerTagFlags { get; set; }
|
|
public byte[] Unk2 { get; set; } // unused
|
|
public MetaHash Unk2ParameterName { get; set; } // unused
|
|
public MetaHash FrameFilterDictionaryName { get; set; }
|
|
public MetaHash FrameFilterName { get; set; }
|
|
public MetaHash FrameFilterParameterName { get; set; }
|
|
public int[] ChildrenOffsets { get; set; }
|
|
public int[] ChildrenFileOffsets { get; set; }
|
|
public MrfNodeNChildData[] ChildrenData { get; set; }
|
|
public uint[] ChildrenFlags { get; set; } // 8 bits per child
|
|
|
|
public MrfNode[] Children { get; set; }
|
|
|
|
// flags getters and setters
|
|
public MrfValueType Unk2Type
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(0, 3);
|
|
set => SetFlagsSubset(0, 3, (uint)value);
|
|
}
|
|
public MrfValueType FrameFilterType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(2, 3);
|
|
set => SetFlagsSubset(2, 3, (uint)value);
|
|
}
|
|
public bool ZeroDestination // name is correct based on RDR3 symbols but not sure about its use
|
|
{ // only difference I found is when true the weight of child #0 returns 1.0 instead of the weight in ChildrenData
|
|
get => GetFlagsSubset(4, 1) != 0;
|
|
set => SetFlagsSubset(4, 1, value ? 1 : 0u);
|
|
}
|
|
public MrfSynchronizerType SynchronizerType
|
|
{
|
|
get => (MrfSynchronizerType)GetFlagsSubset(19, 3);
|
|
set => SetFlagsSubset(19, 3, (uint)value);
|
|
}
|
|
public uint ChildrenCount
|
|
{
|
|
get => GetFlagsSubset(26, 0x3F);
|
|
set => SetFlagsSubset(26, 0x3F, value);
|
|
}
|
|
|
|
// ChildrenFlags getters and setters
|
|
public byte GetChildFlags(int index)
|
|
{
|
|
int blockIndex = 8 * index / 32;
|
|
int bitOffset = 8 * index % 32;
|
|
uint block = ChildrenFlags[blockIndex];
|
|
return (byte)((block >> bitOffset) & 0xFF);
|
|
}
|
|
public void SetChildFlags(int index, byte flags)
|
|
{
|
|
int blockIndex = 8 * index / 32;
|
|
int bitOffset = 8 * index % 32;
|
|
uint block = ChildrenFlags[blockIndex];
|
|
block = (block & ~(0xFFu << bitOffset)) | ((uint)flags << bitOffset);
|
|
ChildrenFlags[blockIndex] = block;
|
|
}
|
|
public MrfValueType GetChildWeightType(int index)
|
|
{
|
|
return (MrfValueType)(GetChildFlags(index) & 3);
|
|
}
|
|
public void SetChildWeightType(int index, MrfValueType type)
|
|
{
|
|
var flags = GetChildFlags(index);
|
|
flags = (byte)(flags & ~3u | ((uint)type & 3u));
|
|
SetChildFlags(index, flags);
|
|
}
|
|
public MrfValueType GetChildFrameFilterType(int index)
|
|
{
|
|
return (MrfValueType)((GetChildFlags(index) >> 4) & 3);
|
|
}
|
|
public void SetChildFrameFilterType(int index, MrfValueType type)
|
|
{
|
|
var flags = GetChildFlags(index);
|
|
flags = (byte)(flags & ~(3u << 4) | (((uint)type & 3u) << 4));
|
|
SetChildFlags(index, flags);
|
|
}
|
|
|
|
public MrfNodeNBase(MrfNodeType type) : base(type) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
SynchronizerTagFlags = (MrfSynchronizerTagFlags)r.ReadUInt32();
|
|
|
|
switch (Unk2Type)
|
|
{
|
|
case MrfValueType.Literal:
|
|
Unk2 = r.ReadBytes(76); // Unused?
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
Unk2ParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
switch (FrameFilterType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
FrameFilterDictionaryName = r.ReadUInt32();
|
|
FrameFilterName = r.ReadUInt32();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
FrameFilterParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
var childrenCount = ChildrenCount;
|
|
if (childrenCount > 0)
|
|
{
|
|
ChildrenOffsets = new int[childrenCount];
|
|
ChildrenFileOffsets = new int[childrenCount];
|
|
|
|
for (int i = 0; i < childrenCount; i++)
|
|
{
|
|
ChildrenOffsets[i] = r.ReadInt32();
|
|
ChildrenFileOffsets[i] = (int)(r.Position + ChildrenOffsets[i] - 4);
|
|
}
|
|
}
|
|
|
|
var childrenFlagsBlockCount = childrenCount * 8 / 32 + 1;
|
|
|
|
if (childrenFlagsBlockCount > 0)
|
|
{
|
|
ChildrenFlags = new uint[childrenFlagsBlockCount];
|
|
|
|
for (int i = 0; i < childrenFlagsBlockCount; i++)
|
|
ChildrenFlags[i] = r.ReadUInt32();
|
|
}
|
|
|
|
if (ChildrenCount == 0)
|
|
return;
|
|
|
|
ChildrenData = new MrfNodeNChildData[childrenCount];
|
|
|
|
for (int i = 0; i < childrenCount; i++)
|
|
{
|
|
var item = new MrfNodeNChildData();
|
|
|
|
switch (GetChildWeightType(i))
|
|
{
|
|
case MrfValueType.Literal:
|
|
item.Weight = r.ReadSingle();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
item.WeightParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
switch (GetChildFrameFilterType(i))
|
|
{
|
|
case MrfValueType.Literal:
|
|
item.FrameFilterDictionaryName = r.ReadUInt32();
|
|
item.FrameFilterName = r.ReadUInt32();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
item.FrameFilterParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
ChildrenData[i] = item;
|
|
}
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
w.Write((uint)SynchronizerTagFlags);
|
|
|
|
switch (Unk2Type)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(Unk2);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(Unk2ParameterName);
|
|
break;
|
|
}
|
|
|
|
switch (FrameFilterType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(FrameFilterDictionaryName);
|
|
w.Write(FrameFilterName);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(FrameFilterParameterName);
|
|
break;
|
|
}
|
|
|
|
var childrenCount = ChildrenCount;
|
|
if (childrenCount > 0)
|
|
{
|
|
foreach (var value in ChildrenOffsets)
|
|
w.Write(value);
|
|
}
|
|
|
|
var childrenFlagsBlockCount = childrenCount * 8 / 32 + 1;
|
|
|
|
if (childrenFlagsBlockCount > 0)
|
|
{
|
|
foreach (var value in ChildrenFlags)
|
|
w.Write(value);
|
|
}
|
|
|
|
if (childrenCount == 0)
|
|
return;
|
|
|
|
for (int i = 0; i < childrenCount; i++)
|
|
{
|
|
var item = ChildrenData[i];
|
|
|
|
switch (GetChildWeightType(i))
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(item.Weight);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(item.WeightParameterName);
|
|
break;
|
|
}
|
|
|
|
switch (GetChildFrameFilterType(i))
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(item.FrameFilterDictionaryName);
|
|
w.Write(item.FrameFilterName);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(item.FrameFilterParameterName);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
|
|
(FrameFilterType, FrameFilterDictionaryName, FrameFilterName, FrameFilterParameterName) = XmlMrf.GetChildParameterizedAsset(node, "FrameFilter");
|
|
SynchronizerType = Xml.GetChildEnumInnerText<MrfSynchronizerType>(node, "SynchronizerType");
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
{
|
|
SynchronizerTagFlags = Xml.GetChildEnumInnerText<MrfSynchronizerTagFlags>(node, "SynchronizerTagFlags");
|
|
}
|
|
ZeroDestination = Xml.GetChildBoolAttribute(node, "ZeroDestination");
|
|
Children = null;
|
|
ChildrenData = null;
|
|
ChildrenFlags = null;
|
|
ChildrenOffsets = null;
|
|
ChildrenCount = 0;
|
|
var statesNode = node.SelectSingleNode("Children");
|
|
if (statesNode != null)
|
|
{
|
|
var inodes = statesNode.SelectNodes("Item");
|
|
if (inodes?.Count > 0)
|
|
{
|
|
ChildrenCount = (uint)inodes.Count;
|
|
Children = new MrfNode[ChildrenCount];
|
|
ChildrenData = new MrfNodeNChildData[ChildrenCount];
|
|
ChildrenFlags = new uint[ChildrenCount * 8 / 32 + 1];
|
|
ChildrenOffsets = new int[ChildrenCount];
|
|
int i = 0;
|
|
foreach (XmlNode inode in inodes)
|
|
{
|
|
var weight = XmlMrf.GetChildParameterizedFloat(inode, "Weight");
|
|
var filter = XmlMrf.GetChildParameterizedAsset(inode, "FrameFilter");
|
|
|
|
ChildrenData[i].Weight = weight.Value;
|
|
ChildrenData[i].WeightParameterName = weight.ParameterName;
|
|
SetChildWeightType(i, weight.Type);
|
|
ChildrenData[i].FrameFilterDictionaryName = filter.DictionaryName;
|
|
ChildrenData[i].FrameFilterName = filter.AssetName;
|
|
ChildrenData[i].FrameFilterParameterName = filter.ParameterName;
|
|
SetChildFrameFilterType(i, filter.Type);
|
|
Children[i] = XmlMrf.ReadChildNode(inode, "Node");
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
|
|
Unk2Type = MrfValueType.None;
|
|
Unk2 = null;
|
|
Unk2ParameterName = 0;
|
|
|
|
MrfXml.ParameterizedAssetTag(sb, indent, "FrameFilter", FrameFilterType, FrameFilterDictionaryName, FrameFilterName, FrameFilterParameterName);
|
|
MrfXml.StringTag(sb, indent, "SynchronizerType", SynchronizerType.ToString());
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
{
|
|
MrfXml.StringTag(sb, indent, "SynchronizerTagFlags", SynchronizerTagFlags.ToString());
|
|
}
|
|
MrfXml.ValueTag(sb, indent, "ZeroDestination", ZeroDestination.ToString());
|
|
int cindent = indent + 1;
|
|
int cindent2 = cindent + 1;
|
|
int childIndex = 0;
|
|
MrfXml.OpenTag(sb, indent, "Children");
|
|
foreach (var child in Children)
|
|
{
|
|
var childData = ChildrenData[childIndex];
|
|
MrfXml.OpenTag(sb, cindent, "Item");
|
|
MrfXml.ParameterizedFloatTag(sb, cindent2, "Weight", GetChildWeightType(childIndex), childData.Weight, childData.WeightParameterName);
|
|
MrfXml.ParameterizedAssetTag(sb, cindent2, "FrameFilter", GetChildFrameFilterType(childIndex), childData.FrameFilterDictionaryName, childData.FrameFilterName, childData.FrameFilterParameterName);
|
|
MrfXml.WriteNode(sb, cindent2, "Node", child);
|
|
MrfXml.CloseTag(sb, cindent, "Item");
|
|
childIndex++;
|
|
}
|
|
MrfXml.CloseTag(sb, indent, "Children");
|
|
}
|
|
|
|
public override void ResolveRelativeOffsets(MrfFile mrf)
|
|
{
|
|
base.ResolveRelativeOffsets(mrf);
|
|
|
|
if (ChildrenFileOffsets != null)
|
|
{
|
|
Children = new MrfNode[ChildrenFileOffsets.Length];
|
|
for (int i = 0; i < ChildrenFileOffsets.Length; i++)
|
|
{
|
|
var node = mrf.FindNodeAtFileOffset(ChildrenFileOffsets[i]);
|
|
if (node == null)
|
|
{ } // no hits
|
|
|
|
Children[i] = node;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void UpdateRelativeOffsets()
|
|
{
|
|
base.UpdateRelativeOffsets();
|
|
|
|
var offset = FileOffset + 0xC/*sizeof(MrfNodeWithFlagsBase)*/;
|
|
offset += SynchronizerType == MrfSynchronizerType.Tag ? 4 : 0;
|
|
offset += Unk2Type == MrfValueType.Literal ? 76 : 0;
|
|
offset += Unk2Type == MrfValueType.Parameter ? 4 : 0;
|
|
offset += FrameFilterType == MrfValueType.Literal ? 8 : 0;
|
|
offset += FrameFilterType == MrfValueType.Parameter ? 4 : 0;
|
|
|
|
if (Children != null)
|
|
{
|
|
ChildrenOffsets = new int[Children.Length];
|
|
ChildrenFileOffsets = new int[Children.Length];
|
|
for (int i = 0; i < Children.Length; i++)
|
|
{
|
|
var node = Children[i];
|
|
ChildrenFileOffsets[i] = node.FileOffset;
|
|
ChildrenOffsets[i] = node.FileOffset - offset;
|
|
offset += 4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public struct MrfNodeNChildData
|
|
{
|
|
public float Weight { get; set; }
|
|
public MetaHash WeightParameterName { get; set; }
|
|
public MetaHash FrameFilterDictionaryName { get; set; }
|
|
public MetaHash FrameFilterName { get; set; }
|
|
public MetaHash FrameFilterParameterName { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"{FloatUtil.ToString(Weight)} - {WeightParameterName} - {FrameFilterDictionaryName} - {FrameFilterName} - {FrameFilterParameterName}";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
#region mrf node structs
|
|
|
|
[TC(typeof(EXP))] public class MrfHeaderUnk1 : IMetaXmlItem
|
|
{
|
|
public uint Size { get; set; }
|
|
public byte[] Bytes { get; set; }
|
|
|
|
public MrfHeaderUnk1()
|
|
{
|
|
}
|
|
|
|
public MrfHeaderUnk1(DataReader r)
|
|
{
|
|
Size = r.ReadUInt32();
|
|
Bytes = r.ReadBytes((int)Size);
|
|
}
|
|
|
|
public void Write(DataWriter w)
|
|
{
|
|
w.Write(Size);
|
|
w.Write(Bytes);
|
|
}
|
|
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
Bytes = Xml.GetChildRawByteArrayNullable(node, "Bytes");
|
|
Size = (uint)(Bytes?.Length ?? 0);
|
|
}
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.WriteRawArray(sb, Bytes, indent, "Bytes", "", MrfXml.FormatHexByte, 16);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Size.ToString() + " bytes";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// If used as <see cref="MrfFile.MoveNetworkTriggers"/>:
|
|
/// Parameter that can be triggered by the game to control transitions.
|
|
/// Only active for 1 tick.
|
|
/// The native `REQUEST_TASK_MOVE_NETWORK_STATE_TRANSITION` uses these triggers but appends "request" to the passed string,
|
|
/// e.g. `REQUEST_TASK_MOVE_NETWORK_STATE_TRANSITION(ped, "running")` will trigger "runningrequest".
|
|
/// <para>
|
|
/// If used as <see cref="MrfFile.MoveNetworkFlags"/>:
|
|
/// Parameter that can be toggled by the game to control transitions.
|
|
/// Can be enabled with fwClipSet.moveNetworkFlags too (seems like only if the game uses it as a MrfClipContainerType.VariableClipSet).
|
|
/// </para>
|
|
/// </summary>
|
|
[TC(typeof(EXP))] public struct MrfMoveNetworkBit : IMetaXmlItem
|
|
{
|
|
public static MrfMoveNetworkBit EndMarker => new MrfMoveNetworkBit { Name = 0xFFFFFFFF, BitPosition = 0 };
|
|
|
|
public MetaHash Name { get; set; }
|
|
public int BitPosition { get; set; }
|
|
|
|
public bool IsEndMarker => Name == 0xFFFFFFFF;
|
|
|
|
public MrfMoveNetworkBit(DataReader r)
|
|
{
|
|
Name = r.ReadUInt32();
|
|
BitPosition = r.ReadInt32();
|
|
}
|
|
|
|
public void Write(DataWriter w)
|
|
{
|
|
w.Write(Name);
|
|
w.Write(BitPosition);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return IsEndMarker ? "--- end marker ---" : $"{Name} - {BitPosition}";
|
|
}
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.StringTag(sb, indent, "Name", MrfXml.HashString(Name));
|
|
MrfXml.ValueTag(sb, indent, "BitPosition", BitPosition.ToString());
|
|
}
|
|
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
Name = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Name"));
|
|
BitPosition = Xml.GetChildIntAttribute(node, "BitPosition");
|
|
}
|
|
}
|
|
|
|
public enum MrfWeightModifierType
|
|
{
|
|
SlowInSlowOut = 0,
|
|
SlowOut = 1,
|
|
SlowIn = 2,
|
|
None = 3,
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfStateTransition : IMetaXmlItem
|
|
{
|
|
// rage::mvTransitionDef
|
|
|
|
public uint Flags { get; set; }
|
|
public MrfSynchronizerTagFlags SynchronizerTagFlags { get; set; }
|
|
public float Duration { get; set; } // time in seconds it takes for the transition to blend between the source and target states
|
|
public MetaHash DurationParameterName { get; set; }
|
|
public MetaHash ProgressParameterName { get; set; } // parameter where to store the transition progress percentage (0.0 to 1.0)
|
|
public int TargetStateOffset { get; set; } // offset from the start of this field
|
|
public int TargetStateFileOffset { get; set; }
|
|
public MetaHash FrameFilterDictionaryName { get; set; }
|
|
public MetaHash FrameFilterName { get; set; }
|
|
public MrfCondition[] Conditions { get; set; }
|
|
|
|
public MrfNodeStateBase TargetState { get; set; }
|
|
|
|
// flags getters and setters
|
|
public bool HasProgressParameter // if set, the transition progress percentage (0.0 to 1.0) is stored in ProgressParameterName
|
|
{
|
|
get => GetFlagsSubset(1, 1) != 0;
|
|
set => SetFlagsSubset(1, 1, value ? 1 : 0u);
|
|
}
|
|
public bool UnkFlag2_DetachUpdateObservers // if set, executes rage::DetachUpdateObservers on the source state
|
|
{
|
|
get => GetFlagsSubset(2, 1) != 0;
|
|
set => SetFlagsSubset(2, 1, value ? 1 : 0u);
|
|
}
|
|
public bool HasDurationParameter // if set use DurationParameterName instead of Duration. Duration is used as default if the paramter is not found
|
|
{
|
|
get => GetFlagsSubset(3, 1) != 0;
|
|
set => SetFlagsSubset(3, 1, value ? 1 : 0u);
|
|
}
|
|
public uint DataSize // number of bytes this transition takes, used to iterate the transitions array
|
|
{
|
|
get => GetFlagsSubset(4, 0x3FFF);
|
|
set => SetFlagsSubset(4, 0x3FFF, value);
|
|
}
|
|
public bool UnkFlag18
|
|
{
|
|
get => GetFlagsSubset(18, 1) != 0;
|
|
set => SetFlagsSubset(18, 1, value ? 1 : 0u);
|
|
}
|
|
public bool UnkFlag19
|
|
{
|
|
get => GetFlagsSubset(19, 1) != 0;
|
|
set => SetFlagsSubset(19, 1, value ? 1 : 0u);
|
|
}
|
|
public uint ConditionCount
|
|
{
|
|
get => GetFlagsSubset(20, 0xF);
|
|
set => SetFlagsSubset(20, 0xF, value);
|
|
}
|
|
public MrfWeightModifierType BlendModifier // modifier for the blend between source and target states
|
|
{
|
|
get => (MrfWeightModifierType)GetFlagsSubset(24, 7);
|
|
set => SetFlagsSubset(24, 7, (uint)value);
|
|
}
|
|
public MrfSynchronizerType SynchronizerType
|
|
{
|
|
get => (MrfSynchronizerType)GetFlagsSubset(28, 3);
|
|
set => SetFlagsSubset(28, 3, (uint)value);
|
|
}
|
|
public bool HasFrameFilter
|
|
{
|
|
get => GetFlagsSubset(30, 1) != 0;
|
|
set => SetFlagsSubset(30, 1, value ? 1 : 0u);
|
|
}
|
|
|
|
[System.ComponentModel.Browsable(false)]
|
|
public MetaHash XmlTargetStateName { get; set; } // for XML loading
|
|
|
|
public MrfStateTransition()
|
|
{
|
|
}
|
|
|
|
public MrfStateTransition(DataReader r)
|
|
{
|
|
var startReadPosition = r.Position;
|
|
|
|
Flags = r.ReadUInt32();
|
|
SynchronizerTagFlags = (MrfSynchronizerTagFlags)r.ReadUInt32();
|
|
Duration = r.ReadSingle();
|
|
DurationParameterName = r.ReadUInt32();
|
|
ProgressParameterName = r.ReadUInt32();
|
|
TargetStateOffset = r.ReadInt32();
|
|
TargetStateFileOffset = (int)(r.Position + TargetStateOffset - 4);
|
|
|
|
if (ConditionCount > 0)
|
|
{
|
|
Conditions = new MrfCondition[ConditionCount];
|
|
for (int i = 0; i < ConditionCount; i++)
|
|
{
|
|
var startPos = r.Position;
|
|
var conditionType = (MrfConditionType)r.ReadUInt16();
|
|
r.Position = startPos;
|
|
|
|
MrfCondition cond;
|
|
switch (conditionType)
|
|
{
|
|
case MrfConditionType.ParameterInsideRange: cond = new MrfConditionParameterInsideRange(r); break;
|
|
case MrfConditionType.ParameterOutsideRange: cond = new MrfConditionParameterOutsideRange(r); break;
|
|
case MrfConditionType.MoveNetworkTrigger: cond = new MrfConditionMoveNetworkTrigger(r); break;
|
|
case MrfConditionType.MoveNetworkFlag: cond = new MrfConditionMoveNetworkFlag(r); break;
|
|
case MrfConditionType.EventOccurred: cond = new MrfConditionEventOccurred(r); break;
|
|
case MrfConditionType.ParameterGreaterThan: cond = new MrfConditionParameterGreaterThan(r); break;
|
|
case MrfConditionType.ParameterGreaterOrEqual: cond = new MrfConditionParameterGreaterOrEqual(r); break;
|
|
case MrfConditionType.ParameterLessThan: cond = new MrfConditionParameterLessThan(r); break;
|
|
case MrfConditionType.ParameterLessOrEqual: cond = new MrfConditionParameterLessOrEqual(r); break;
|
|
case MrfConditionType.TimeGreaterThan: cond = new MrfConditionTimeGreaterThan(r); break;
|
|
case MrfConditionType.TimeLessThan: cond = new MrfConditionTimeLessThan(r); break;
|
|
case MrfConditionType.BoolParameterExists: cond = new MrfConditionBoolParameterExists(r); break;
|
|
case MrfConditionType.BoolParameterEquals: cond = new MrfConditionBoolParameterEquals(r); break;
|
|
default: throw new Exception($"Unknown condition type ({conditionType})");
|
|
}
|
|
|
|
Conditions[i] = cond;
|
|
}
|
|
}
|
|
|
|
if (HasFrameFilter)
|
|
{
|
|
FrameFilterDictionaryName = r.ReadUInt32();
|
|
FrameFilterName = r.ReadUInt32();
|
|
}
|
|
else
|
|
{
|
|
FrameFilterDictionaryName = 0;
|
|
FrameFilterName = 0;
|
|
}
|
|
|
|
if ((r.Position - startReadPosition) != DataSize)
|
|
{ } // not hits
|
|
}
|
|
|
|
public void Write(DataWriter w)
|
|
{
|
|
ConditionCount = (uint)(Conditions?.Length ?? 0);
|
|
|
|
w.Write(Flags);
|
|
w.Write((uint)SynchronizerTagFlags);
|
|
w.Write(Duration);
|
|
w.Write(DurationParameterName);
|
|
w.Write(ProgressParameterName);
|
|
w.Write(TargetStateOffset);
|
|
|
|
if (Conditions != null)
|
|
for (int i = 0; i < Conditions.Length; i++)
|
|
Conditions[i].Write(w);
|
|
|
|
if (HasFrameFilter)
|
|
{
|
|
w.Write(FrameFilterDictionaryName);
|
|
w.Write(FrameFilterName);
|
|
}
|
|
}
|
|
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
XmlTargetStateName = XmlMrf.ReadChildNodeRef(node, "TargetState");
|
|
Duration = Xml.GetChildFloatAttribute(node, "Duration");
|
|
DurationParameterName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "DurationParameterName"));
|
|
ProgressParameterName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "ProgressParameterName"));
|
|
HasDurationParameter = DurationParameterName != 0;
|
|
HasProgressParameter = ProgressParameterName != 0;
|
|
BlendModifier = Xml.GetChildEnumInnerText<MrfWeightModifierType>(node, "BlendModifier");
|
|
|
|
SynchronizerType = Xml.GetChildEnumInnerText<MrfSynchronizerType>(node, "SynchronizerType");
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
{
|
|
SynchronizerTagFlags = Xml.GetChildEnumInnerText<MrfSynchronizerTagFlags>(node, "SynchronizerTagFlags");
|
|
}
|
|
else
|
|
{
|
|
SynchronizerTagFlags = (MrfSynchronizerTagFlags)0xFFFFFFFF;
|
|
}
|
|
|
|
var filter = XmlMrf.GetChildParameterizedAsset(node, "FrameFilter");
|
|
if (filter.Type == MrfValueType.Literal)
|
|
{
|
|
HasFrameFilter = true;
|
|
FrameFilterDictionaryName = filter.DictionaryName;
|
|
FrameFilterName = filter.AssetName;
|
|
}
|
|
else
|
|
{
|
|
HasFrameFilter = false;
|
|
FrameFilterDictionaryName = 0;
|
|
FrameFilterName = 0;
|
|
}
|
|
|
|
UnkFlag2_DetachUpdateObservers = Xml.GetChildBoolAttribute(node, "UnkFlag2_DetachUpdateObservers");
|
|
UnkFlag18 = Xml.GetChildBoolAttribute(node, "UnkFlag18");
|
|
UnkFlag19 = Xml.GetChildBoolAttribute(node, "UnkFlag19");
|
|
|
|
Conditions = null;
|
|
var conditionsNode = node.SelectSingleNode("Conditions");
|
|
if (conditionsNode != null)
|
|
{
|
|
var inodes = conditionsNode.SelectNodes("Item");
|
|
if (inodes?.Count > 0)
|
|
{
|
|
Conditions = new MrfCondition[inodes.Count];
|
|
int i = 0;
|
|
foreach (XmlNode inode in inodes)
|
|
{
|
|
Conditions[i] = XmlMrf.ReadCondition(inode);
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
CalculateDataSize();
|
|
}
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
//MrfXml.ValueTag(sb, indent, "Flags", Flags.ToString());
|
|
MrfXml.WriteNodeRef(sb, indent, "TargetState", TargetState);
|
|
MrfXml.ValueTag(sb, indent, "Duration", FloatUtil.ToString(Duration));
|
|
MrfXml.StringTag(sb, indent, "DurationParameterName", HasDurationParameter ? MrfXml.HashString(DurationParameterName) : null);
|
|
MrfXml.StringTag(sb, indent, "ProgressParameterName", HasProgressParameter ? MrfXml.HashString(ProgressParameterName) : null);
|
|
MrfXml.StringTag(sb, indent, "BlendModifier", BlendModifier.ToString());
|
|
|
|
MrfXml.StringTag(sb, indent, "SynchronizerType", SynchronizerType.ToString());
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
{
|
|
MrfXml.StringTag(sb, indent, "SynchronizerTagFlags", SynchronizerTagFlags.ToString());
|
|
}
|
|
|
|
if (HasFrameFilter)
|
|
{
|
|
MrfXml.ParameterizedAssetTag(sb, indent, "FrameFilter", MrfValueType.Literal, FrameFilterDictionaryName, FrameFilterName, 0);
|
|
}
|
|
else
|
|
{
|
|
MrfXml.SelfClosingTag(sb, indent, "FrameFilter");
|
|
}
|
|
|
|
MrfXml.ValueTag(sb, indent, "UnkFlag2_DetachUpdateObservers", UnkFlag2_DetachUpdateObservers.ToString());
|
|
MrfXml.ValueTag(sb, indent, "UnkFlag18", UnkFlag18.ToString());
|
|
MrfXml.ValueTag(sb, indent, "UnkFlag19", UnkFlag19.ToString());
|
|
|
|
if (Conditions != null)
|
|
{
|
|
int cindent = indent + 1;
|
|
MrfXml.OpenTag(sb, indent, "Conditions");
|
|
foreach (var c in Conditions)
|
|
{
|
|
MrfXml.WriteCondition(sb, cindent, "Item", c);
|
|
}
|
|
MrfXml.CloseTag(sb, indent, "Conditions");
|
|
}
|
|
else
|
|
{
|
|
MrfXml.SelfClosingTag(sb, indent, "Conditions");
|
|
}
|
|
}
|
|
|
|
public uint GetFlagsSubset(int bitOffset, uint mask)
|
|
{
|
|
return (Flags >> bitOffset) & mask;
|
|
}
|
|
|
|
public void SetFlagsSubset(int bitOffset, uint mask, uint value)
|
|
{
|
|
Flags = (Flags & ~(mask << bitOffset)) | ((value & mask) << bitOffset);
|
|
}
|
|
|
|
public void CalculateDataSize()
|
|
{
|
|
uint dataSize = 0x18;
|
|
if (Conditions != null)
|
|
{
|
|
dataSize += (uint)Conditions.Sum(c => c.DataSize);
|
|
}
|
|
|
|
if (HasFrameFilter)
|
|
{
|
|
dataSize += 8;
|
|
}
|
|
|
|
DataSize = dataSize;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"{TargetState?.Name.ToString() ?? TargetStateFileOffset.ToString()} - {FloatUtil.ToString(Duration)} - {Conditions?.Length ?? 0} conditions";
|
|
}
|
|
}
|
|
|
|
public enum MrfConditionType : ushort
|
|
{
|
|
ParameterInsideRange = 0, // condition = Param > MinValue && Param < MaxValue
|
|
ParameterOutsideRange = 1, // condition = Param < MinValue || Param > MaxValue
|
|
MoveNetworkTrigger = 2, // condition = bittest(rage::mvMotionWeb.field_8, BitPosition) != Invert (each bit of field_8 represents a MrfMoveNetworkTrigger)
|
|
MoveNetworkFlag = 3, // condition = bittest(rage::mvMotionWeb.field_C, BitPosition) != Invert (each bit of field_C represents a MrfMoveNetworkFlag)
|
|
EventOccurred = 4, // condition = same behaviour as BoolParamExists but seems to be used with event names only
|
|
ParameterGreaterThan = 5, // condition = Param > Value
|
|
ParameterGreaterOrEqual = 6, // condition = Param >= Value
|
|
ParameterLessThan = 7, // condition = Param < Value
|
|
ParameterLessOrEqual = 8, // condition = Param <= Value
|
|
TimeGreaterThan = 9, // condition = Time > Value (time since tha state started in seconds)
|
|
TimeLessThan = 10, // condition = Time < Value
|
|
BoolParameterExists = 11, // condition = exists(Param) != Invert
|
|
BoolParameterEquals = 12, // condition = Param == Value
|
|
}
|
|
|
|
[TC(typeof(EXP))] public abstract class MrfCondition : IMetaXmlItem
|
|
{
|
|
// rage::mvConditionDef
|
|
|
|
public MrfConditionType Type { get; set; }
|
|
public short Unk2 { get; set; } = 0; // always 0
|
|
|
|
public abstract uint DataSize { get; }
|
|
|
|
public MrfCondition(MrfConditionType type)
|
|
{
|
|
Type = type;
|
|
}
|
|
|
|
public MrfCondition(DataReader r)
|
|
{
|
|
Type = (MrfConditionType)r.ReadUInt16();
|
|
Unk2 = r.ReadInt16();
|
|
}
|
|
|
|
public virtual void Write(DataWriter w)
|
|
{
|
|
w.Write((ushort)Type);
|
|
w.Write(Unk2);
|
|
}
|
|
|
|
public virtual void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
}
|
|
|
|
public virtual void ReadXml(XmlNode node)
|
|
{
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Type.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the condition as a C-like expression. Mainly to include it in the debug DOT graphs.
|
|
/// </summary>
|
|
public abstract string ToExpressionString(MrfFile mrf);
|
|
|
|
public static MrfCondition CreateCondition(MrfConditionType conditionType)
|
|
{
|
|
switch (conditionType)
|
|
{
|
|
case MrfConditionType.ParameterInsideRange: return new MrfConditionParameterInsideRange();
|
|
case MrfConditionType.ParameterOutsideRange: return new MrfConditionParameterOutsideRange();
|
|
case MrfConditionType.MoveNetworkTrigger: return new MrfConditionMoveNetworkTrigger();
|
|
case MrfConditionType.MoveNetworkFlag: return new MrfConditionMoveNetworkFlag();
|
|
case MrfConditionType.EventOccurred: return new MrfConditionEventOccurred();
|
|
case MrfConditionType.ParameterGreaterThan: return new MrfConditionParameterGreaterThan();
|
|
case MrfConditionType.ParameterGreaterOrEqual: return new MrfConditionParameterGreaterOrEqual();
|
|
case MrfConditionType.ParameterLessThan: return new MrfConditionParameterLessThan();
|
|
case MrfConditionType.ParameterLessOrEqual: return new MrfConditionParameterLessOrEqual();
|
|
case MrfConditionType.TimeGreaterThan: return new MrfConditionTimeGreaterThan();
|
|
case MrfConditionType.TimeLessThan: return new MrfConditionTimeLessThan();
|
|
case MrfConditionType.BoolParameterExists: return new MrfConditionBoolParameterExists();
|
|
case MrfConditionType.BoolParameterEquals: return new MrfConditionBoolParameterEquals();
|
|
default: throw new Exception($"Unknown condition type ({conditionType})");
|
|
}
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public abstract class MrfConditionWithParameterAndRangeBase : MrfCondition
|
|
{
|
|
public override uint DataSize => 16;
|
|
public MetaHash ParameterName { get; set; }
|
|
public float MaxValue { get; set; }
|
|
public float MinValue { get; set; }
|
|
|
|
public MrfConditionWithParameterAndRangeBase(MrfConditionType type) : base(type) { }
|
|
public MrfConditionWithParameterAndRangeBase(DataReader r) : base(r)
|
|
{
|
|
ParameterName = r.ReadUInt32();
|
|
MaxValue = r.ReadSingle();
|
|
MinValue = r.ReadSingle();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(ParameterName);
|
|
w.Write(MaxValue);
|
|
w.Write(MinValue);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
ParameterName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "ParameterName"));
|
|
MinValue = Xml.GetChildFloatAttribute(node, "Min");
|
|
MaxValue = Xml.GetChildFloatAttribute(node, "Max");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
MrfXml.StringTag(sb, indent, "ParameterName", MrfXml.HashString(ParameterName));
|
|
MrfXml.ValueTag(sb, indent, "Min", FloatUtil.ToString(MinValue));
|
|
MrfXml.ValueTag(sb, indent, "Max", FloatUtil.ToString(MaxValue));
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString() + $" - {{ {nameof(ParameterName)} = {ParameterName}, {nameof(MaxValue)} = {FloatUtil.ToString(MaxValue)}, {nameof(MinValue)} = {FloatUtil.ToString(MinValue)} }}";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public abstract class MrfConditionWithParameterAndValueBase : MrfCondition
|
|
{
|
|
public override uint DataSize => 12;
|
|
public MetaHash ParameterName { get; set; }
|
|
public float Value { get; set; }
|
|
|
|
public MrfConditionWithParameterAndValueBase(MrfConditionType type) : base(type) { }
|
|
public MrfConditionWithParameterAndValueBase(DataReader r) : base(r)
|
|
{
|
|
ParameterName = r.ReadUInt32();
|
|
Value = r.ReadSingle();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(ParameterName);
|
|
w.Write(Value);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
ParameterName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "ParameterName"));
|
|
Value = Xml.GetChildFloatAttribute(node, "Value");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
MrfXml.StringTag(sb, indent, "ParameterName", MrfXml.HashString(ParameterName));
|
|
MrfXml.ValueTag(sb, indent, "Value", FloatUtil.ToString(Value));
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString() + $" - {{ {nameof(ParameterName)} = {ParameterName}, {nameof(Value)} = {FloatUtil.ToString(Value)} }}";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public abstract class MrfConditionWithValueBase : MrfCondition
|
|
{
|
|
public override uint DataSize => 8;
|
|
public float Value { get; set; }
|
|
|
|
public MrfConditionWithValueBase(MrfConditionType type) : base(type) { }
|
|
public MrfConditionWithValueBase(DataReader r) : base(r)
|
|
{
|
|
Value = r.ReadSingle();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(Value);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
Value = Xml.GetChildFloatAttribute(node, "Value");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
MrfXml.ValueTag(sb, indent, "Value", FloatUtil.ToString(Value));
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString() + $" - {{ {nameof(Value)} = {FloatUtil.ToString(Value)} }}";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public abstract class MrfConditionWithParameterAndBoolValueBase : MrfCondition
|
|
{
|
|
public override uint DataSize => 12;
|
|
public MetaHash ParameterName { get; set; }
|
|
public bool Value { get; set; }
|
|
|
|
public MrfConditionWithParameterAndBoolValueBase(MrfConditionType type) : base(type) { }
|
|
public MrfConditionWithParameterAndBoolValueBase(DataReader r) : base(r)
|
|
{
|
|
ParameterName = r.ReadUInt32();
|
|
Value = r.ReadUInt32() != 0;
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(ParameterName);
|
|
w.Write(Value ? 1 : 0u);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
ParameterName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "ParameterName"));
|
|
Value = Xml.GetChildBoolAttribute(node, "Value");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
MrfXml.StringTag(sb, indent, "ParameterName", MrfXml.HashString(ParameterName));
|
|
MrfXml.ValueTag(sb, indent, "Value", Value.ToString());
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString() + $" - {{ {nameof(ParameterName)} = {ParameterName}, {nameof(Value)} = {Value} }}";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public abstract class MrfConditionBitTestBase : MrfCondition
|
|
{
|
|
public override uint DataSize => 12;
|
|
public int BitPosition { get; set; }
|
|
public bool Invert { get; set; }
|
|
|
|
public MrfConditionBitTestBase(MrfConditionType type) : base(type) { }
|
|
public MrfConditionBitTestBase(DataReader r) : base(r)
|
|
{
|
|
BitPosition = r.ReadInt32();
|
|
Invert = r.ReadUInt32() != 0;
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(BitPosition);
|
|
w.Write(Invert ? 1u : 0u);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
BitPosition = Xml.GetChildIntAttribute(node, "BitPosition");
|
|
Invert = Xml.GetChildBoolAttribute(node, "Invert");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
MrfXml.ValueTag(sb, indent, "BitPosition", BitPosition.ToString());
|
|
MrfXml.ValueTag(sb, indent, "Invert", Invert.ToString());
|
|
}
|
|
|
|
public string FindBitName(MrfFile mrf)
|
|
{
|
|
MetaHash? bitNameHash = null;
|
|
if (mrf != null)
|
|
{
|
|
bitNameHash = Type == MrfConditionType.MoveNetworkTrigger ?
|
|
mrf.FindMoveNetworkTriggerForBit(BitPosition)?.Name :
|
|
mrf.FindMoveNetworkFlagForBit(BitPosition)?.Name;
|
|
}
|
|
return bitNameHash.HasValue ? $"'{bitNameHash.Value}'" : BitPosition.ToString();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString() + $" - {{ {nameof(BitPosition)} = {BitPosition}, {nameof(Invert)} = {Invert} }}";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfConditionParameterInsideRange : MrfConditionWithParameterAndRangeBase
|
|
{
|
|
public MrfConditionParameterInsideRange() : base(MrfConditionType.ParameterInsideRange) { }
|
|
public MrfConditionParameterInsideRange(DataReader r) : base(r) { }
|
|
|
|
public override string ToExpressionString(MrfFile mrf)
|
|
{
|
|
return $"{FloatUtil.ToString(MinValue)} < '{ParameterName}' < {FloatUtil.ToString(MaxValue)}";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public class MrfConditionParameterOutsideRange : MrfConditionWithParameterAndRangeBase
|
|
{
|
|
public MrfConditionParameterOutsideRange() : base(MrfConditionType.ParameterOutsideRange) { }
|
|
public MrfConditionParameterOutsideRange(DataReader r) : base(r) { }
|
|
|
|
public override string ToExpressionString(MrfFile mrf)
|
|
{
|
|
return $"'{ParameterName}' < {FloatUtil.ToString(MinValue)} < {FloatUtil.ToString(MaxValue)} < '{ParameterName}'";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public class MrfConditionMoveNetworkTrigger : MrfConditionBitTestBase
|
|
{
|
|
public MrfConditionMoveNetworkTrigger() : base(MrfConditionType.MoveNetworkTrigger) { }
|
|
public MrfConditionMoveNetworkTrigger(DataReader r) : base(r) { }
|
|
|
|
public override string ToExpressionString(MrfFile mrf)
|
|
{
|
|
return (Invert ? "!" : "") + $"trigger({FindBitName(mrf)})";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public class MrfConditionMoveNetworkFlag : MrfConditionBitTestBase
|
|
{
|
|
public MrfConditionMoveNetworkFlag() : base(MrfConditionType.MoveNetworkFlag) { }
|
|
public MrfConditionMoveNetworkFlag(DataReader r) : base(r) { }
|
|
|
|
public override string ToExpressionString(MrfFile mrf)
|
|
{
|
|
return (Invert ? "!" : "") + $"flag({FindBitName(mrf)})";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public class MrfConditionParameterGreaterThan : MrfConditionWithParameterAndValueBase
|
|
{
|
|
public MrfConditionParameterGreaterThan() : base(MrfConditionType.ParameterGreaterThan) { }
|
|
public MrfConditionParameterGreaterThan(DataReader r) : base(r) { }
|
|
|
|
public override string ToExpressionString(MrfFile mrf)
|
|
{
|
|
return $"'{ParameterName}' > {FloatUtil.ToString(Value)}";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public class MrfConditionParameterGreaterOrEqual : MrfConditionWithParameterAndValueBase
|
|
{
|
|
public MrfConditionParameterGreaterOrEqual() : base(MrfConditionType.ParameterGreaterOrEqual) { }
|
|
public MrfConditionParameterGreaterOrEqual(DataReader r) : base(r) { }
|
|
|
|
public override string ToExpressionString(MrfFile mrf)
|
|
{
|
|
return $"'{ParameterName}' >= {FloatUtil.ToString(Value)}";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public class MrfConditionParameterLessThan : MrfConditionWithParameterAndValueBase
|
|
{
|
|
public MrfConditionParameterLessThan() : base(MrfConditionType.ParameterLessThan) { }
|
|
public MrfConditionParameterLessThan(DataReader r) : base(r) { }
|
|
|
|
public override string ToExpressionString(MrfFile mrf)
|
|
{
|
|
return $"'{ParameterName}' < {FloatUtil.ToString(Value)}";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public class MrfConditionParameterLessOrEqual : MrfConditionWithParameterAndValueBase
|
|
{
|
|
public MrfConditionParameterLessOrEqual() : base(MrfConditionType.ParameterLessOrEqual) { }
|
|
public MrfConditionParameterLessOrEqual(DataReader r) : base(r) { }
|
|
|
|
public override string ToExpressionString(MrfFile mrf)
|
|
{
|
|
return $"'{ParameterName}' <= {FloatUtil.ToString(Value)}";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public class MrfConditionTimeGreaterThan : MrfConditionWithValueBase
|
|
{
|
|
public MrfConditionTimeGreaterThan() : base(MrfConditionType.TimeGreaterThan) { }
|
|
public MrfConditionTimeGreaterThan(DataReader r) : base(r) { }
|
|
|
|
public override string ToExpressionString(MrfFile mrf)
|
|
{
|
|
return $"Time > {FloatUtil.ToString(Value)}";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public class MrfConditionTimeLessThan : MrfConditionWithValueBase
|
|
{
|
|
public MrfConditionTimeLessThan() : base(MrfConditionType.TimeLessThan) { }
|
|
public MrfConditionTimeLessThan(DataReader r) : base(r) { }
|
|
|
|
public override string ToExpressionString(MrfFile mrf)
|
|
{
|
|
return $"Time < {FloatUtil.ToString(Value)}";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public class MrfConditionEventOccurred : MrfConditionWithParameterAndBoolValueBase
|
|
{
|
|
public bool Invert { get => Value; set => Value = value; }
|
|
|
|
public MrfConditionEventOccurred() : base(MrfConditionType.EventOccurred) { }
|
|
public MrfConditionEventOccurred(DataReader r) : base(r) { }
|
|
|
|
public override string ToExpressionString(MrfFile mrf)
|
|
{
|
|
return (Invert ? "!" : "") + $"event('{ParameterName}')";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public class MrfConditionBoolParameterExists : MrfConditionWithParameterAndBoolValueBase
|
|
{
|
|
public bool Invert { get => Value; set => Value = value; }
|
|
|
|
public MrfConditionBoolParameterExists() : base(MrfConditionType.BoolParameterExists) { }
|
|
public MrfConditionBoolParameterExists(DataReader r) : base(r) { }
|
|
|
|
public override string ToExpressionString(MrfFile mrf)
|
|
{
|
|
return (Invert ? "!" : "") + $"exists('{ParameterName}')";
|
|
}
|
|
}
|
|
[TC(typeof(EXP))] public class MrfConditionBoolParameterEquals : MrfConditionWithParameterAndBoolValueBase
|
|
{
|
|
public MrfConditionBoolParameterEquals() : base(MrfConditionType.BoolParameterEquals) { }
|
|
public MrfConditionBoolParameterEquals(DataReader r) : base(r) { }
|
|
|
|
public override string ToExpressionString(MrfFile mrf)
|
|
{
|
|
return $"'{ParameterName}' == {Value}";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before the target node updates, sets the target node parameter to the source network parameter value.
|
|
/// </summary>
|
|
[TC(typeof(EXP))] public class MrfStateInputParameter : IMetaXmlItem
|
|
{
|
|
// rage::mvNodeStateDef::InputParameter
|
|
|
|
public MetaHash SourceParameterName { get; set; }
|
|
public ushort TargetNodeIndex { get; set; }
|
|
public /*MrfNodeParameterId*/ushort TargetNodeParameterId { get; set; }
|
|
public uint TargetNodeParameterExtraArg { get; set; } // some node parameters require an additional argument to be passed (e.g. a name hash)
|
|
|
|
public MrfStateInputParameter() { }
|
|
public MrfStateInputParameter(DataReader r)
|
|
{
|
|
SourceParameterName = r.ReadUInt32();
|
|
TargetNodeIndex = r.ReadUInt16();
|
|
TargetNodeParameterId = r.ReadUInt16();
|
|
TargetNodeParameterExtraArg = r.ReadUInt32();
|
|
}
|
|
|
|
public void Write(DataWriter w)
|
|
{
|
|
w.Write(SourceParameterName);
|
|
w.Write(TargetNodeIndex);
|
|
w.Write(TargetNodeParameterId);
|
|
w.Write(TargetNodeParameterExtraArg);
|
|
}
|
|
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
SourceParameterName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "SourceParameterName"));
|
|
TargetNodeIndex = (ushort)Xml.GetChildUIntAttribute(node, "TargetNodeIndex");
|
|
TargetNodeParameterId = (ushort)Xml.GetChildUIntAttribute(node, "TargetNodeParameterId");
|
|
TargetNodeParameterExtraArg = Xml.GetChildUIntAttribute(node, "TargetNodeParameterExtraArg");
|
|
}
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.StringTag(sb, indent, "SourceParameterName", MrfXml.HashString(SourceParameterName));
|
|
MrfXml.ValueTag(sb, indent, "TargetNodeIndex", TargetNodeIndex.ToString());
|
|
MrfXml.ValueTag(sb, indent, "TargetNodeParameterId", TargetNodeParameterId.ToString());
|
|
MrfXml.ValueTag(sb, indent, "TargetNodeParameterExtraArg", TargetNodeParameterExtraArg.ToString());
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return SourceParameterName.ToString() + " - " + TargetNodeIndex.ToString() + " - " + TargetNodeParameterId.ToString() + " - " + TargetNodeParameterExtraArg.ToString();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a network bool parameter named <see cref="ParameterName"/> to true when the event occurs on the specified node.
|
|
/// </summary>
|
|
[TC(typeof(EXP))] public class MrfStateEvent : IMetaXmlItem
|
|
{
|
|
// rage::mvNodeStateDef::Event
|
|
|
|
public ushort NodeIndex { get; set; }
|
|
public /*MrfNodeEventId*/ushort NodeEventId { get; set; }
|
|
public MetaHash ParameterName { get; set; }
|
|
|
|
public MrfStateEvent() { }
|
|
public MrfStateEvent(DataReader r)
|
|
{
|
|
NodeIndex = r.ReadUInt16();
|
|
NodeEventId = r.ReadUInt16();
|
|
ParameterName = r.ReadUInt32();
|
|
}
|
|
|
|
public void Write(DataWriter w)
|
|
{
|
|
w.Write(NodeIndex);
|
|
w.Write(NodeEventId);
|
|
w.Write(ParameterName);
|
|
}
|
|
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
NodeIndex = (ushort)Xml.GetChildUIntAttribute(node, "NodeIndex");
|
|
NodeEventId = (ushort)Xml.GetChildUIntAttribute(node, "NodeEventId");
|
|
ParameterName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "ParameterName"));
|
|
}
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.ValueTag(sb, indent, "NodeIndex", NodeIndex.ToString());
|
|
MrfXml.ValueTag(sb, indent, "NodeEventId", NodeEventId.ToString());
|
|
MrfXml.StringTag(sb, indent, "ParameterName", MrfXml.HashString(ParameterName));
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return NodeIndex.ToString() + " - " + NodeEventId.ToString() + " - " + ParameterName.ToString();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// After the source node updates, sets the target network parameter to the source node parameter value.
|
|
/// </summary>
|
|
[TC(typeof(EXP))] public class MrfStateOutputParameter : IMetaXmlItem
|
|
{
|
|
// rage::mvNodeStateDef::OutputParameter
|
|
|
|
public MetaHash TargetParameterName { get; set; }
|
|
public ushort SourceNodeIndex { get; set; }
|
|
public /*MrfNodeParameterId*/ushort SourceNodeParameterId { get; set; } // if 0xFFFF, it stores the node itself, so it can be used by NodeProxy
|
|
public uint SourceNodeParameterExtraArg { get; set; }
|
|
|
|
public MrfStateOutputParameter() { }
|
|
public MrfStateOutputParameter(DataReader r)
|
|
{
|
|
TargetParameterName = r.ReadUInt32();
|
|
SourceNodeIndex = r.ReadUInt16();
|
|
SourceNodeParameterId = r.ReadUInt16();
|
|
SourceNodeParameterExtraArg = r.ReadUInt32();
|
|
}
|
|
|
|
public void Write(DataWriter w)
|
|
{
|
|
w.Write(TargetParameterName);
|
|
w.Write(SourceNodeIndex);
|
|
w.Write(SourceNodeParameterId);
|
|
w.Write(SourceNodeParameterExtraArg);
|
|
}
|
|
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
TargetParameterName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "TargetParameterName"));
|
|
SourceNodeIndex = (ushort)Xml.GetChildUIntAttribute(node, "SourceNodeIndex");
|
|
SourceNodeParameterId = (ushort)Xml.GetChildUIntAttribute(node, "SourceNodeParameterId");
|
|
SourceNodeParameterExtraArg = Xml.GetChildUIntAttribute(node, "SourceNodeParameterExtraArg");
|
|
}
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.StringTag(sb, indent, "TargetParameterName", MrfXml.HashString(TargetParameterName));
|
|
MrfXml.ValueTag(sb, indent, "SourceNodeIndex", SourceNodeIndex.ToString());
|
|
MrfXml.ValueTag(sb, indent, "SourceNodeParameterId", SourceNodeParameterId.ToString());
|
|
MrfXml.ValueTag(sb, indent, "SourceNodeParameterExtraArg", SourceNodeParameterExtraArg.ToString());
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return TargetParameterName.ToString() + " - " + SourceNodeIndex.ToString() + " - " + SourceNodeParameterId.ToString() + " - " + SourceNodeParameterExtraArg.ToString();
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfStateRef
|
|
{
|
|
public MetaHash StateName { get; set; }
|
|
public int StateOffset { get; set; } // offset from the start of this field
|
|
public int StateFileOffset { get; set; }
|
|
|
|
public MrfNodeStateBase State { get; set; }
|
|
|
|
public MrfStateRef()
|
|
{
|
|
}
|
|
|
|
public MrfStateRef(DataReader r)
|
|
{
|
|
StateName = r.ReadUInt32();
|
|
StateOffset = r.ReadInt32();
|
|
StateFileOffset = (int)(r.Position + StateOffset - 4);
|
|
}
|
|
|
|
public void Write(DataWriter w)
|
|
{
|
|
w.Write(StateName);
|
|
w.Write(StateOffset);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return StateName.ToString();
|
|
}
|
|
}
|
|
|
|
public enum MrfOperatorType : uint
|
|
{
|
|
Finish = 0, // finish the operation
|
|
PushLiteral = 1, // push a specific value, not used in vanilla MRFs
|
|
PushParameter = 2, // push a network parameter value
|
|
Add = 3, // adds the two values at the top of the stack, not used in vanilla MRFs
|
|
Multiply = 4, // multiplies the two values at the top of the stack
|
|
Remap = 5, // remaps the value at the top of the stack to another range
|
|
}
|
|
|
|
[TC(typeof(EXP))] public abstract class MrfStateOperator : IMetaXmlItem
|
|
{
|
|
// rage::mvNodeStateDef::Operator
|
|
|
|
public MrfOperatorType Type { get; set; } //0, 2, 4, 5
|
|
|
|
public MrfStateOperator(MrfOperatorType type)
|
|
{
|
|
Type = type;
|
|
}
|
|
public MrfStateOperator(DataReader r)
|
|
{
|
|
Type = (MrfOperatorType)r.ReadUInt32();
|
|
}
|
|
|
|
public virtual void Write(DataWriter w)
|
|
{
|
|
w.Write((uint)Type);
|
|
}
|
|
|
|
public virtual void ReadXml(XmlNode node) { }
|
|
public virtual void WriteXml(StringBuilder sb, int indent) { }
|
|
|
|
public override string ToString()
|
|
{
|
|
return Type.ToString();
|
|
}
|
|
|
|
public static MrfStateOperator CreateOperator(MrfOperatorType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case MrfOperatorType.Finish: return new MrfStateOperatorFinish();
|
|
case MrfOperatorType.PushLiteral: return new MrfStateOperatorPushLiteral();
|
|
case MrfOperatorType.PushParameter: return new MrfStateOperatorPushParameter();
|
|
case MrfOperatorType.Add: return new MrfStateOperatorAdd();
|
|
case MrfOperatorType.Multiply: return new MrfStateOperatorMultiply();
|
|
case MrfOperatorType.Remap: return new MrfStateOperatorRemap();
|
|
default: throw new Exception($"Unknown operator type ({type})");
|
|
}
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfStateOperatorFinish : MrfStateOperator
|
|
{
|
|
public uint Unk1_Unused { get; set; }
|
|
|
|
public MrfStateOperatorFinish() : base(MrfOperatorType.Finish) { }
|
|
public MrfStateOperatorFinish(DataReader r) : base(r)
|
|
{
|
|
Unk1_Unused = r.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(Unk1_Unused);
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfStateOperatorPushLiteral : MrfStateOperator
|
|
{
|
|
public float Value { get; set; }
|
|
|
|
public MrfStateOperatorPushLiteral() : base(MrfOperatorType.PushLiteral) { }
|
|
public MrfStateOperatorPushLiteral(DataReader r) : base(r)
|
|
{
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(Value);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
Value = Xml.GetChildFloatAttribute(node, "Value");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.ValueTag(sb, indent, "Value", FloatUtil.ToString(Value));
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Type + " " + FloatUtil.ToString(Value);
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfStateOperatorPushParameter : MrfStateOperator
|
|
{
|
|
public MetaHash ParameterName { get; set; }
|
|
|
|
public MrfStateOperatorPushParameter() : base(MrfOperatorType.PushParameter) { }
|
|
public MrfStateOperatorPushParameter(DataReader r) : base(r)
|
|
{
|
|
ParameterName = r.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(ParameterName);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
ParameterName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "ParameterName"));
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.StringTag(sb, indent, "ParameterName", MrfXml.HashString(ParameterName));
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Type + " '" + ParameterName + "'";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfStateOperatorAdd : MrfStateOperator
|
|
{
|
|
public uint Unk1_Unused { get; set; }
|
|
|
|
public MrfStateOperatorAdd() : base(MrfOperatorType.Add) { }
|
|
public MrfStateOperatorAdd(DataReader r) : base(r)
|
|
{
|
|
Unk1_Unused = r.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(Unk1_Unused);
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfStateOperatorMultiply : MrfStateOperator
|
|
{
|
|
public uint Unk1_Unused { get; set; }
|
|
|
|
public MrfStateOperatorMultiply() : base(MrfOperatorType.Multiply) { }
|
|
public MrfStateOperatorMultiply(DataReader r) : base(r)
|
|
{
|
|
Unk1_Unused = r.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(Unk1_Unused);
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfStateOperatorRemap : MrfStateOperator
|
|
{
|
|
public int DataOffset { get; set; } = 4; // offset from the start of this field to Min field (always 4)
|
|
public float Min { get; set; } // minimum of input range
|
|
public float Max { get; set; } // maximum of input range
|
|
public uint RangeCount { get; set; }
|
|
public int RangesOffset { get; set; } = 4; // offset from the start of this field to Ranges array (always 4)
|
|
|
|
public MrfStateOperatorRemapRange[] Ranges { get; set; } // output ranges to choose from
|
|
|
|
public MrfStateOperatorRemap() : base(MrfOperatorType.Remap) { }
|
|
public MrfStateOperatorRemap(DataReader r) : base(r)
|
|
{
|
|
DataOffset = r.ReadInt32();
|
|
Min = r.ReadSingle();
|
|
Max = r.ReadSingle();
|
|
RangeCount = r.ReadUInt32();
|
|
RangesOffset = r.ReadInt32();
|
|
Ranges = new MrfStateOperatorRemapRange[RangeCount];
|
|
for (int i = 0; i < RangeCount; i++)
|
|
{
|
|
Ranges[i] = new MrfStateOperatorRemapRange(r);
|
|
}
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
RangeCount = (uint)(Ranges?.Length ?? 0);
|
|
|
|
w.Write(DataOffset);
|
|
w.Write(Min);
|
|
w.Write(Max);
|
|
w.Write(RangeCount);
|
|
w.Write(RangesOffset);
|
|
|
|
foreach (var item in Ranges)
|
|
item.Write(w);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
Min = Xml.GetChildFloatAttribute(node, "Min");
|
|
Max = Xml.GetChildFloatAttribute(node, "Max");
|
|
Ranges = XmlMeta.ReadItemArray<MrfStateOperatorRemapRange>(node, "Ranges");
|
|
RangeCount = (uint)(Ranges?.Length ?? 0);
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.ValueTag(sb, indent, "Min", FloatUtil.ToString(Min));
|
|
MrfXml.ValueTag(sb, indent, "Max", FloatUtil.ToString(Max));
|
|
MrfXml.WriteItemArray(sb, Ranges, indent, "Ranges");
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Type + " (" + FloatUtil.ToString(Min) + ".." + FloatUtil.ToString(Max) + ") -> [" + string.Join(",", Ranges.AsEnumerable()) + "]";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfStateOperatorRemapRange : IMetaXmlItem
|
|
{
|
|
public uint Unk1_Unused { get; set; } // always 0, seems unused
|
|
public float Percent { get; set; } // if less than or equal to ((origValue - origMin) / (origMax - origMin)), this range is selected for the remap operation
|
|
public float Length { get; set; } // Length = Max - Min
|
|
public float Min { get; set; }
|
|
|
|
public MrfStateOperatorRemapRange() { }
|
|
public MrfStateOperatorRemapRange(DataReader r)
|
|
{
|
|
Unk1_Unused = r.ReadUInt32();
|
|
Percent = r.ReadSingle();
|
|
Length = r.ReadSingle();
|
|
Min = r.ReadSingle();
|
|
}
|
|
|
|
public void Write(DataWriter w)
|
|
{
|
|
w.Write(Unk1_Unused);
|
|
w.Write(Percent);
|
|
w.Write(Length);
|
|
w.Write(Min);
|
|
}
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.ValueTag(sb, indent, "Percent", FloatUtil.ToString(Percent));
|
|
MrfXml.ValueTag(sb, indent, "Min", FloatUtil.ToString(Min));
|
|
MrfXml.ValueTag(sb, indent, "Length", FloatUtil.ToString(Length));
|
|
}
|
|
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
Percent = Xml.GetChildFloatAttribute(node, "Percent");
|
|
Min = Xml.GetChildFloatAttribute(node, "Min");
|
|
Length = Xml.GetChildFloatAttribute(node, "Length");
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return FloatUtil.ToString(Percent) + " - (" + FloatUtil.ToString(Min) + ".." + FloatUtil.ToString(Min+Length) + ")";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before the node updates, calculates the specified operations and stores the value in a node parameter.
|
|
/// </summary>
|
|
[TC(typeof(EXP))] public class MrfStateOperation : IMetaXmlItem
|
|
{
|
|
// rage::mvNodeStateDef::Operation
|
|
|
|
public ushort NodeIndex { get; set; }
|
|
public /*MrfNodeParameterId*/ushort NodeParameterId { get; set; }
|
|
public ushort StackSize { get; set; } // in bytes, Operators.Length * 8
|
|
public ushort NodeParameterExtraArg { get; set; }
|
|
public MrfStateOperator[] Operators { get; set; }
|
|
|
|
public MrfStateOperation() { }
|
|
public MrfStateOperation(DataReader r)
|
|
{
|
|
NodeIndex = r.ReadUInt16();
|
|
NodeParameterId = r.ReadUInt16();
|
|
StackSize = r.ReadUInt16();
|
|
NodeParameterExtraArg = r.ReadUInt16();
|
|
|
|
var operators = new List<MrfStateOperator>();
|
|
|
|
while (true)
|
|
{
|
|
var startPos = r.Position;
|
|
var opType = (MrfOperatorType)r.ReadUInt32();
|
|
r.Position = startPos;
|
|
|
|
MrfStateOperator op;
|
|
switch (opType)
|
|
{
|
|
case MrfOperatorType.Finish: op = new MrfStateOperatorFinish(r); break;
|
|
case MrfOperatorType.PushLiteral: op = new MrfStateOperatorPushLiteral(r); break;
|
|
case MrfOperatorType.PushParameter: op = new MrfStateOperatorPushParameter(r); break;
|
|
case MrfOperatorType.Add: op = new MrfStateOperatorAdd(r); break;
|
|
case MrfOperatorType.Multiply: op = new MrfStateOperatorMultiply(r); break;
|
|
case MrfOperatorType.Remap: op = new MrfStateOperatorRemap(r); break;
|
|
default: throw new Exception($"Unknown operator type ({opType})");
|
|
}
|
|
|
|
operators.Add(op);
|
|
if (opType == MrfOperatorType.Finish)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
Operators = operators.ToArray();
|
|
|
|
if (StackSize != Operators.Length * 8)
|
|
{ } // no hit
|
|
}
|
|
|
|
public void Write(DataWriter w)
|
|
{
|
|
w.Write(NodeIndex);
|
|
w.Write(NodeParameterId);
|
|
w.Write(StackSize);
|
|
w.Write(NodeParameterExtraArg);
|
|
|
|
foreach (var op in Operators)
|
|
op.Write(w);
|
|
}
|
|
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
NodeIndex = (ushort)Xml.GetChildUIntAttribute(node, "NodeIndex");
|
|
NodeParameterId = (ushort)Xml.GetChildUIntAttribute(node, "NodeParameterId");
|
|
NodeParameterExtraArg = (ushort)Xml.GetChildUIntAttribute(node, "NodeParameterExtraArg");
|
|
Operators = null;
|
|
var operatorsNode = node.SelectSingleNode("Operators");
|
|
if (operatorsNode != null)
|
|
{
|
|
var inodes = operatorsNode.SelectNodes("Item");
|
|
if (inodes?.Count > 0)
|
|
{
|
|
Operators = new MrfStateOperator[inodes.Count];
|
|
int i = 0;
|
|
foreach (XmlNode inode in inodes)
|
|
{
|
|
Operators[i] = XmlMrf.ReadOperator(inode);
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
StackSize = (ushort)((Operators?.Length ?? 0) * 8);
|
|
}
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.ValueTag(sb, indent, "NodeIndex", NodeIndex.ToString());
|
|
MrfXml.ValueTag(sb, indent, "NodeParameterId", NodeParameterId.ToString());
|
|
MrfXml.ValueTag(sb, indent, "NodeParameterExtraArg", NodeParameterExtraArg.ToString());
|
|
int cindent = indent + 1;
|
|
MrfXml.OpenTag(sb, indent, "Operators");
|
|
foreach (var op in Operators)
|
|
{
|
|
MrfXml.WriteOperator(sb, cindent, "Item", op);
|
|
}
|
|
MrfXml.CloseTag(sb, indent, "Operators");
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return NodeIndex.ToString() + " - " + NodeParameterId.ToString() + " - " + StackSize.ToString() + " - " + NodeParameterExtraArg.ToString() + " - " +
|
|
(Operators?.Length ?? 0).ToString() + " operators";
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region mrf node classes
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeStateMachine : MrfNodeStateBase
|
|
{
|
|
// rage__mvNodeStateMachineClass (1)
|
|
|
|
public MrfStateRef[] States { get; set; }
|
|
|
|
public MrfNodeStateMachine() : base(MrfNodeType.StateMachine) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
|
|
if (StateChildCount > 0)
|
|
{
|
|
States = new MrfStateRef[StateChildCount];
|
|
for (int i = 0; i < StateChildCount; i++)
|
|
States[i] = new MrfStateRef(r);
|
|
|
|
}
|
|
|
|
if (TransitionCount > 0)
|
|
{
|
|
if (r.Position != TransitionsFileOffset)
|
|
{ } // no hits
|
|
|
|
Transitions = new MrfStateTransition[TransitionCount];
|
|
for (int i = 0; i < TransitionCount; i++)
|
|
Transitions[i] = new MrfStateTransition(r);
|
|
}
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
StateChildCount = (byte)(States?.Length ?? 0);
|
|
TransitionCount = (byte)(Transitions?.Length ?? 0);
|
|
|
|
base.Write(w);
|
|
|
|
if (States != null)
|
|
foreach (var state in States)
|
|
state.Write(w);
|
|
|
|
if (Transitions != null)
|
|
foreach(var transition in Transitions)
|
|
transition.Write(w);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
|
|
States = null;
|
|
var statesNode = node.SelectSingleNode("States");
|
|
if (statesNode != null)
|
|
{
|
|
var inodes = statesNode.SelectNodes("Item");
|
|
if (inodes?.Count > 0)
|
|
{
|
|
States = new MrfStateRef[inodes.Count];
|
|
int i = 0;
|
|
foreach (XmlNode inode in inodes)
|
|
{
|
|
var s = new MrfStateRef();
|
|
s.State = (MrfNodeStateBase)XmlMrf.ReadNode(inode);
|
|
s.StateName = s.State.Name;
|
|
States[i] = s;
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
StateChildCount = (byte)(States?.Length ?? 0);
|
|
var initialStateName = XmlMrf.ReadChildNodeRef(node, "InitialState");
|
|
InitialNode = States?.FirstOrDefault(s => s.StateName == initialStateName)?.State;
|
|
Transitions = XmlMeta.ReadItemArray<MrfStateTransition>(node, "Transitions");
|
|
TransitionCount = (byte)(Transitions?.Length ?? 0);
|
|
|
|
ResolveXmlTargetStatesInTransitions(States);
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
|
|
MrfXml.WriteNodeRef(sb, indent, "InitialState", InitialNode);
|
|
int cindent = indent + 1;
|
|
MrfXml.OpenTag(sb, indent, "States");
|
|
foreach (var s in States)
|
|
{
|
|
MrfXml.WriteNode(sb, cindent, "Item", s.State);
|
|
}
|
|
MrfXml.CloseTag(sb, indent, "States");
|
|
MrfXml.WriteItemArray(sb, Transitions, indent, "Transitions");
|
|
}
|
|
|
|
public override void ResolveRelativeOffsets(MrfFile mrf)
|
|
{
|
|
base.ResolveRelativeOffsets(mrf);
|
|
|
|
ResolveNodeOffsetsInTransitions(Transitions, mrf);
|
|
ResolveNodeOffsetsInStates(States, mrf);
|
|
}
|
|
|
|
public override void UpdateRelativeOffsets()
|
|
{
|
|
base.UpdateRelativeOffsets();
|
|
|
|
var offset = (int)(FileOffset + 0x20/*sizeof(MrfNodeStateBase)*/);
|
|
offset = UpdateNodeOffsetsInStates(States, offset);
|
|
|
|
offset = UpdateNodeOffsetsInTransitions(Transitions, offset,
|
|
offsetSetToZeroIfNoTransitions: TransitionsOffset == 0); // MrfNodeStateMachine doesn't seem consistent on whether TransitionsOffset
|
|
// should be 0 if there are no transitions, so if it's already zero don't change it
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeTail : MrfNode
|
|
{
|
|
// rage__mvNodeTail (2)
|
|
|
|
public MrfNodeTail() : base(MrfNodeType.Tail) { }
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeInlinedStateMachine : MrfNodeStateBase
|
|
{
|
|
// rage__mvNodeInlinedStateMachine (3)
|
|
|
|
public int FallbackNodeOffset { get; set; }
|
|
public int FallbackNodeFileOffset { get; set; }
|
|
public MrfStateRef[] States { get; set; }
|
|
|
|
public MrfNode FallbackNode { get; set; } // node used when a NodeTail is reached (maybe in some other cases too?). This node is considered a child
|
|
// of the parent NodeState, so FallbackNode and its children (including their NodeIndex) should be
|
|
// included in the parent NodeState.StateChildCount, not in this NodeInlinedStateMachine.StateChildCount
|
|
|
|
public MrfNodeInlinedStateMachine() : base(MrfNodeType.InlinedStateMachine) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
FallbackNodeOffset = r.ReadInt32();
|
|
FallbackNodeFileOffset = (int)(r.Position + FallbackNodeOffset - 4);
|
|
|
|
if (StateChildCount > 0)
|
|
{
|
|
States = new MrfStateRef[StateChildCount];
|
|
for (int i = 0; i < StateChildCount; i++)
|
|
States[i] = new MrfStateRef(r);
|
|
}
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
StateChildCount = (byte)(States?.Length ?? 0);
|
|
|
|
base.Write(w);
|
|
|
|
w.Write(FallbackNodeOffset);
|
|
|
|
if (States != null)
|
|
foreach (var item in States)
|
|
item.Write(w);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
|
|
States = null;
|
|
var statesNode = node.SelectSingleNode("States");
|
|
if (statesNode != null)
|
|
{
|
|
var inodes = statesNode.SelectNodes("Item");
|
|
if (inodes?.Count > 0)
|
|
{
|
|
States = new MrfStateRef[inodes.Count];
|
|
int i = 0;
|
|
foreach (XmlNode inode in inodes)
|
|
{
|
|
var s = new MrfStateRef();
|
|
s.State = (MrfNodeStateBase)XmlMrf.ReadNode(inode);
|
|
s.StateName = s.State.Name;
|
|
States[i] = s;
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
StateChildCount = (byte)(States?.Length ?? 0);
|
|
var initialStateName = XmlMrf.ReadChildNodeRef(node, "InitialState");
|
|
InitialNode = States?.FirstOrDefault(s => s.StateName == initialStateName)?.State;
|
|
FallbackNode = XmlMrf.ReadChildNode(node, "FallbackNode");
|
|
|
|
ResolveXmlTargetStatesInTransitions(States);
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
|
|
MrfXml.WriteNodeRef(sb, indent, "InitialState", InitialNode);
|
|
int cindent = indent + 1;
|
|
MrfXml.OpenTag(sb, indent, "States");
|
|
foreach (var s in States)
|
|
{
|
|
MrfXml.WriteNode(sb, cindent, "Item", s.State);
|
|
}
|
|
MrfXml.CloseTag(sb, indent, "States");
|
|
MrfXml.WriteNode(sb, indent, "FallbackNode", FallbackNode);
|
|
}
|
|
|
|
public override void ResolveRelativeOffsets(MrfFile mrf)
|
|
{
|
|
base.ResolveRelativeOffsets(mrf);
|
|
|
|
var fallbackNode = mrf.FindNodeAtFileOffset(FallbackNodeFileOffset);
|
|
if (fallbackNode == null)
|
|
{ } // no hits
|
|
|
|
FallbackNode = fallbackNode;
|
|
|
|
ResolveNodeOffsetsInStates(States, mrf);
|
|
}
|
|
|
|
public override void UpdateRelativeOffsets()
|
|
{
|
|
base.UpdateRelativeOffsets();
|
|
|
|
var offset = FileOffset + 0x20/*sizeof(MrfNodeStateBase)*/;
|
|
|
|
FallbackNodeFileOffset = FallbackNode.FileOffset;
|
|
FallbackNodeOffset = FallbackNodeFileOffset - offset;
|
|
|
|
offset += 4;
|
|
offset = UpdateNodeOffsetsInStates(States, offset);
|
|
|
|
offset = UpdateNodeOffsetsInTransitions(null, offset, offsetSetToZeroIfNoTransitions: true);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString() + " - " + FallbackNodeOffset.ToString();
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeAnimation : MrfNodeWithFlagsBase
|
|
{
|
|
// rage__mvNode* (4) not used in final game
|
|
// Probably not worth researching further. Seems like the introduction of NodeClip (and rage::crClip), made this node obsolete.
|
|
// Even the function pointer used to lookup the rage::crAnimation when AnimationType==Literal is null, so the only way to get animations is through a parameter.
|
|
|
|
public uint AnimationUnkDataLength { get; set; }
|
|
public byte[] AnimationUnkData { get; set; }
|
|
public MetaHash AnimationName => AnimationUnkData == null ? 0 : BitConverter.ToUInt32(AnimationUnkData, 0);
|
|
public MetaHash AnimationParameterName { get; set; }
|
|
public uint Unk3 { get; set; }
|
|
public uint Unk4 { get; set; }
|
|
public uint Unk5 { get; set; }
|
|
public uint Unk6 { get; set; }
|
|
public uint Unk7 { get; set; }
|
|
|
|
// flags getters and setters
|
|
public MrfValueType AnimationType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(0, 3);
|
|
set => SetFlagsSubset(0, 3, (uint)value);
|
|
}
|
|
|
|
public MrfNodeAnimation() : base(MrfNodeType.Animation) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
switch (AnimationType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
{
|
|
AnimationUnkDataLength = r.ReadUInt32();
|
|
AnimationUnkData = r.ReadBytes((int)AnimationUnkDataLength);
|
|
break;
|
|
}
|
|
case MrfValueType.Parameter:
|
|
AnimationParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
if (((Flags >> 2) & 3) != 0)
|
|
Unk3 = r.ReadUInt32();
|
|
|
|
if (((Flags >> 4) & 3) != 0)
|
|
Unk4 = r.ReadUInt32();
|
|
|
|
if (((Flags >> 6) & 3) != 0)
|
|
Unk5 = r.ReadUInt32();
|
|
|
|
if (((Flags >> 8) & 3) != 0)
|
|
Unk6 = r.ReadUInt32();
|
|
|
|
if (((Flags >> 10) & 3) != 0)
|
|
Unk7 = r.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
switch (AnimationType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
{
|
|
w.Write(AnimationUnkDataLength);
|
|
w.Write(AnimationUnkData);
|
|
break;
|
|
}
|
|
case MrfValueType.Parameter:
|
|
w.Write(AnimationParameterName);
|
|
break;
|
|
}
|
|
|
|
if (((Flags >> 2) & 3) != 0)
|
|
w.Write(Unk3);
|
|
|
|
if (((Flags >> 4) & 3) != 0)
|
|
w.Write(Unk4);
|
|
|
|
if (((Flags >> 6) & 3) != 0)
|
|
w.Write(Unk5);
|
|
|
|
if (((Flags >> 8) & 3) != 0)
|
|
w.Write(Unk6);
|
|
|
|
if (((Flags >> 10) & 3) != 0)
|
|
w.Write(Unk7);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeBlend : MrfNodePairWeightedBase
|
|
{
|
|
// rage__mvNodeBlend (5)
|
|
|
|
public MrfNodeBlend() : base(MrfNodeType.Blend) { }
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeAddSubtract : MrfNodePairWeightedBase
|
|
{
|
|
// rage__mvNodeAddSubtract (6)
|
|
|
|
public MrfNodeAddSubtract() : base(MrfNodeType.AddSubtract) { }
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeFilter : MrfNodeWithChildAndFilterBase
|
|
{
|
|
// rage__mvNodeFilter (7)
|
|
|
|
public MrfNodeFilter() : base(MrfNodeType.Filter) { }
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeMirror : MrfNodeWithChildAndFilterBase
|
|
{
|
|
// rage__mvNodeMirror (8)
|
|
|
|
public MrfNodeMirror() : base(MrfNodeType.Mirror) { }
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeFrame : MrfNodeWithFlagsBase
|
|
{
|
|
// rage__mvNodeFrame (9)
|
|
|
|
public MetaHash FrameParameterName { get; set; }
|
|
public MetaHash Unk3 { get; set; } // unused
|
|
|
|
// flags getters and setters
|
|
public MrfValueType FrameType // only Parameter type is supported
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(0, 3);
|
|
set => SetFlagsSubset(0, 3, (uint)value);
|
|
}
|
|
public MrfValueType Unk3Type
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(4, 3);
|
|
set => SetFlagsSubset(4, 3, (uint)value);
|
|
}
|
|
|
|
public MrfNodeFrame() : base(MrfNodeType.Frame) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
if (FrameType != MrfValueType.None)
|
|
FrameParameterName = r.ReadUInt32();
|
|
|
|
if (Unk3Type != MrfValueType.None)
|
|
Unk3 = r.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
if (FrameType != MrfValueType.None)
|
|
w.Write(FrameParameterName);
|
|
|
|
if (Unk3Type != MrfValueType.None)
|
|
w.Write(Unk3);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
|
|
Unk3 = 0;
|
|
Unk3Type = MrfValueType.None;
|
|
|
|
(FrameType, _, _, FrameParameterName) = XmlMrf.GetChildParameterizedAsset(node, "Frame");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
|
|
MrfXml.ParameterizedAssetTag(sb, indent, "Frame", FrameType, 0, 0, FrameParameterName);
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeIk : MrfNode
|
|
{
|
|
// rage__mvNodeIk (10)
|
|
|
|
public MrfNodeIk() : base(MrfNodeType.Ik) { }
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeBlendN : MrfNodeNBase
|
|
{
|
|
// rage__mvNodeBlendN (13)
|
|
|
|
public MrfNodeBlendN() : base(MrfNodeType.BlendN) { }
|
|
}
|
|
|
|
public enum MrfClipContainerType : uint
|
|
{
|
|
VariableClipSet = 0, // a fwClipSet stored in the move network by the game code (when this clipset is added to the network it enables its fwClipSet.moveNetworkFlags, when removed they are disabled)
|
|
ClipSet = 1, // a fwClipSet
|
|
ClipDictionary = 2, // a .ycd
|
|
Unk3 = 3, // unknown, only ClipContainerName is set when used (only used in minigame_drilling_bag.mrf)
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeClip : MrfNodeWithFlagsBase
|
|
{
|
|
// rage__mvNodeClip (15)
|
|
|
|
public MetaHash ClipParameterName { get; set; }
|
|
public MrfClipContainerType ClipContainerType { get; set; }
|
|
public MetaHash ClipContainerName { get; set; }
|
|
public MetaHash ClipName { get; set; }
|
|
public float Phase { get; set; }
|
|
public MetaHash PhaseParameterName { get; set; }
|
|
public float Rate { get; set; }
|
|
public MetaHash RateParameterName { get; set; }
|
|
public float Delta { get; set; }
|
|
public MetaHash DeltaParameterName { get; set; }
|
|
public bool Looped { get; set; }
|
|
public MetaHash LoopedParameterName { get; set; }
|
|
|
|
// flags getters and setters
|
|
public MrfValueType ClipType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(0, 3);
|
|
set => SetFlagsSubset(0, 3, (uint)value);
|
|
}
|
|
public MrfValueType PhaseType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(2, 3);
|
|
set => SetFlagsSubset(2, 3, (uint)value);
|
|
}
|
|
public MrfValueType RateType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(4, 3);
|
|
set => SetFlagsSubset(4, 3, (uint)value);
|
|
}
|
|
public MrfValueType DeltaType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(6, 3);
|
|
set => SetFlagsSubset(6, 3, (uint)value);
|
|
}
|
|
public MrfValueType LoopedType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(8, 3);
|
|
set => SetFlagsSubset(8, 3, (uint)value);
|
|
}
|
|
public uint UnkFlag10
|
|
{
|
|
get => GetFlagsSubset(10, 3);
|
|
set => SetFlagsSubset(10, 3, value);
|
|
}
|
|
|
|
public MrfNodeClip() : base(MrfNodeType.Clip) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
switch (ClipType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
{
|
|
ClipContainerType = (MrfClipContainerType)r.ReadUInt32();
|
|
ClipContainerName = r.ReadUInt32();
|
|
|
|
if (ClipContainerType != MrfClipContainerType.Unk3)
|
|
ClipName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
case MrfValueType.Parameter:
|
|
ClipParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
switch (PhaseType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
Phase = r.ReadSingle();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
PhaseParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
switch (RateType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
Rate = r.ReadSingle();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
RateParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
switch (DeltaType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
Delta = r.ReadSingle();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
DeltaParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
switch (LoopedType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
Looped = r.ReadUInt32() != 0;
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
LoopedParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
switch (ClipType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
{
|
|
w.Write((uint)ClipContainerType);
|
|
w.Write(ClipContainerName);
|
|
|
|
if (ClipContainerType != MrfClipContainerType.Unk3)
|
|
w.Write(ClipName);
|
|
|
|
break;
|
|
}
|
|
case MrfValueType.Parameter:
|
|
w.Write(ClipParameterName);
|
|
break;
|
|
}
|
|
|
|
switch (PhaseType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(Phase);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(PhaseParameterName);
|
|
break;
|
|
}
|
|
|
|
switch (RateType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(Rate);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(RateParameterName);
|
|
break;
|
|
}
|
|
|
|
switch (DeltaType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(Delta);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(DeltaParameterName);
|
|
break;
|
|
}
|
|
|
|
switch (LoopedType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(Looped ? 0x01000000 : 0u); // bool originally stored as a big-endian uint, game just checks != 0. Here we do it to output the same bytes as the input
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(LoopedParameterName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
|
|
(ClipType, ClipContainerType, ClipContainerName, ClipName, ClipParameterName) = XmlMrf.GetChildParameterizedClip(node, "Clip");
|
|
(PhaseType, Phase, PhaseParameterName) = XmlMrf.GetChildParameterizedFloat(node, "Phase");
|
|
(RateType, Rate, RateParameterName) = XmlMrf.GetChildParameterizedFloat(node, "Rate");
|
|
(DeltaType, Delta, DeltaParameterName) = XmlMrf.GetChildParameterizedFloat(node, "Delta");
|
|
(LoopedType, Looped, LoopedParameterName) = XmlMrf.GetChildParameterizedBool(node, "Looped");
|
|
UnkFlag10 = Xml.GetChildUIntAttribute(node, "UnkFlag10");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
|
|
MrfXml.ParameterizedClipTag(sb, indent, "Clip", ClipType, ClipContainerType, ClipContainerName, ClipName, ClipParameterName);
|
|
MrfXml.ParameterizedFloatTag(sb, indent, "Phase", PhaseType, Phase, PhaseParameterName);
|
|
MrfXml.ParameterizedFloatTag(sb, indent, "Rate", RateType, Rate, RateParameterName);
|
|
MrfXml.ParameterizedFloatTag(sb, indent, "Delta", DeltaType, Delta, DeltaParameterName);
|
|
MrfXml.ParameterizedBoolTag(sb, indent, "Looped", LoopedType, Looped, LoopedParameterName);
|
|
MrfXml.ValueTag(sb, indent, "UnkFlag10", UnkFlag10.ToString());
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodePm : MrfNodeWithFlagsBase
|
|
{
|
|
// rage__mvNode* (17) not used in final game
|
|
// The backing node is rage::crmtNodePm
|
|
// Pm = Parameterized Motion
|
|
// In RDR3 renamed to rage::mvNodeMotion/rage::crmtNodeMotion?
|
|
|
|
// Seems similar to NodeClip but for rage::crpmMotion/.#pm files (added in RDR3, WIP in GTA5/MP3?)
|
|
// In GTA5 the function pointer used to lookup the rage::crpmMotion is null
|
|
|
|
public uint MotionUnkDataLength { get; set; }
|
|
public byte[] MotionUnkData { get; set; }
|
|
public MetaHash MotionName => MotionUnkData == null ? 0 : BitConverter.ToUInt32(MotionUnkData, 0);
|
|
public MetaHash MotionParameterName { get; set; }
|
|
public uint Unk3 { get; set; }
|
|
public uint Unk4 { get; set; }
|
|
public uint Unk5 { get; set; }
|
|
public uint[] Unk6 { get; set; }
|
|
|
|
// flags getters and setters
|
|
public MrfValueType MotionType
|
|
{
|
|
// Literal not supported, the function pointer used to lookup the motion is null
|
|
get => (MrfValueType)GetFlagsSubset(0, 3);
|
|
set => SetFlagsSubset(0, 3, (uint)value);
|
|
}
|
|
|
|
public MrfNodePm() : base(MrfNodeType.Pm) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
switch (MotionType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
{
|
|
MotionUnkDataLength = r.ReadUInt32();
|
|
MotionUnkData = r.ReadBytes((int)MotionUnkDataLength);
|
|
break;
|
|
}
|
|
case MrfValueType.Parameter:
|
|
MotionParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
if (((Flags >> 2) & 3) != 0)
|
|
Unk3 = r.ReadUInt32();
|
|
|
|
if (((Flags >> 4) & 3) != 0)
|
|
Unk4 = r.ReadUInt32();
|
|
|
|
if ((Flags >> 6) != 0)
|
|
Unk5 = r.ReadUInt32();
|
|
|
|
var flags = Flags >> 19;
|
|
var unk6Count = (Flags >> 10) & 0xF;
|
|
|
|
Unk6 = new uint[unk6Count];
|
|
|
|
for (int i = 0; i < unk6Count; i++)
|
|
{
|
|
if ((flags & 3) != 0)
|
|
Unk6[i] = r.ReadUInt32();
|
|
|
|
flags >>= 2;
|
|
}
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
switch (MotionType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
{
|
|
w.Write(MotionUnkDataLength);
|
|
w.Write(MotionUnkData);
|
|
break;
|
|
}
|
|
case MrfValueType.Parameter:
|
|
w.Write(MotionParameterName);
|
|
break;
|
|
}
|
|
|
|
if (((Flags >> 2) & 3) != 0)
|
|
w.Write(Unk3);
|
|
|
|
if (((Flags >> 4) & 3) != 0)
|
|
w.Write(Unk4);
|
|
|
|
if ((Flags >> 6) != 0)
|
|
w.Write(Unk5);
|
|
|
|
var unk6Count = (Flags >> 10) & 0xF;
|
|
|
|
if (unk6Count > 0)
|
|
{
|
|
foreach (var value in Unk6)
|
|
w.Write(value);
|
|
}
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeExtrapolate : MrfNodeWithChildBase
|
|
{
|
|
// rage__mvNodeExtrapolate (18)
|
|
|
|
public float Damping { get; set; }
|
|
public MetaHash DampingParameterName { get; set; }
|
|
|
|
// flags getters and setters
|
|
public MrfValueType DampingType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(0, 3);
|
|
set => SetFlagsSubset(0, 3, (uint)value);
|
|
}
|
|
|
|
public MrfNodeExtrapolate() : base(MrfNodeType.Extrapolate) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
switch (DampingType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
Damping = r.ReadSingle();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
DampingParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
switch (DampingType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(Damping);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(DampingParameterName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
|
|
(DampingType, Damping, DampingParameterName) = XmlMrf.GetChildParameterizedFloat(node, "Damping");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
|
|
MrfXml.ParameterizedFloatTag(sb, indent, "Damping", DampingType, Damping, DampingParameterName);
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeExpression : MrfNodeWithChildBase
|
|
{
|
|
// rage__mvNodeExpression (19)
|
|
|
|
public MetaHash ExpressionDictionaryName { get; set; }
|
|
public MetaHash ExpressionName { get; set; }
|
|
public MetaHash ExpressionParameterName { get; set; }
|
|
public float Weight { get; set; }
|
|
public MetaHash WeightParameterName { get; set; }
|
|
public MrfNodeExpressionVariable[] Variables { get; set; }
|
|
|
|
// flags getters and setters
|
|
public MrfValueType ExpressionType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(0, 3);
|
|
set => SetFlagsSubset(0, 3, (uint)value);
|
|
}
|
|
public MrfValueType WeightType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(2, 3);
|
|
set => SetFlagsSubset(2, 3, (uint)value);
|
|
}
|
|
public uint VariableFlags
|
|
{
|
|
get => GetFlagsSubset(4, 0xFFFFFF);
|
|
set => SetFlagsSubset(4, 0xFFFFFF, value);
|
|
}
|
|
public uint VariableCount
|
|
{
|
|
get => GetFlagsSubset(28, 0xF);
|
|
set => SetFlagsSubset(28, 0xF, value);
|
|
}
|
|
|
|
// VariableFlags accessors by index
|
|
public MrfValueType GetVariableType(int index)
|
|
{
|
|
return (MrfValueType)GetFlagsSubset(4 + 2 * index, 3);
|
|
}
|
|
public void SetVariableType(int index, MrfValueType type)
|
|
{
|
|
SetFlagsSubset(4 + 2 * index, 3, (uint)type);
|
|
}
|
|
|
|
public MrfNodeExpression() : base(MrfNodeType.Expression) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
switch (ExpressionType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
ExpressionDictionaryName = r.ReadUInt32();
|
|
ExpressionName = r.ReadUInt32();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
ExpressionParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
switch (WeightType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
Weight = r.ReadSingle();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
WeightParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
var varCount = VariableCount;
|
|
|
|
if (varCount == 0)
|
|
return;
|
|
|
|
Variables = new MrfNodeExpressionVariable[varCount];
|
|
|
|
for (int i = 0; i < varCount; i++)
|
|
{
|
|
var type = GetVariableType(i);
|
|
var name = r.ReadUInt32();
|
|
float value = 0.0f;
|
|
uint valueParameterName = 0;
|
|
|
|
switch (type)
|
|
{
|
|
case MrfValueType.Literal:
|
|
value = r.ReadSingle();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
valueParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
|
|
Variables[i] = new MrfNodeExpressionVariable(name, value, valueParameterName);
|
|
}
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
switch (ExpressionType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(ExpressionDictionaryName);
|
|
w.Write(ExpressionName);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(ExpressionParameterName);
|
|
break;
|
|
}
|
|
|
|
switch (WeightType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(Weight);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(WeightParameterName);
|
|
break;
|
|
}
|
|
|
|
var varCount = VariableCount;
|
|
|
|
if (varCount == 0)
|
|
return;
|
|
|
|
for (int i = 0; i < varCount; i++)
|
|
{
|
|
var type = GetVariableType(i);
|
|
var variable = Variables[i];
|
|
w.Write(variable.Name);
|
|
|
|
switch (type)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(variable.Value);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(variable.ValueParameterName);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
|
|
(WeightType, Weight, WeightParameterName) = XmlMrf.GetChildParameterizedFloat(node, "Weight");
|
|
(ExpressionType, ExpressionDictionaryName, ExpressionName, ExpressionParameterName) = XmlMrf.GetChildParameterizedAsset(node, "Expression");
|
|
|
|
Variables = null;
|
|
VariableFlags = 0;
|
|
VariableCount = 0;
|
|
var variablesNode = node.SelectSingleNode("Variables");
|
|
if (variablesNode != null)
|
|
{
|
|
var inodes = variablesNode.SelectNodes("Item");
|
|
if (inodes?.Count > 0)
|
|
{
|
|
VariableCount = (uint)inodes.Count;
|
|
Variables = new MrfNodeExpressionVariable[VariableCount];
|
|
int i = 0;
|
|
foreach (XmlNode inode in inodes)
|
|
{
|
|
var name = XmlMeta.GetHash(Xml.GetChildInnerText(inode, "Name"));
|
|
var value = XmlMrf.GetChildParameterizedFloat(inode, "Value");
|
|
Variables[i] = new MrfNodeExpressionVariable(name, value.Value, value.ParameterName);
|
|
SetVariableType(i, value.Type);
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
|
|
MrfXml.ParameterizedFloatTag(sb, indent, "Weight", WeightType, Weight, WeightParameterName);
|
|
MrfXml.ParameterizedAssetTag(sb, indent, "Expression", ExpressionType, ExpressionDictionaryName, ExpressionName, ExpressionParameterName);
|
|
if (Variables != null)
|
|
{
|
|
int cindent = indent + 1;
|
|
int cindent2 = cindent + 1;
|
|
int varIndex = 0;
|
|
MrfXml.OpenTag(sb, indent, "Variables");
|
|
foreach (var v in Variables)
|
|
{
|
|
MrfXml.OpenTag(sb, cindent, "Item");
|
|
MrfXml.StringTag(sb, cindent2, "Name", MrfXml.HashString(v.Name));
|
|
MrfXml.ParameterizedFloatTag(sb, cindent2, "Value", GetVariableType(varIndex), v.Value, v.ValueParameterName);
|
|
MrfXml.CloseTag(sb, cindent, "Item");
|
|
varIndex++;
|
|
}
|
|
MrfXml.CloseTag(sb, indent, "Variables");
|
|
}
|
|
else
|
|
{
|
|
MrfXml.SelfClosingTag(sb, indent, "Variables");
|
|
}
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public struct MrfNodeExpressionVariable
|
|
{
|
|
public MetaHash Name { get; set; }
|
|
public float Value { get; set; } // used if type == Literal
|
|
public MetaHash ValueParameterName { get; set; } // used if type == Parameter
|
|
|
|
public MrfNodeExpressionVariable(MetaHash name, float value, MetaHash valueParameterName)
|
|
{
|
|
Name = name;
|
|
Value = value;
|
|
ValueParameterName = valueParameterName;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Name.ToString() + " - " + FloatUtil.ToString(Value) + " | " + ValueParameterName.ToString();
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeCapture : MrfNodeWithChildBase
|
|
{
|
|
// rage::mvNodeCaptureDef (20)
|
|
|
|
public MetaHash FrameParameterName { get; set; }
|
|
public MetaHash Unk3 { get; set; } // unused
|
|
|
|
// flags getters and setters
|
|
public MrfValueType FrameType // only Parameter type is supported
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(0, 3);
|
|
set => SetFlagsSubset(0, 3, (uint)value);
|
|
}
|
|
public MrfValueType Unk3Type
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(4, 3);
|
|
set => SetFlagsSubset(4, 3, (uint)value);
|
|
}
|
|
|
|
public MrfNodeCapture() : base(MrfNodeType.Capture) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
if (FrameType != MrfValueType.None)
|
|
FrameParameterName = r.ReadUInt32();
|
|
|
|
if (Unk3Type != MrfValueType.None)
|
|
Unk3 = r.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
if (FrameType != MrfValueType.None)
|
|
w.Write(FrameParameterName);
|
|
|
|
if (Unk3Type != MrfValueType.None)
|
|
w.Write(Unk3);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
|
|
Unk3 = 0;
|
|
Unk3Type = MrfValueType.None;
|
|
|
|
(FrameType, _, _, FrameParameterName) = XmlMrf.GetChildParameterizedAsset(node, "Frame");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
|
|
MrfXml.ParameterizedAssetTag(sb, indent, "Frame", FrameType, 0, 0, FrameParameterName);
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeProxy : MrfNode
|
|
{
|
|
// rage__mvNodeProxy (21)
|
|
|
|
public MetaHash NodeParameterName { get; set; } // lookups a rage::crmtObserver parameter, then gets the observed node
|
|
|
|
public MrfNodeProxy() : base(MrfNodeType.Proxy) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
NodeParameterName = r.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(NodeParameterName);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
|
|
NodeParameterName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "NodeParameterName"));
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
|
|
MrfXml.StringTag(sb, indent, "NodeParameterName", MrfXml.HashString(NodeParameterName));
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString() + " - " + NodeParameterName.ToString();
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeAddN : MrfNodeNBase
|
|
{
|
|
// rage__mvNodeAddN (22)
|
|
|
|
public MrfNodeAddN() : base(MrfNodeType.AddN) { }
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeIdentity : MrfNode
|
|
{
|
|
// rage__mvNodeIdentity (23)
|
|
|
|
public MrfNodeIdentity() : base(MrfNodeType.Identity) { }
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeMerge : MrfNodePairBase
|
|
{
|
|
// rage::mvNodeMergeDef (24)
|
|
|
|
public MrfSynchronizerTagFlags SynchronizerTagFlags { get; set; }
|
|
public MetaHash FrameFilterDictionaryName { get; set; }
|
|
public MetaHash FrameFilterName { get; set; }
|
|
public MetaHash FrameFilterParameterName { get; set; }
|
|
|
|
// flags getters and setters
|
|
public MrfValueType FrameFilterType
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(0, 3);
|
|
set => SetFlagsSubset(0, 3, (uint)value);
|
|
}
|
|
public MrfInfluenceOverride Child0InfluenceOverride
|
|
{
|
|
get => (MrfInfluenceOverride)GetFlagsSubset(2, 3);
|
|
set => SetFlagsSubset(2, 3, (uint)value);
|
|
}
|
|
public MrfInfluenceOverride Child1InfluenceOverride
|
|
{
|
|
get => (MrfInfluenceOverride)GetFlagsSubset(4, 3);
|
|
set => SetFlagsSubset(4, 3, (uint)value);
|
|
}
|
|
public bool UnkFlag6 // Transitional? RDR3's rage::mvNodeMergeDef::GetTransitionalFlagFrom(uint) reads these bits
|
|
{ // always 0
|
|
get => GetFlagsSubset(6, 1) != 0;
|
|
set => SetFlagsSubset(6, 1, value ? 1 : 0u);
|
|
}
|
|
public uint UnkFlag7 // Immutable? RDR3's rage::mvNodeMergeDef::GetImmutableFlagFrom(uint) reads these bits
|
|
{ // 0 or 2
|
|
get => GetFlagsSubset(7, 3);
|
|
set => SetFlagsSubset(7, 3, value);
|
|
}
|
|
public MrfSynchronizerType SynchronizerType
|
|
{
|
|
get => (MrfSynchronizerType)GetFlagsSubset(19, 3);
|
|
set => SetFlagsSubset(19, 3, (uint)value);
|
|
}
|
|
public uint UnkFlag21 // OutputParameterRuleSet? RDR3's rage::mvNodeMergeDef::GetOutputParameterRuleSetFrom(uint) reads these bits
|
|
{ // always 0
|
|
get => GetFlagsSubset(21, 3);
|
|
set => SetFlagsSubset(21, 3, value);
|
|
}
|
|
|
|
public MrfNodeMerge() : base(MrfNodeType.Merge) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
SynchronizerTagFlags = (MrfSynchronizerTagFlags)r.ReadUInt32();
|
|
|
|
switch (FrameFilterType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
FrameFilterDictionaryName = r.ReadUInt32();
|
|
FrameFilterName = r.ReadUInt32();
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
FrameFilterParameterName = r.ReadUInt32();
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
w.Write((uint)SynchronizerTagFlags);
|
|
|
|
switch (FrameFilterType)
|
|
{
|
|
case MrfValueType.Literal:
|
|
w.Write(FrameFilterDictionaryName);
|
|
w.Write(FrameFilterName);
|
|
break;
|
|
case MrfValueType.Parameter:
|
|
w.Write(FrameFilterParameterName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
|
|
Child0InfluenceOverride = Xml.GetChildEnumInnerText<MrfInfluenceOverride>(node, "Child0InfluenceOverride");
|
|
Child1InfluenceOverride = Xml.GetChildEnumInnerText<MrfInfluenceOverride>(node, "Child1InfluenceOverride");
|
|
(FrameFilterType, FrameFilterDictionaryName, FrameFilterName, FrameFilterParameterName) = XmlMrf.GetChildParameterizedAsset(node, "FrameFilter");
|
|
SynchronizerType = Xml.GetChildEnumInnerText<MrfSynchronizerType>(node, "SynchronizerType");
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
{
|
|
SynchronizerTagFlags = Xml.GetChildEnumInnerText<MrfSynchronizerTagFlags>(node, "SynchronizerTagFlags");
|
|
}
|
|
UnkFlag6 = Xml.GetChildBoolAttribute(node, "UnkFlag6");
|
|
UnkFlag7 = Xml.GetChildUIntAttribute(node, "UnkFlag7");
|
|
UnkFlag21 = Xml.GetChildUIntAttribute(node, "UnkFlag21");
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
|
|
MrfXml.StringTag(sb, indent, "Child0InfluenceOverride", Child0InfluenceOverride.ToString());
|
|
MrfXml.StringTag(sb, indent, "Child1InfluenceOverride", Child1InfluenceOverride.ToString());
|
|
MrfXml.ParameterizedAssetTag(sb, indent, "FrameFilter", FrameFilterType, FrameFilterDictionaryName, FrameFilterName, FrameFilterParameterName);
|
|
MrfXml.StringTag(sb, indent, "SynchronizerType", SynchronizerType.ToString());
|
|
if (SynchronizerType == MrfSynchronizerType.Tag)
|
|
{
|
|
MrfXml.StringTag(sb, indent, "SynchronizerTagFlags", SynchronizerTagFlags.ToString());
|
|
}
|
|
MrfXml.ValueTag(sb, indent, "UnkFlag6", UnkFlag6.ToString());
|
|
MrfXml.ValueTag(sb, indent, "UnkFlag7", UnkFlag7.ToString());
|
|
MrfXml.ValueTag(sb, indent, "UnkFlag21", UnkFlag21.ToString());
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodePose : MrfNodeWithFlagsBase
|
|
{
|
|
// rage__mvNodePose (25)
|
|
|
|
public uint Unk1 { get; set; } // unused, with type==Literal always 0x01000000, probably a bool for the Pose_IsNormalized parameter hardcoded to true in the final game
|
|
|
|
// flags getters and setters
|
|
public MrfValueType Unk1Type
|
|
{
|
|
get => (MrfValueType)GetFlagsSubset(0, 3);
|
|
set => SetFlagsSubset(0, 3, (uint)value);
|
|
}
|
|
|
|
public MrfNodePose() : base(MrfNodeType.Pose) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
if (Unk1Type != MrfValueType.None)
|
|
Unk1 = r.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
if (Unk1Type != MrfValueType.None)
|
|
w.Write(Unk1);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
Unk1Type = MrfValueType.Literal; // for roundtripness
|
|
Unk1 = 0x01000000;
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeMergeN : MrfNodeNBase
|
|
{
|
|
// rage__mvNodeMergeN (26)
|
|
|
|
public MrfNodeMergeN() : base(MrfNodeType.MergeN) { }
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeState : MrfNodeStateBase
|
|
{
|
|
// rage__mvNodeState (27)
|
|
|
|
public int InputParametersOffset { get; set; }
|
|
public int InputParametersFileOffset { get; set; }
|
|
public uint InputParameterCount { get; set; }
|
|
public int EventsOffset { get; set; }
|
|
public int EventsFileOffset { get; set; }
|
|
public uint EventCount { get; set; }
|
|
public int OutputParametersOffset { get; set; }
|
|
public int OutputParametersFileOffset { get; set; }
|
|
public uint OutputParameterCount { get; set; }
|
|
public int OperationsOffset { get; set; }
|
|
public int OperationsFileOffset { get; set; }
|
|
public uint OperationCount { get; set; }
|
|
|
|
public MrfStateInputParameter[] InputParameters { get; set; }
|
|
public MrfStateEvent[] Events { get; set; }
|
|
public MrfStateOutputParameter[] OutputParameters { get; set; }
|
|
public MrfStateOperation[] Operations { get; set; }
|
|
|
|
public MrfNodeState() : base(MrfNodeType.State) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
InputParametersOffset = r.ReadInt32();
|
|
InputParametersFileOffset = (int)(r.Position + InputParametersOffset - 4);
|
|
InputParameterCount = r.ReadUInt32();
|
|
EventsOffset = r.ReadInt32();
|
|
EventsFileOffset = (int)(r.Position + EventsOffset - 4);
|
|
EventCount = r.ReadUInt32();
|
|
OutputParametersOffset = r.ReadInt32();
|
|
OutputParametersFileOffset = (int)(r.Position + OutputParametersOffset - 4);
|
|
OutputParameterCount = r.ReadUInt32();
|
|
OperationsOffset = r.ReadInt32();
|
|
OperationsFileOffset = (int)(r.Position + OperationsOffset - 4);
|
|
OperationCount = r.ReadUInt32();
|
|
|
|
|
|
if (TransitionCount > 0)
|
|
{
|
|
if (r.Position != TransitionsFileOffset)
|
|
{ } // no hits
|
|
|
|
Transitions = new MrfStateTransition[TransitionCount];
|
|
for (int i = 0; i < TransitionCount; i++)
|
|
Transitions[i] = new MrfStateTransition(r);
|
|
}
|
|
|
|
if (InputParameterCount > 0)
|
|
{
|
|
if (r.Position != InputParametersFileOffset)
|
|
{ } // no hits
|
|
|
|
InputParameters = new MrfStateInputParameter[InputParameterCount];
|
|
for (int i = 0; i < InputParameterCount; i++)
|
|
InputParameters[i] = new MrfStateInputParameter(r);
|
|
}
|
|
|
|
if (EventCount > 0)
|
|
{
|
|
if (r.Position != EventsFileOffset)
|
|
{ } // no hits
|
|
|
|
Events = new MrfStateEvent[EventCount];
|
|
for (int i = 0; i < EventCount; i++)
|
|
Events[i] = new MrfStateEvent(r);
|
|
}
|
|
|
|
if (OutputParameterCount > 0)
|
|
{
|
|
if (r.Position != OutputParametersFileOffset)
|
|
{ } // no hits
|
|
|
|
OutputParameters = new MrfStateOutputParameter[OutputParameterCount];
|
|
for (int i = 0; i < OutputParameterCount; i++)
|
|
OutputParameters[i] = new MrfStateOutputParameter(r);
|
|
}
|
|
|
|
if (OperationCount > 0)
|
|
{
|
|
if (r.Position != OperationsFileOffset)
|
|
{ } // no hits
|
|
|
|
Operations = new MrfStateOperation[OperationCount];
|
|
for (int i = 0; i < OperationCount; i++)
|
|
Operations[i] = new MrfStateOperation(r);
|
|
}
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
TransitionCount = (byte)(Transitions?.Length ?? 0);
|
|
InputParameterCount = (uint)(InputParameters?.Length ?? 0);
|
|
EventCount = (uint)(Events?.Length ?? 0);
|
|
OutputParameterCount = (uint)(OutputParameters?.Length ?? 0);
|
|
OperationCount = (uint)(Operations?.Length ?? 0);
|
|
|
|
base.Write(w);
|
|
|
|
w.Write(InputParametersOffset);
|
|
w.Write(InputParameterCount);
|
|
w.Write(EventsOffset);
|
|
w.Write(EventCount);
|
|
w.Write(OutputParametersOffset);
|
|
w.Write(OutputParameterCount);
|
|
w.Write(OperationsOffset);
|
|
w.Write(OperationCount);
|
|
|
|
if (Transitions != null)
|
|
foreach (var transition in Transitions)
|
|
transition.Write(w);
|
|
|
|
if (InputParameters != null)
|
|
foreach (var item in InputParameters)
|
|
item.Write(w);
|
|
|
|
if (Events != null)
|
|
foreach (var item in Events)
|
|
item.Write(w);
|
|
|
|
if (OutputParameters != null)
|
|
foreach (var item in OutputParameters)
|
|
item.Write(w);
|
|
|
|
if (Operations != null)
|
|
foreach (var item in Operations)
|
|
item.Write(w);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
|
|
InitialNode = XmlMrf.ReadChildNode(node, "InitialNode");
|
|
Transitions = XmlMeta.ReadItemArray<MrfStateTransition>(node, "Transitions");
|
|
InputParameters = XmlMeta.ReadItemArray<MrfStateInputParameter>(node, "InputParameters");
|
|
OutputParameters = XmlMeta.ReadItemArray<MrfStateOutputParameter>(node, "OutputParameters");
|
|
Events = XmlMeta.ReadItemArray<MrfStateEvent>(node, "Events");
|
|
Operations = XmlMeta.ReadItemArray<MrfStateOperation>(node, "Operations");
|
|
TransitionCount = (byte)(Transitions?.Length ?? 0);
|
|
InputParameterCount = (byte)(InputParameters?.Length ?? 0);
|
|
OutputParameterCount = (byte)(InputParameters?.Length ?? 0);
|
|
EventCount = (byte)(InputParameters?.Length ?? 0);
|
|
OperationCount = (byte)(Operations?.Length ?? 0);
|
|
StateChildCount = (byte)GetChildren(excludeTailNodes: true).Count;
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
MrfXml.WriteNode(sb, indent, "InitialNode", InitialNode);
|
|
MrfXml.WriteItemArray(sb, Transitions, indent, "Transitions");
|
|
MrfXml.WriteItemArray(sb, InputParameters, indent, "InputParameters");
|
|
MrfXml.WriteItemArray(sb, OutputParameters, indent, "OutputParameters");
|
|
MrfXml.WriteItemArray(sb, Events, indent, "Events");
|
|
MrfXml.WriteItemArray(sb, Operations, indent, "Operations");
|
|
}
|
|
|
|
public override void ResolveRelativeOffsets(MrfFile mrf)
|
|
{
|
|
base.ResolveRelativeOffsets(mrf);
|
|
|
|
ResolveNodeOffsetsInTransitions(Transitions, mrf);
|
|
}
|
|
|
|
public override void UpdateRelativeOffsets()
|
|
{
|
|
base.UpdateRelativeOffsets();
|
|
|
|
var offset = FileOffset + 0x20/*sizeof(MrfNodeStateBase)*/ + 8*4/*all offsets/counts*/;
|
|
|
|
offset = UpdateNodeOffsetsInTransitions(Transitions, offset, offsetSetToZeroIfNoTransitions: false);
|
|
|
|
InputParametersFileOffset = offset;
|
|
InputParametersOffset = InputParametersFileOffset - (FileOffset + 0x20 + 0);
|
|
offset += (int)InputParameterCount * 0xC;
|
|
|
|
EventsFileOffset = offset;
|
|
EventsOffset = EventsFileOffset - (FileOffset + 0x20 + 8);
|
|
offset += (int)EventCount * 0x8;
|
|
|
|
OutputParametersFileOffset = offset;
|
|
OutputParametersOffset = OutputParametersFileOffset - (FileOffset + 0x20 + 0x10);
|
|
offset += (int)OutputParameterCount * 0xC;
|
|
|
|
OperationsFileOffset = offset;
|
|
OperationsOffset = OperationsFileOffset - (FileOffset + 0x20 + 0x18);
|
|
}
|
|
|
|
public List<MrfNode> GetChildren(bool excludeTailNodes)
|
|
{
|
|
var result = new List<MrfNode>();
|
|
if (InitialNode == null) return result;
|
|
|
|
var q = new Queue<MrfNode>();
|
|
q.Enqueue(InitialNode);
|
|
while (q.Count > 0)
|
|
{
|
|
var n = q.Dequeue();
|
|
if (!excludeTailNodes || !(n is MrfNodeTail))
|
|
{
|
|
result.Add(n);
|
|
}
|
|
|
|
if (n is MrfNodeWithChildBase nc)
|
|
{
|
|
q.Enqueue(nc.Child);
|
|
}
|
|
else if (n is MrfNodePairBase np)
|
|
{
|
|
q.Enqueue(np.Child0);
|
|
q.Enqueue(np.Child1);
|
|
}
|
|
else if (n is MrfNodeNBase nn)
|
|
{
|
|
foreach (var c in nn.Children)
|
|
{
|
|
q.Enqueue(c);
|
|
}
|
|
}
|
|
else if (n is MrfNodeInlinedStateMachine ism)
|
|
{
|
|
q.Enqueue(ism.FallbackNode);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeInvalid : MrfNode
|
|
{
|
|
// rage__mvNodeInvalid (28)
|
|
|
|
public MrfNodeInvalid() : base(MrfNodeType.Invalid) { }
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeJointLimit : MrfNodeWithChildAndFilterBase
|
|
{
|
|
// rage__mvNodeJointLimit (29)
|
|
|
|
public MrfNodeJointLimit() : base(MrfNodeType.JointLimit) { }
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeSubNetwork : MrfNode
|
|
{
|
|
// rage__mvNodeSubNetworkClass (30)
|
|
|
|
public MetaHash SubNetworkParameterName { get; set; } // parameter of type rage::mvSubNetwork to lookup
|
|
|
|
public MrfNodeSubNetwork() : base(MrfNodeType.SubNetwork) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
SubNetworkParameterName = r.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
w.Write(SubNetworkParameterName);
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
SubNetworkParameterName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "SubNetworkParameterName"));
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
MrfXml.StringTag(sb, indent, "SubNetworkParameterName", MrfXml.HashString(SubNetworkParameterName));
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class MrfNodeReference : MrfNode
|
|
{
|
|
// rage__mvNodeReference (31)
|
|
|
|
// Unused in the final game but from testing, seems to work fine initially but when it finishes it crashes calling a pure virtual function rage::crmtNode::GetNodeTypeInfo
|
|
// Maybe some kind of double-free/use-after-free bug, not sure if a R* bug or an issue with the generated MRF file.
|
|
|
|
public MetaHash MoveNetworkName { get; set; } // .mrf file to lookup. Its RootState must be MrfNodeState, not MrfNodeStateMachine or it will crash
|
|
public int InitialParametersOffset { get; set; }
|
|
public int InitialParametersFileOffset { get; set; }
|
|
public uint InitialParameterCount { get; set; }
|
|
public MrfNodeReferenceInitialParameter[] InitialParameters { get; set; } // parameters added when the new network is created
|
|
public uint ImportedParameterCount { get; set; }
|
|
public MrfNodeReferenceNamePair[] ImportedParameters { get; set; } // each update these parameters are copied from the parent network to the new network
|
|
public uint MoveNetworkFlagCount { get; set; }
|
|
public MrfNodeReferenceNamePair[] MoveNetworkFlags { get; set; } // each update copies flag 'Name' state in the parent network to another bit in the *parent* network flags, the new bit position is defined by 'NewName' in the MrfFile.MoveNetworkFlags of the new network
|
|
public uint MoveNetworkTriggerCount { get; set; }
|
|
public MrfNodeReferenceNamePair[] MoveNetworkTriggers { get; set; } // same as with the flags
|
|
|
|
public MrfNodeReference() : base(MrfNodeType.Reference) { }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
base.Read(r);
|
|
|
|
MoveNetworkName = r.ReadUInt32();
|
|
InitialParametersOffset = r.ReadInt32();
|
|
InitialParametersFileOffset = (int)(r.Position + InitialParametersOffset - 4);
|
|
InitialParameterCount = r.ReadUInt32();
|
|
ImportedParameterCount = r.ReadUInt32();
|
|
MoveNetworkFlagCount = r.ReadUInt32();
|
|
MoveNetworkTriggerCount = r.ReadUInt32();
|
|
|
|
if (ImportedParameterCount > 0)
|
|
{
|
|
ImportedParameters = new MrfNodeReferenceNamePair[ImportedParameterCount];
|
|
|
|
for (int i = 0; i < ImportedParameterCount; i++)
|
|
{
|
|
var name = r.ReadUInt32();
|
|
var newName = r.ReadUInt32();
|
|
ImportedParameters[i] = new MrfNodeReferenceNamePair(name, newName);
|
|
}
|
|
}
|
|
|
|
if (MoveNetworkFlagCount > 0)
|
|
{
|
|
MoveNetworkFlags = new MrfNodeReferenceNamePair[MoveNetworkFlagCount];
|
|
|
|
for (int i = 0; i < MoveNetworkFlagCount; i++)
|
|
{
|
|
var name = r.ReadUInt32();
|
|
var newName = r.ReadUInt32();
|
|
MoveNetworkFlags[i] = new MrfNodeReferenceNamePair(name, newName);
|
|
}
|
|
}
|
|
|
|
if (MoveNetworkTriggerCount > 0)
|
|
{
|
|
MoveNetworkTriggers = new MrfNodeReferenceNamePair[MoveNetworkTriggerCount];
|
|
|
|
for (int i = 0; i < MoveNetworkTriggerCount; i++)
|
|
{
|
|
var name = r.ReadUInt32();
|
|
var newName = r.ReadUInt32();
|
|
MoveNetworkTriggers[i] = new MrfNodeReferenceNamePair(name, newName);
|
|
}
|
|
}
|
|
|
|
if (InitialParameterCount > 0)
|
|
{
|
|
if (r.Position != InitialParametersFileOffset)
|
|
{ }
|
|
|
|
InitialParameters = new MrfNodeReferenceInitialParameter[InitialParameterCount];
|
|
|
|
for (int i = 0; i < InitialParameterCount; i++)
|
|
{
|
|
var type = r.ReadUInt32();
|
|
var name = r.ReadUInt32();
|
|
var data = r.ReadInt32();
|
|
InitialParameters[i] = new MrfNodeReferenceInitialParameter(type, name, data);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Write(DataWriter w)
|
|
{
|
|
base.Write(w);
|
|
|
|
InitialParameterCount = (uint)(InitialParameters?.Length ?? 0);
|
|
ImportedParameterCount = (uint)(ImportedParameters?.Length ?? 0);
|
|
MoveNetworkFlagCount = (uint)(MoveNetworkFlags?.Length ?? 0);
|
|
MoveNetworkTriggerCount = (uint)(MoveNetworkTriggers?.Length ?? 0);
|
|
|
|
w.Write(MoveNetworkName);
|
|
w.Write(InitialParametersOffset);
|
|
w.Write(InitialParameterCount);
|
|
w.Write(ImportedParameterCount);
|
|
w.Write(MoveNetworkFlagCount);
|
|
w.Write(MoveNetworkTriggerCount);
|
|
|
|
if (ImportedParameterCount > 0)
|
|
{
|
|
foreach (var entry in ImportedParameters)
|
|
{
|
|
w.Write(entry.Name);
|
|
w.Write(entry.NewName);
|
|
}
|
|
}
|
|
|
|
if (MoveNetworkFlagCount > 0)
|
|
{
|
|
foreach (var entry in MoveNetworkFlags)
|
|
{
|
|
w.Write(entry.Name);
|
|
w.Write(entry.NewName);
|
|
}
|
|
}
|
|
|
|
if (MoveNetworkTriggerCount > 0)
|
|
{
|
|
foreach (var entry in MoveNetworkTriggers)
|
|
{
|
|
w.Write(entry.Name);
|
|
w.Write(entry.NewName);
|
|
}
|
|
}
|
|
|
|
if (InitialParameterCount > 0)
|
|
{
|
|
// FIXME: Data when used as offset is not updated and not sure where what it would point to should be written
|
|
foreach (var entry in InitialParameters)
|
|
{
|
|
w.Write(entry.Type);
|
|
w.Write(entry.Name);
|
|
w.Write(entry.Data);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
base.ReadXml(node);
|
|
|
|
MoveNetworkName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "MoveNetworkName"));
|
|
ImportedParameters = XmlMeta.ReadItemArray<MrfNodeReferenceNamePair>(node, "ImportedParameters");
|
|
MoveNetworkFlags = XmlMeta.ReadItemArray<MrfNodeReferenceNamePair>(node, "MoveNetworkFlags");
|
|
MoveNetworkTriggers = XmlMeta.ReadItemArray<MrfNodeReferenceNamePair>(node, "MoveNetworkTriggers");
|
|
InitialParameters = XmlMeta.ReadItemArray<MrfNodeReferenceInitialParameter>(node, "InitialParameters");
|
|
ImportedParameterCount = (uint)(ImportedParameters?.Length ?? 0);
|
|
MoveNetworkFlagCount = (uint)(MoveNetworkFlags?.Length ?? 0);
|
|
MoveNetworkTriggerCount = (uint)(MoveNetworkTriggers?.Length ?? 0);
|
|
InitialParameterCount = (uint)(InitialParameters?.Length ?? 0);
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
base.WriteXml(sb, indent);
|
|
|
|
MrfXml.StringTag(sb, indent, "MoveNetworkName", MrfXml.HashString(MoveNetworkName));
|
|
MrfXml.WriteItemArray(sb, ImportedParameters, indent, "ImportedParameters");
|
|
MrfXml.WriteItemArray(sb, MoveNetworkFlags, indent, "MoveNetworkFlags");
|
|
MrfXml.WriteItemArray(sb, MoveNetworkTriggers, indent, "MoveNetworkTriggers");
|
|
MrfXml.WriteItemArray(sb, InitialParameters, indent, "InitialParameters");
|
|
}
|
|
|
|
public override void UpdateRelativeOffsets()
|
|
{
|
|
base.UpdateRelativeOffsets();
|
|
|
|
var offset = FileOffset + 0x20;
|
|
offset += (int)ImportedParameterCount * 8;
|
|
offset += (int)MoveNetworkFlagCount * 8;
|
|
offset += (int)MoveNetworkTriggerCount * 8;
|
|
InitialParametersFileOffset = offset;
|
|
InitialParametersOffset = InitialParametersFileOffset - (FileOffset + 0xC);
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public struct MrfNodeReferenceNamePair : IMetaXmlItem
|
|
{
|
|
public MetaHash Name { get; set; } // name in the parent network
|
|
public MetaHash NewName { get; set; } // name in the new network
|
|
|
|
public MrfNodeReferenceNamePair(MetaHash name, MetaHash newName)
|
|
{
|
|
Name = name;
|
|
NewName = newName;
|
|
}
|
|
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
Name = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Name"));
|
|
NewName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "NewName"));
|
|
}
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.StringTag(sb, indent, "Name", MrfXml.HashString(Name));
|
|
MrfXml.StringTag(sb, indent, "NewName", MrfXml.HashString(NewName));
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"{Name} - {NewName}";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public struct MrfNodeReferenceInitialParameter : IMetaXmlItem
|
|
{
|
|
public uint Type { get; set; } // Animation = 0, Clip = 1, Expression = 2, Motion = 4, Float = 7
|
|
public MetaHash Name { get; set; }
|
|
public int Data { get; set; } // For Type==Float, this the float value. For the other types, this is the offset to the actual data needed for the lookup (e.g. dictionary/name hash pair).
|
|
|
|
public MrfNodeReferenceInitialParameter(uint type, MetaHash name, int data)
|
|
{
|
|
Type = type;
|
|
Name = name;
|
|
Data = data;
|
|
}
|
|
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
Type = Xml.GetChildUIntAttribute(node, "Type");
|
|
Name = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Name"));
|
|
Data = Xml.GetChildIntAttribute(node, "Data");
|
|
}
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
MrfXml.ValueTag(sb, indent, "Type", Type.ToString());
|
|
MrfXml.StringTag(sb, indent, "Name", MrfXml.HashString(Name));
|
|
MrfXml.ValueTag(sb, indent, "Data", Data.ToString());
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"{Type} - {Name} - {Data}";
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
public class MrfXml : MetaXmlBase
|
|
{
|
|
public static string GetXml(MrfFile mrf)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.AppendLine(XmlHeader);
|
|
|
|
if (mrf != null)
|
|
{
|
|
var name = "MoveNetwork";
|
|
|
|
OpenTag(sb, 0, name);
|
|
|
|
mrf.WriteXml(sb, 1);
|
|
|
|
CloseTag(sb, 0, name);
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
public static void WriteNode(StringBuilder sb, int indent, string name, MrfNode node)
|
|
{
|
|
OpenTag(sb, indent, name + " type=\"" + node.NodeType + "\"");
|
|
node.WriteXml(sb, indent + 1);
|
|
CloseTag(sb, indent, name);
|
|
}
|
|
|
|
public static void WriteNodeRef(StringBuilder sb, int indent, string name, MrfNode node)
|
|
{
|
|
Indent(sb, indent);
|
|
sb.Append("<");
|
|
sb.Append(name);
|
|
sb.Append(" ref=\"");
|
|
sb.Append(HashString(node.Name));
|
|
sb.Append("\" />");
|
|
sb.AppendLine();
|
|
}
|
|
public static void WriteCondition(StringBuilder sb, int indent, string name, MrfCondition condition)
|
|
{
|
|
OpenTag(sb, indent, name + " type=\"" + condition.Type + "\"");
|
|
condition.WriteXml(sb, indent + 1);
|
|
CloseTag(sb, indent, name);
|
|
}
|
|
public static void WriteOperator(StringBuilder sb, int indent, string name, MrfStateOperator op)
|
|
{
|
|
OpenTag(sb, indent, name + " type=\"" + op.Type + "\"");
|
|
op.WriteXml(sb, indent + 1);
|
|
CloseTag(sb, indent, name);
|
|
}
|
|
|
|
public static void ParameterizedFloatTag(StringBuilder sb, int indent, string name, MrfValueType type, float value, MetaHash parameter)
|
|
{
|
|
switch (type)
|
|
{
|
|
case MrfValueType.None: SelfClosingTag(sb, indent, name); break;
|
|
case MrfValueType.Literal: ValueTag(sb, indent, name, FloatUtil.ToString(value), "value"); break;
|
|
case MrfValueType.Parameter: ValueTag(sb, indent, name, HashString(parameter), "parameter"); break;
|
|
}
|
|
}
|
|
|
|
public static void ParameterizedBoolTag(StringBuilder sb, int indent, string name, MrfValueType type, bool value, MetaHash parameter)
|
|
{
|
|
switch (type)
|
|
{
|
|
case MrfValueType.None: SelfClosingTag(sb, indent, name); break;
|
|
case MrfValueType.Literal: ValueTag(sb, indent, name, value.ToString(), "value"); break;
|
|
case MrfValueType.Parameter: ValueTag(sb, indent, name, HashString(parameter), "parameter"); break;
|
|
}
|
|
}
|
|
|
|
public static void ParameterizedAssetTag(StringBuilder sb, int indent, string name, MrfValueType type, MetaHash dictionaryName, MetaHash assetName, MetaHash parameter)
|
|
{
|
|
switch (type)
|
|
{
|
|
case MrfValueType.None: SelfClosingTag(sb, indent, name); break;
|
|
case MrfValueType.Literal:
|
|
OpenTag(sb, indent, name);
|
|
StringTag(sb, indent + 1, "DictionaryName", HashString(dictionaryName));
|
|
StringTag(sb, indent + 1, "Name", HashString(assetName));
|
|
CloseTag(sb, indent, name);
|
|
break;
|
|
case MrfValueType.Parameter: ValueTag(sb, indent, name, HashString(parameter), "parameter"); break;
|
|
}
|
|
}
|
|
|
|
public static void ParameterizedClipTag(StringBuilder sb, int indent, string name, MrfValueType type, MrfClipContainerType containerType, MetaHash containerName, MetaHash clipName, MetaHash parameter)
|
|
{
|
|
switch (type)
|
|
{
|
|
case MrfValueType.None: SelfClosingTag(sb, indent, name); break;
|
|
case MrfValueType.Literal:
|
|
OpenTag(sb, indent, name);
|
|
StringTag(sb, indent + 1, "ContainerType", containerType.ToString());
|
|
StringTag(sb, indent + 1, "ContainerName", HashString(containerName));
|
|
StringTag(sb, indent + 1, "Name", HashString(clipName));
|
|
CloseTag(sb, indent, name);
|
|
break;
|
|
case MrfValueType.Parameter: ValueTag(sb, indent, name, HashString(parameter), "parameter"); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class XmlMrf
|
|
{
|
|
public static MrfFile GetMrf(string xml)
|
|
{
|
|
XmlDocument doc = new XmlDocument();
|
|
doc.LoadXml(xml);
|
|
return GetMrf(doc);
|
|
}
|
|
|
|
public static MrfFile GetMrf(XmlDocument doc)
|
|
{
|
|
MrfFile mrf = new MrfFile();
|
|
mrf.ReadXml(doc.DocumentElement);
|
|
return mrf;
|
|
}
|
|
|
|
public static MrfNode ReadChildNode(XmlNode node, string name)
|
|
{
|
|
return ReadNode(node.SelectSingleNode(name));
|
|
}
|
|
public static MrfNode ReadNode(XmlNode node)
|
|
{
|
|
if (node != null && Enum.TryParse<MrfNodeType>(Xml.GetStringAttribute(node, "type"), out var type))
|
|
{
|
|
var n = MrfFile.CreateNode(type);
|
|
n.ReadXml(node);
|
|
return n;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
public static MetaHash ReadChildNodeRef(XmlNode node, string name)
|
|
{
|
|
return ReadNodeRef(node.SelectSingleNode(name));
|
|
}
|
|
public static MetaHash ReadNodeRef(XmlNode node)
|
|
{
|
|
var name = XmlMeta.GetHash(Xml.GetStringAttribute(node, "ref"));
|
|
return name;
|
|
}
|
|
public static MrfCondition ReadCondition(XmlNode node)
|
|
{
|
|
if (node != null && Enum.TryParse<MrfConditionType>(Xml.GetStringAttribute(node, "type"), out var type))
|
|
{
|
|
var n = MrfCondition.CreateCondition(type);
|
|
n.ReadXml(node);
|
|
return n;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
public static MrfStateOperator ReadOperator(XmlNode node)
|
|
{
|
|
if (node != null && Enum.TryParse<MrfOperatorType>(Xml.GetStringAttribute(node, "type"), out var type))
|
|
{
|
|
var op = MrfStateOperator.CreateOperator(type);
|
|
op.ReadXml(node);
|
|
return op;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static (MrfValueType Type, float Value, MetaHash ParameterName) GetChildParameterizedFloat(XmlNode node, string name)
|
|
{
|
|
var type = MrfValueType.None;
|
|
var value = 0.0f;
|
|
var parameter = default(MetaHash);
|
|
|
|
var childNode = node.SelectSingleNode(name);
|
|
if (childNode?.Attributes["value"] != null)
|
|
{
|
|
type = MrfValueType.Literal;
|
|
value = Xml.GetFloatAttribute(childNode, "value");
|
|
}
|
|
else if (childNode?.Attributes["parameter"] != null)
|
|
{
|
|
type = MrfValueType.Parameter;
|
|
parameter = XmlMeta.GetHash(Xml.GetStringAttribute(childNode, "parameter"));
|
|
}
|
|
|
|
return (type, value, parameter);
|
|
}
|
|
|
|
public static (MrfValueType Type, bool Value, MetaHash ParameterName) GetChildParameterizedBool(XmlNode node, string name)
|
|
{
|
|
var type = MrfValueType.None;
|
|
var value = false;
|
|
var parameter = default(MetaHash);
|
|
|
|
var childNode = node.SelectSingleNode(name);
|
|
if (childNode?.Attributes["value"] != null)
|
|
{
|
|
type = MrfValueType.Literal;
|
|
value = Xml.GetBoolAttribute(childNode, "value");
|
|
}
|
|
else if (childNode?.Attributes["parameter"] != null)
|
|
{
|
|
type = MrfValueType.Parameter;
|
|
parameter = XmlMeta.GetHash(Xml.GetStringAttribute(childNode, "parameter"));
|
|
}
|
|
|
|
return (type, value, parameter);
|
|
}
|
|
|
|
public static (MrfValueType Type, MetaHash DictionaryName, MetaHash AssetName, MetaHash ParameterName) GetChildParameterizedAsset(XmlNode node, string name)
|
|
{
|
|
var type = MrfValueType.None;
|
|
var dictionaryName = default(MetaHash);
|
|
var assetName = default(MetaHash);
|
|
var parameter = default(MetaHash);
|
|
|
|
var childNode = node.SelectSingleNode(name);
|
|
var dictionaryNode = childNode?.SelectSingleNode("DictionaryName");
|
|
var nameNode = childNode?.SelectSingleNode("Name");
|
|
if (dictionaryNode != null && nameNode != null)
|
|
{
|
|
type = MrfValueType.Literal;
|
|
dictionaryName = XmlMeta.GetHash(dictionaryNode.InnerText);
|
|
assetName = XmlMeta.GetHash(nameNode.InnerText);
|
|
}
|
|
else if (childNode?.Attributes["parameter"] != null)
|
|
{
|
|
type = MrfValueType.Parameter;
|
|
parameter = XmlMeta.GetHash(Xml.GetStringAttribute(childNode, "parameter"));
|
|
}
|
|
|
|
return (type, dictionaryName, assetName, parameter);
|
|
}
|
|
|
|
public static (MrfValueType Type, MrfClipContainerType ContainerType, MetaHash ContainerName, MetaHash ClipName, MetaHash ParameterName) GetChildParameterizedClip(XmlNode node, string name)
|
|
{
|
|
var type = MrfValueType.None;
|
|
var containerType = default(MrfClipContainerType);
|
|
var containerName = default(MetaHash);
|
|
var assetName = default(MetaHash);
|
|
var parameter = default(MetaHash);
|
|
|
|
var childNode = node.SelectSingleNode(name);
|
|
var containerTypeNode = childNode?.SelectSingleNode("ContainerType");
|
|
var containerNode = childNode?.SelectSingleNode("ContainerName");
|
|
var nameNode = childNode?.SelectSingleNode("Name");
|
|
if (containerTypeNode != null && containerNode != null && nameNode != null)
|
|
{
|
|
type = MrfValueType.Literal;
|
|
containerType = Xml.GetEnumValue<MrfClipContainerType>(containerTypeNode.InnerText);
|
|
containerName = XmlMeta.GetHash(containerNode.InnerText);
|
|
assetName = XmlMeta.GetHash(nameNode.InnerText);
|
|
}
|
|
else if (childNode?.Attributes["parameter"] != null)
|
|
{
|
|
type = MrfValueType.Parameter;
|
|
parameter = XmlMeta.GetHash(Xml.GetStringAttribute(childNode, "parameter"));
|
|
}
|
|
|
|
return (type, containerType, containerName, assetName, parameter);
|
|
}
|
|
}
|
|
}
|