diff --git a/Assets/Scripts/OpenTS2/Content/DBPF/ObjectDefinitionAsset.cs b/Assets/Scripts/OpenTS2/Content/DBPF/ObjectDefinitionAsset.cs index fe288cd..70252b2 100644 --- a/Assets/Scripts/OpenTS2/Content/DBPF/ObjectDefinitionAsset.cs +++ b/Assets/Scripts/OpenTS2/Content/DBPF/ObjectDefinitionAsset.cs @@ -1,4 +1,6 @@ -using System; +using OpenTS2.Common; +using OpenTS2.Files.Formats.DBPF; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,6 +10,8 @@ namespace OpenTS2.Content.DBPF { public class ObjectDefinitionAsset : AbstractAsset { + // TODO: Person and Template object types seem to use 0x80 as the SG instance id for some reason. + public SemiGlobalAsset SemiGlobal => ContentProvider.Get().GetAsset(new ResourceKey(1, GlobalTGI.GroupID, TypeIDs.SEMIGLOBAL)); public string FileName; public enum FieldNames diff --git a/Assets/Scripts/OpenTS2/Engine/Main.cs b/Assets/Scripts/OpenTS2/Engine/Main.cs index c70efd4..0c10388 100644 --- a/Assets/Scripts/OpenTS2/Engine/Main.cs +++ b/Assets/Scripts/OpenTS2/Engine/Main.cs @@ -5,6 +5,7 @@ using OpenTS2.Diagnostic; using OpenTS2.Files; using OpenTS2.Files.Formats.DBPF; using OpenTS2.Rendering; +using OpenTS2.SimAntics.Primitives; using UnityEngine; namespace OpenTS2.Engine @@ -34,6 +35,7 @@ namespace OpenTS2.Engine Filesystem.Initialize(new JSONPathProvider(), epManager); CodecAttribute.Initialize(); CheatSystem.Initialize(); + VMPrimitiveRegistry.Initialize(); //Initialize the game assembly, do all reflection things. AssemblyHelper.InitializeLoadedAssemblies(); s_initialized = true; diff --git a/Assets/Scripts/OpenTS2/Files/Formats/DBPF/GroupsTypes.cs b/Assets/Scripts/OpenTS2/Files/Formats/DBPF/GroupsTypes.cs index ddd7ba6..ffbbbea 100644 --- a/Assets/Scripts/OpenTS2/Files/Formats/DBPF/GroupsTypes.cs +++ b/Assets/Scripts/OpenTS2/Files/Formats/DBPF/GroupsTypes.cs @@ -49,6 +49,7 @@ namespace OpenTS2.Files.Formats.DBPF public const uint DLL = 0x7582DEC6; public const uint CTSS = 0x43545353; public const uint UI = 0x0; + public const uint BHAV = 0x42484156; public const uint SEMIGLOBAL = 0x7F8D70BF; } public static class GroupIDs @@ -60,5 +61,6 @@ namespace OpenTS2.Files.Formats.DBPF public const uint DIR = 0xE86B1EEF; public const uint Scenegraph = 0x1C0532FA; public const uint Effects = 0xEA5118B1; + public const uint Global = 0x7FD46CD0; } } diff --git a/Assets/Scripts/OpenTS2/SimAntics.meta b/Assets/Scripts/OpenTS2/SimAntics.meta new file mode 100644 index 0000000..492dd94 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e31a40caa8341a846bd8243595805e3d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/BHAVAsset.cs b/Assets/Scripts/OpenTS2/SimAntics/BHAVAsset.cs new file mode 100644 index 0000000..2047d72 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/BHAVAsset.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTS2.Content; + +namespace OpenTS2.SimAntics +{ + /// + /// Script in the SimAntics language. Handles most of the simulation. + /// + public class BHAVAsset : AbstractAsset + { + public string FileName = ""; + public int ArgumentCount = 0; + public int LocalCount = 0; + public List Nodes = new List(); + + public class Node + { + public const ushort ErrorReturnValue = 0xFFFC; + public const ushort TrueReturnValue = 0xFFFD; + public const ushort FalseReturnValue = 0xFFFE; + public ushort OpCode; + public ushort TrueTarget; + public ushort FalseTarget; + public byte[] Operands; + public byte Version; + + /// + /// Retrieves the operand at index. + /// + /// Operand byte value, or 0 if the index is out of range. + public byte GetOperand(int index) + { + if (index < 0) + return 0; + if (index >= Operands.Length) + return 0; + return Operands[index]; + } + + /// + /// Retrieves an operand byte array starting at index and of the specified length. + /// + /// Operand bytes, bytes out of range return 0. + public byte[] GetOperands(int index, int length) + { + var array = new byte[length]; + for(var i=0;i + /// Retrieves an ushort from the operand array, starting at index. + /// + public ushort GetUInt16Operand(int index) + { + return BitConverter.ToUInt16(GetOperands(index,2), 0); + } + + /// + /// Retrieves a short from the operand array, starting at index. + /// + public short GetInt16Operand(int index) + { + return BitConverter.ToInt16(GetOperands(index, 2), 0); + } + } + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/BHAVAsset.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/BHAVAsset.cs.meta new file mode 100644 index 0000000..33e96a9 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/BHAVAsset.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1bc064226f63fda41bec72257f2ffc1c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/BHAVCodec.cs b/Assets/Scripts/OpenTS2/SimAntics/BHAVCodec.cs new file mode 100644 index 0000000..bc35c64 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/BHAVCodec.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTS2.Common; +using OpenTS2.Content; +using OpenTS2.Files.Formats.DBPF; +using OpenTS2.Files.Utils; +using UnityEngine; + +namespace OpenTS2.SimAntics +{ + /// + /// Parses a BHAV resource into an asset. + /// + /// https://modthesims.info/wiki.php?title=42484156 + [Codec(TypeIDs.BHAV)] + public class BHAVCodec : AbstractCodec + { + public override AbstractAsset Deserialize(byte[] bytes, ResourceKey tgi, DBPFFile sourceFile) + { + var asset = new BHAVAsset(); + var stream = new MemoryStream(bytes); + var reader = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN); + + // Header + asset.FileName = reader.ReadNullTerminatedUTF8(); + reader.Seek(SeekOrigin.Begin, 64); + var magic = reader.ReadUInt16(); + Debug.Assert(magic <= 0x8009); + var instructionCount = reader.ReadUInt16(); + var type = reader.ReadByte(); + asset.ArgumentCount = reader.ReadByte(); + asset.LocalCount = reader.ReadByte(); + var flags = reader.ReadByte(); + var treeVersion = reader.ReadUInt32(); + + byte cacheFlags = 0; + // Sims wiki says cacheflags are at the end of each node, not in the header, which isn't correct. + if (magic >= 0x8009) + cacheFlags = reader.ReadByte(); + + // Nodes + for (var i=0;i + /// Expression Primitive, handles variable assignation, retrieval, math. + /// + public class VMExpression : VMPrimitive + { + public enum Operator : byte + { + GreaterThan, + LessThan, + EqualTo, + Add, + Subtract, + Assign, + Multiply, + Divide, + IsFlagSet, + SetFlag, + ClearFlag, + IncThenLessThan, + Modulo, + And, + GreaterThanOrEqualTo, + LessThanOrEqualTo, + NotEqualTo, + DecThenGreaterThan, + Or, + Xor, + Abs, + Assign32BitValue + } + public override VMReturnValue Execute(VMContext ctx) + { + var lhsData = ctx.Node.GetInt16Operand(0); + var rhsData = ctx.Node.GetInt16Operand(2); + var signedFlag = ctx.Node.Operands[4]; + var op = (Operator)ctx.Node.Operands[5]; + var lhsSource = (VMDataSource)ctx.Node.Operands[6]; + var rhsSource = (VMDataSource)ctx.Node.Operands[7]; + + short lhs, rhs; + + switch(op) + { + case Operator.GreaterThan: + return ctx.GetData(lhsSource, lhsData) > ctx.GetData(rhsSource, rhsData) ? + VMReturnValue.ReturnTrue : VMReturnValue.ReturnFalse; + case Operator.LessThan: + return ctx.GetData(lhsSource, lhsData) < ctx.GetData(rhsSource, rhsData) ? + VMReturnValue.ReturnTrue : VMReturnValue.ReturnFalse; + case Operator.EqualTo: + return ctx.GetData(lhsSource, lhsData) == ctx.GetData(rhsSource, rhsData) ? + VMReturnValue.ReturnTrue : VMReturnValue.ReturnFalse; + case Operator.Add: + lhs = ctx.GetData(lhsSource, lhsData); + rhs = ctx.GetData(rhsSource, rhsData); + ctx.SetData(lhsSource, lhsData, (short)(lhs + rhs)); + return VMReturnValue.ReturnTrue; + case Operator.Subtract: + lhs = ctx.GetData(lhsSource, lhsData); + rhs = ctx.GetData(rhsSource, rhsData); + ctx.SetData(lhsSource, lhsData, (short)(lhs - rhs)); + return VMReturnValue.ReturnTrue; + case Operator.Assign: + rhs = ctx.GetData(rhsSource, rhsData); + ctx.SetData(lhsSource, lhsData, rhs); + return VMReturnValue.ReturnTrue; + case Operator.Multiply: + lhs = ctx.GetData(lhsSource, lhsData); + rhs = ctx.GetData(rhsSource, rhsData); + ctx.SetData(lhsSource, lhsData, (short)(lhs * rhs)); + return VMReturnValue.ReturnTrue; + case Operator.Divide: + lhs = ctx.GetData(lhsSource, lhsData); + rhs = ctx.GetData(rhsSource, rhsData); + ctx.SetData(lhsSource, lhsData, (short)(lhs / rhs)); + return VMReturnValue.ReturnTrue; + case Operator.IsFlagSet: + lhs = ctx.GetData(lhsSource, lhsData); + rhs = ctx.GetData(rhsSource, rhsData); + return ((lhs & (1 << (rhs - 1))) > 0) ? + VMReturnValue.ReturnTrue : VMReturnValue.ReturnFalse; + case Operator.SetFlag: + lhs = ctx.GetData(lhsSource, lhsData); + rhs = ctx.GetData(rhsSource, rhsData); + var bitval = 1 << (rhs - 1); + var finalSet = (int)lhs | bitval; + ctx.SetData(lhsSource, lhsData, (short)(finalSet)); + return VMReturnValue.ReturnTrue; + case Operator.ClearFlag: + lhs = ctx.GetData(lhsSource, lhsData); + rhs = ctx.GetData(rhsSource, rhsData); + var clearBitVal = ~(1 << (rhs - 1)); + var finalClear = (int)lhs & clearBitVal; + ctx.SetData(lhsSource, lhsData, (short)(finalClear)); + return VMReturnValue.ReturnTrue; + case Operator.IncThenLessThan: + lhs = ctx.GetData(lhsSource, lhsData); + rhs = ctx.GetData(rhsSource, rhsData); + var lhsAdded = lhs + 1; + ctx.SetData(lhsSource, lhsData, (short)lhsAdded); + return lhsAdded < rhs ? + VMReturnValue.ReturnTrue : VMReturnValue.ReturnFalse; + case Operator.Modulo: + lhs = ctx.GetData(lhsSource, lhsData); + rhs = ctx.GetData(rhsSource, rhsData); + var lhsMod = lhs % rhs; + ctx.SetData(lhsSource, lhsData, (short)lhsMod); + return VMReturnValue.ReturnTrue; + case Operator.And: + lhs = ctx.GetData(lhsSource, lhsData); + rhs = ctx.GetData(rhsSource, rhsData); + var lhsAnd = lhs & rhs; + ctx.SetData(lhsSource, lhsData, (short)lhsAnd); + return VMReturnValue.ReturnTrue; + case Operator.GreaterThanOrEqualTo: + return ctx.GetData(lhsSource, lhsData) >= ctx.GetData(rhsSource, rhsData) ? + VMReturnValue.ReturnTrue : VMReturnValue.ReturnFalse; + case Operator.LessThanOrEqualTo: + return ctx.GetData(lhsSource, lhsData) <= ctx.GetData(rhsSource, rhsData) ? + VMReturnValue.ReturnTrue : VMReturnValue.ReturnFalse; + case Operator.NotEqualTo: + return ctx.GetData(lhsSource, lhsData) != ctx.GetData(rhsSource, rhsData) ? + VMReturnValue.ReturnTrue : VMReturnValue.ReturnFalse; + case Operator.DecThenGreaterThan: + lhs = ctx.GetData(lhsSource, lhsData); + rhs = ctx.GetData(rhsSource, rhsData); + var lhsSubbed = lhs - 1; + ctx.SetData(lhsSource, lhsData, (short)lhsSubbed); + return lhsSubbed > rhs ? + VMReturnValue.ReturnTrue : VMReturnValue.ReturnFalse; + case Operator.Or: + lhs = ctx.GetData(lhsSource, lhsData); + rhs = ctx.GetData(rhsSource, rhsData); + var lhsOr = lhs | rhs; + ctx.SetData(lhsSource, lhsData, (short)lhsOr); + return VMReturnValue.ReturnTrue; + case Operator.Xor: + lhs = ctx.GetData(lhsSource, lhsData); + rhs = ctx.GetData(rhsSource, rhsData); + var lhsXor = lhs ^ rhs; + ctx.SetData(lhsSource, lhsData, (short)lhsXor); + return VMReturnValue.ReturnTrue; + case Operator.Abs: + rhs = ctx.GetData(rhsSource, rhsData); + ctx.SetData(lhsSource, lhsData, Math.Abs(rhs)); + return VMReturnValue.ReturnTrue; + // TODO: This is new in TS2 so I'm not sure if this is correct. + case Operator.Assign32BitValue: + rhs = ctx.GetData(rhsSource, rhsData); + var rhs2 = ctx.GetData(rhsSource, (short)(rhsData+1)); + ctx.SetData(lhsSource, lhsData, rhs); + ctx.SetData(lhsSource, (short)(lhsData+1), rhs2); + return VMReturnValue.ReturnTrue; + } + return VMReturnValue.ReturnFalse; + } + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMExpression.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMExpression.cs.meta new file mode 100644 index 0000000..090a199 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMExpression.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 66c1d2b5188323d4ebb04f6fc5f499e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMNotifyStackObjectOutOfIdle.cs b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMNotifyStackObjectOutOfIdle.cs new file mode 100644 index 0000000..a5a8d25 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMNotifyStackObjectOutOfIdle.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenTS2.SimAntics.Primitives +{ + public class VMNotifyStackObjectOutOfIdle : VMPrimitive + { + public override VMReturnValue Execute(VMContext ctx) + { + var stackObject = ctx.VM.GetEntityByID(ctx.StackFrame.StackObjectID); + if (stackObject == null) + throw new KeyNotFoundException($"Couldn't find Object with ID {ctx.StackFrame.StackObjectID}"); + ctx.VM.Scheduler.ScheduleInterrupt(ctx.StackObjectEntity.Stack); + return VMReturnValue.ReturnTrue; + } + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMNotifyStackObjectOutOfIdle.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMNotifyStackObjectOutOfIdle.cs.meta new file mode 100644 index 0000000..816d201 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMNotifyStackObjectOutOfIdle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9cde7a92de8623c40a7dbf0f9527174b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMPrimitiveRegistry.cs b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMPrimitiveRegistry.cs new file mode 100644 index 0000000..b434b77 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMPrimitiveRegistry.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenTS2.SimAntics.Primitives +{ + public static class VMPrimitiveRegistry + { + private static Dictionary s_primitiveByOpCode = new Dictionary(); + + public static void Initialize() + { + RegisterPrimitive(0x0); + RegisterPrimitive(0x2); + RegisterPrimitive(0x8); + RegisterPrimitive(0x12); + RegisterPrimitive(0x31); + } + + public static void RegisterPrimitive(ushort opcode) where T : VMPrimitive + { + s_primitiveByOpCode[opcode] = Activator.CreateInstance(typeof(T)) as VMPrimitive; + } + + public static T GetPrimitive(ushort opcode) where T : VMPrimitive + { + return GetPrimitive(opcode) as T; + } + + public static VMPrimitive GetPrimitive(ushort opcode) + { + if (s_primitiveByOpCode.TryGetValue(opcode, out VMPrimitive returnPrim)) + return returnPrim; + return null; + } + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMPrimitiveRegistry.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMPrimitiveRegistry.cs.meta new file mode 100644 index 0000000..d4d593d --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMPrimitiveRegistry.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2251550a13386fc4ca77428e52e11359 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMRandomNumber.cs b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMRandomNumber.cs new file mode 100644 index 0000000..a9f5538 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMRandomNumber.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenTS2.SimAntics.Primitives +{ + public class VMRandomNumber : VMPrimitive + { + public override VMReturnValue Execute(VMContext ctx) + { + var lhsSource = (VMDataSource)ctx.Node.GetOperand(2); + var lhsData = ctx.Node.GetInt16Operand(0); + + var rhsSource = (VMDataSource)ctx.Node.GetOperand(6); + var rhsData = ctx.Node.GetInt16Operand(4); + + var randomMaxValue = ctx.GetData(rhsSource, rhsData); + var randomFinalValue = (short)UnityEngine.Random.Range(0, randomMaxValue); + + ctx.SetData(lhsSource, lhsData, randomFinalValue); + + return VMReturnValue.ReturnTrue; + } + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMRandomNumber.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMRandomNumber.cs.meta new file mode 100644 index 0000000..e4f9a3f --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMRandomNumber.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f67753d3b8a97c442b581b3a03d97f53 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMRemoveObjectInstance.cs b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMRemoveObjectInstance.cs new file mode 100644 index 0000000..1f039b5 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMRemoveObjectInstance.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTS2.Common; + +namespace OpenTS2.SimAntics.Primitives +{ + public class VMRemoveObjectInstance : VMPrimitive + { + public override VMReturnValue Execute(VMContext ctx) + { + // These two seem to be a mystery according to FreeSO src + var returnImmediately = ((ctx.Node.GetOperand(2) & 1) == 1); + var cleanUpAll = ((ctx.Node.GetOperand(2) & 2) != 2); + + var entityToRemove = ctx.Node.GetOperand(0) > 0 ? ctx.StackObjectEntity : ctx.Entity; + entityToRemove.Delete(); + + // FreeSO yields 1 tick in the case of self deletion, preventing further execution of the script, so we also do that. + // TS2 BHAVs tend to remove themselves -> idle for a few ticks -> loop back to remove prim. + if (entityToRemove == ctx.Entity) + return VMReturnValue.ReturnTrueNextTick; + + return VMReturnValue.ReturnTrue; + } + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMRemoveObjectInstance.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMRemoveObjectInstance.cs.meta new file mode 100644 index 0000000..1536edc --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMRemoveObjectInstance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f4f2d62be2b411b43b4fb2ab0347a435 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMSleep.cs b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMSleep.cs new file mode 100644 index 0000000..7aa2e0e --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMSleep.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenTS2.SimAntics.Primitives +{ + /// + /// Sleeps the current thread for a number of ticks. + /// + public class VMSleep : VMPrimitive + { + public override VMReturnValue Execute(VMContext ctx) + { + var argumentIndex = ctx.Node.GetUInt16Operand(0); + var sleepTicks = (uint)Math.Max(0,(int)ctx.StackFrame.Arguments[argumentIndex]); + return new VMReturnValue(new ContinueHandler(ctx.Stack, sleepTicks)); + } + + /// + /// Handles VM thread blocking for the Sleep prim. + /// + public class ContinueHandler : VMContinueHandler + { + public uint TargetTick = 0; + VMStack _stack; + + public ContinueHandler(VMStack stack, uint ticks) + { + _stack = stack; + var vm = _stack.Entity.VM; + TargetTick = vm.CurrentTick + ticks; + } + + public override VMExitCode Tick() + { + if (_stack.Interrupt) + { + // Handled! + _stack.Interrupt = false; + return VMExitCode.True; + } + if (_stack.Entity.VM.CurrentTick >= TargetTick) + return VMExitCode.True; + return VMExitCode.Continue; + } + } + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMSleep.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMSleep.cs.meta new file mode 100644 index 0000000..09d76b1 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/Primitives/VMSleep.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4b295e337c8206e43bfa5c8ba0fc3fb5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/VM.cs b/Assets/Scripts/OpenTS2/SimAntics/VM.cs new file mode 100644 index 0000000..5c207e6 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VM.cs @@ -0,0 +1,82 @@ +using OpenTS2.Common; +using OpenTS2.Content; +using OpenTS2.Files.Formats.DBPF; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace OpenTS2.SimAntics +{ + /// + /// SimAntics virtual machine. + /// + public class VM + { + public VMScheduler Scheduler = new VMScheduler(); + public List Entities = new List(); + public uint CurrentTick = 0; + + private Dictionary _entitiesByID = new Dictionary(); + + /// + /// Ticks all entities and advances the Simulation by 1 tick. + /// + public void Tick() + { + Scheduler.OnBeginTick(this); + foreach(var entity in Entities) + { + entity.Tick(); + } + Scheduler.OnEndTick(this); + CurrentTick++; + } + + /// + /// Retrieves a BHAV Asset from the content system. + /// + public static BHAVAsset GetBHAV(ushort id, uint groupID) + { + return ContentProvider.Get().GetAsset(new ResourceKey(id, groupID, TypeIDs.BHAV)); + } + + public VMEntity GetEntityByID(short id) + { + if (_entitiesByID.TryGetValue(id, out VMEntity result)) + return result; + return null; + } + + /// + /// Adds an entity to the simulator, and assigns a unique ID to it. + /// + /// + public void AddEntity(VMEntity entity) + { + entity.VM = this; + entity.ID = GetUniqueID(); + Entities.Add(entity); + _entitiesByID[entity.ID] = entity; + } + + public void RemoveEntity(short id) + { + if (!_entitiesByID.TryGetValue(id, out VMEntity result)) + return; + _entitiesByID.Remove(id); + Entities.Remove(result); + } + + /// + /// Returns a free unused entity ID. + /// + public short GetUniqueID() + { + short resultID = 1; + while (_entitiesByID.TryGetValue(resultID, out VMEntity _)) + resultID++; + return resultID; + } + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/VM.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/VM.cs.meta new file mode 100644 index 0000000..80f988e --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VM.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 41af7919700444842831300feb1548fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMContext.cs b/Assets/Scripts/OpenTS2/SimAntics/VMContext.cs new file mode 100644 index 0000000..6e9a845 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMContext.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenTS2.SimAntics +{ + /// + /// SimAntics virtual machine context to be sent to primitives and such. + /// + public struct VMContext + { + public VMStackFrame StackFrame; + public BHAVAsset.Node Node; + public VMStack Stack => StackFrame.Stack; + public VMEntity Entity => StackFrame.Stack.Entity; + public VMEntity StackObjectEntity => VM.GetEntityByID(StackFrame.StackObjectID); + public VM VM => Entity.VM; + + // TODO - still super incomplete, just enough to run basic scripts. + public short GetData(VMDataSource source, short dataIndex) + { + return source switch + { + VMDataSource.Literal => dataIndex, + VMDataSource.Temps => Entity.Temps[dataIndex], + VMDataSource.Params => StackFrame.Arguments[dataIndex], + VMDataSource.StackObjectID => StackFrame.StackObjectID, + VMDataSource.TempByTempIndex => Entity.Temps[Entity.Temps[dataIndex]], + VMDataSource.StackObjectsTemp => StackObjectEntity.Temps[dataIndex], + VMDataSource.Local => StackFrame.Locals[dataIndex], + VMDataSource.StackObjectsDefinition => (short)StackObjectEntity.ObjectDefinition.Fields[dataIndex], + _ => throw new ArgumentOutOfRangeException("SimAntics data source out of range!") + }; + } + + public void SetData(VMDataSource source, short dataIndex, short value) + { + switch(source) + { + case VMDataSource.Temps: + Entity.Temps[dataIndex] = value; + return; + case VMDataSource.Params: + StackFrame.Arguments[dataIndex] = value; + return; + case VMDataSource.StackObjectID: + StackFrame.StackObjectID = value; + return; + case VMDataSource.TempByTempIndex: + Entity.Temps[Entity.Temps[dataIndex]] = value; + return; + case VMDataSource.StackObjectsTemp: + StackObjectEntity.Temps[dataIndex] = value; + return; + case VMDataSource.Local: + StackFrame.Locals[dataIndex] = value; + return; + } + throw new ArgumentOutOfRangeException("SimAntics data source out of range!"); + } + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMContext.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/VMContext.cs.meta new file mode 100644 index 0000000..cb23f11 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 002b800405232b04eb76e97756962bd1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMContinueHandler.cs b/Assets/Scripts/OpenTS2/SimAntics/VMContinueHandler.cs new file mode 100644 index 0000000..dff3d8f --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMContinueHandler.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenTS2.SimAntics +{ + public abstract class VMContinueHandler + { + public abstract VMExitCode Tick(); + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMContinueHandler.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/VMContinueHandler.cs.meta new file mode 100644 index 0000000..7296166 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMContinueHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 80b0d4061c5924a4f86ed92e1b1d4886 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMDataSource.cs b/Assets/Scripts/OpenTS2/SimAntics/VMDataSource.cs new file mode 100644 index 0000000..f8e44c8 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMDataSource.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenTS2.SimAntics +{ + public enum VMDataSource : byte + { + MyObjectsAttributes, + StackObjectsAttributes, + MyObjectsSemiAttributes, + MyObject, + StackObject, + StackObjectsSemiAttributes, + Globals, + Literal, + Temps, + Params, + StackObjectID, + // Temp[Temp] + TempByTempIndex, + CheckTreeAdRange, + StackObjectsTemp, + MyMotives, + StackObjectsMotives, + StackObjectsSlot, + // Stack obj's motive[temp] + StackObjectsMotiveByTemp, + MyPersonData, + StackObjectsPersonData, + MySlot, + StackObjectsDefinition, + // Stack Objs Attribute[Param] + StackObjectsAttributeByParam, + // Room[Temp0] + RoomInTemp0, + NeighborInStackObject, + Local, + Constant, + Unused, + CheckTreeAdPersonalityVar, + CheckTreeAdMin, + // My Person Data[Temp] + MyPersonDataByTemp, + // Stack Obj's person data [Temp] + StackObjectsPersonDataByTemp, + NeighborsPersonData, + // Job data [temp0,1] + JobDataByTemp0And1, + NeighborhoodDataReadOnly, + StackObjectsFunction, + MyTypeAttribute, + StackObjectsTypeAttribute, + NeighborsDefinition, + MyTempToken, + StackObjectsTempToken, + // My object array [array] Iterator Index + MyObjectsArrayByArrayIteratorIndex, + // Stack Object's object array [array] iterator Index + StackObjectsArrayByArrayIteratorIndex, + // My object array [array] iterator data + MyObjectsArrayByArrayIteratorData, + // Stack Object's object array [array] iterator Data + StackObjectsArrayByArrayIteratorData, + // My object array [array] element at Temp0 + MyObjectsArrayByArrayElementAtTemp0, + // Stack Object's object array [array] element at Temp0 + StackObjectsArrayByArrayElementAtTemp0, + // Const[Temp] + ConstByTemp, + // My slot[Temp] + MySlotByTemp, + // Stack Object's slot[Temp] + StackObjectsSlotByTemp, + // Stack Object's semi attr[Param] + StackObjectsSemiAttributeByParam, + StackObjectsMasterDefinition + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMDataSource.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/VMDataSource.cs.meta new file mode 100644 index 0000000..7d1d186 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMDataSource.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b936f65f70e77874f8c424e5363af9f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMEntity.cs b/Assets/Scripts/OpenTS2/SimAntics/VMEntity.cs new file mode 100644 index 0000000..b1c01e1 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMEntity.cs @@ -0,0 +1,60 @@ +using OpenTS2.Content.DBPF; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenTS2.SimAntics +{ + /// + /// This is a process or thread running in the SimAntics virtual machine, with its own stack and temp variables. + /// + public class VMEntity + { + public bool PendingDeletion = false; + public short ID = 1; + public short[] Temps = new short[20]; + public VMStack Stack; + public VM VM; + public ObjectDefinitionAsset ObjectDefinition; + public uint PrivateGroupID => ObjectDefinition.GlobalTGI.GroupID; + public uint SemiGlobalGroupID + { + get + { + var semiGlobal = ObjectDefinition.SemiGlobal; + if (semiGlobal == null) + return 0; + return semiGlobal.SemiGlobalGroupID; + } + } + + protected VMEntity() + { + Stack = new VMStack(this); + } + + public VMEntity(ObjectDefinitionAsset objectDefinition) : this() + { + ObjectDefinition = objectDefinition; + } + + public void Tick() + { + Stack.Tick(); + } + + public void Delete() + { + if (VM.Scheduler.RunningTick) + { + if (PendingDeletion) + return; + VM.Scheduler.ScheduleDeletion(this); + return; + } + VM.RemoveEntity(ID); + } + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMEntity.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/VMEntity.cs.meta new file mode 100644 index 0000000..bb48c54 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMEntity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 17d0bfd1bb2c4ab48a5e03c6c55d4c3d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMExitCode.cs b/Assets/Scripts/OpenTS2/SimAntics/VMExitCode.cs new file mode 100644 index 0000000..4576a18 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMExitCode.cs @@ -0,0 +1,9 @@ +namespace OpenTS2.SimAntics +{ + public enum VMExitCode : byte + { + True, + False, + Continue + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMExitCode.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/VMExitCode.cs.meta new file mode 100644 index 0000000..ffbf506 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMExitCode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3e8ea9071ef11a043b50e2f12f622914 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMPrimitive.cs b/Assets/Scripts/OpenTS2/SimAntics/VMPrimitive.cs new file mode 100644 index 0000000..9306322 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMPrimitive.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenTS2.SimAntics +{ + public abstract class VMPrimitive + { + public abstract VMReturnValue Execute(VMContext ctx); + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMPrimitive.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/VMPrimitive.cs.meta new file mode 100644 index 0000000..6fde0f6 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMPrimitive.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 636b30d1f9f76fa4b8ff19785fe663f8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMReturnValue.cs b/Assets/Scripts/OpenTS2/SimAntics/VMReturnValue.cs new file mode 100644 index 0000000..b4e77d6 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMReturnValue.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenTS2.SimAntics +{ + public struct VMReturnValue + { + public VMExitCode Code; + public VMContinueHandler ContinueHandler; + public static VMReturnValue ReturnTrue = new VMReturnValue(VMExitCode.True); + public static VMReturnValue ReturnFalse = new VMReturnValue(VMExitCode.False); + public static VMReturnValue ReturnTrueNextTick = new VMReturnValue(new YieldOneTickContinueHandler(VMExitCode.True)); + public static VMReturnValue ReturnFalseNextTick = new VMReturnValue(new YieldOneTickContinueHandler(VMExitCode.False)); + + public VMReturnValue(VMExitCode exitCode) + { + Code = exitCode; + ContinueHandler = null; + } + + public VMReturnValue(VMContinueHandler continueHandler) + { + Code = VMExitCode.Continue; + ContinueHandler = continueHandler; + } + + public class YieldOneTickContinueHandler : VMContinueHandler + { + bool _ticked = false; + VMExitCode _exitCode; + + public YieldOneTickContinueHandler(VMExitCode exitCode) + { + _exitCode = exitCode; + } + + public override VMExitCode Tick() + { + if (!_ticked) + { + _ticked = true; + return VMExitCode.Continue; + } + return _exitCode; + } + } + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMReturnValue.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/VMReturnValue.cs.meta new file mode 100644 index 0000000..169cf26 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMReturnValue.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f827d2fe75bd932488d54abccaf9fd28 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMScheduler.cs b/Assets/Scripts/OpenTS2/SimAntics/VMScheduler.cs new file mode 100644 index 0000000..326bc18 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMScheduler.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenTS2.SimAntics +{ + public class VMScheduler + { + public bool RunningTick => _runningTick; + private bool _runningTick = false; + + private VMScheduledEvents _preTickEvents = new VMScheduledEvents(); + private VMScheduledEvents _postTickEvents = new VMScheduledEvents(); + + /// + /// Runs a function immediately if the VM is not currently in a tick, otherwise schedules it to run at the end of the current tick. + /// + public void ScheduleWhenPossible(Action func) + { + if (!RunningTick) + { + func.Invoke(); + return; + } + ScheduleOnEndTick(func); + } + + /// + /// Schedules a function to run once the VM begins a tick. + /// + public void ScheduleOnBeginTick(Action func, uint targetTick = 0) + { + _preTickEvents.AddEvent(new VMEvent(func, targetTick)); + } + + /// + /// Schedules a function to run once the VM is done with a tick. + /// + public void ScheduleOnEndTick(Action func, uint targetTick = 0) + { + _postTickEvents.AddEvent(new VMEvent(func, targetTick)); + } + + // TODO - Right now the way this works is that once an entity has been notified out of idle/interrupted, the first idle that gets executed on the next tick (or that continues to run if there is one already running) won't actually sleep. If no idles are ran then the interrupt is just discarded. Verify this is okay! + public void ScheduleInterrupt(VMStack stack) + { + ScheduleWhenPossible(() => + { + stack.Interrupt = true; + }); + } + + public void ScheduleDeletion(VMEntity entity) + { + ScheduleWhenPossible(() => + { + entity.Delete(); + }); + entity.PendingDeletion = true; + } + + public void OnBeginTick(VM vm) + { + _preTickEvents.Run(vm); + _runningTick = true; + } + + public void OnEndTick(VM vm) + { + _runningTick = false; + _postTickEvents.Run(vm); + } + + public class VMEvent + { + public Action Event; + public uint TargetTick = 0; + + public VMEvent(Action func, uint targetTick = 0) + { + Event = func; + TargetTick = targetTick; + } + } + + public class VMScheduledEvents + { + private List _events = new List(); + public void Run(VM vm) + { + var newEvList = new List(); + foreach(var ev in _events) + { + if (vm.CurrentTick >= ev.TargetTick) + { + ev.Event?.Invoke(); + } + else + newEvList.Add(ev); + } + _events = newEvList; + } + + public void AddEvent(VMEvent ev) + { + _events.Add(ev); + } + } + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMScheduler.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/VMScheduler.cs.meta new file mode 100644 index 0000000..c4241ae --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMScheduler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 86354629246dece4fac8ccc5660ae5f3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMStack.cs b/Assets/Scripts/OpenTS2/SimAntics/VMStack.cs new file mode 100644 index 0000000..8220d52 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMStack.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenTS2.SimAntics +{ + /// + /// Stack of scripts to run on a SimAntics entity/thread. + /// + public class VMStack + { + public bool Interrupt = false; + // For check trees and other things that should execute and return immediately we should set this to false. + public bool CanYield = true; + public VMEntity Entity; + public Stack Frames = new Stack(); + public VMStack(VMEntity entity) + { + Entity = entity; + } + + public VMStackFrame GetCurrentFrame() + { + if (Frames.Count == 0) + return null; + return Frames.Peek(); + } + + VMExitCode TickInternal() + { + var currentFrame = GetCurrentFrame(); + if (currentFrame == null) + return VMExitCode.False; + var returnValue = currentFrame.Tick(); + if (returnValue == VMExitCode.Continue && !CanYield) + throw new Exception("Attempted to yield in a non-yielding VMStack."); + while (returnValue != VMExitCode.Continue) + { + Frames.Pop(); + currentFrame = GetCurrentFrame(); + if (currentFrame == null) + return returnValue; + var currentNode = currentFrame.GetCurrentNode(); + + if (returnValue == VMExitCode.True) + currentFrame.CurrentNode = currentNode.TrueTarget; + else + currentFrame.CurrentNode = currentNode.FalseTarget; + + returnValue = currentFrame.Tick(); + } + return returnValue; + } + + public VMExitCode Tick() + { + var returnValue = TickInternal(); + Interrupt = false; + return returnValue; + } + } +} diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMStack.cs.meta b/Assets/Scripts/OpenTS2/SimAntics/VMStack.cs.meta new file mode 100644 index 0000000..b4c49e2 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMStack.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d66f3e25b8c58944789a10675ddaa524 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/OpenTS2/SimAntics/VMStackFrame.cs b/Assets/Scripts/OpenTS2/SimAntics/VMStackFrame.cs new file mode 100644 index 0000000..17fd573 --- /dev/null +++ b/Assets/Scripts/OpenTS2/SimAntics/VMStackFrame.cs @@ -0,0 +1,230 @@ +using OpenTS2.Files.Formats.DBPF; +using OpenTS2.SimAntics.Primitives; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenTS2.SimAntics +{ + /// + /// A frame in a SimAntics stack. + /// + public class VMStackFrame + { + public VMStack Stack; + public BHAVAsset BHAV; + public short StackObjectID = 0; + public int CurrentNode = 0; + /// + /// Current blocking behavior. As long as this variable's Tick() returns Continue this thread won't move. + /// + public VMContinueHandler CurrentContinueHandler = null; + public short[] Locals; + public short[] Arguments; + + public VMStackFrame(BHAVAsset bhav, VMStack stack) + { + BHAV = bhav; + Stack = stack; + Locals = new short[BHAV.LocalCount]; + Arguments = new short[BHAV.ArgumentCount]; + } + + public VMExitCode Tick() + { + if (CurrentContinueHandler != null) + { + var returnCode = CurrentContinueHandler.Tick(); + if (returnCode == VMExitCode.Continue) + return returnCode; + else + { + return AdvanceNodeAndRunTick(returnCode); + } + } + return RunCurrentTick(); + } + + VMExitCode AdvanceNodeAndRunTick(VMExitCode exitCode) + { + var currentNode = GetCurrentNode(); + ushort returnTarget; + if (exitCode == VMExitCode.True) + returnTarget = currentNode.TrueTarget; + else + returnTarget = currentNode.FalseTarget; + switch (returnTarget) + { + case BHAVAsset.Node.FalseReturnValue: + return VMExitCode.False; + case BHAVAsset.Node.TrueReturnValue: + return VMExitCode.True; + case BHAVAsset.Node.ErrorReturnValue: + throw new Exception("Jumped to Error."); + default: + SetCurrentNode(returnTarget); + return RunCurrentTick(); + } + } + + VMExitCode RunCurrentTick() + { + var currentNode = GetCurrentNode(); + if (currentNode != null) + { + var context = new VMContext + { + StackFrame = this, + Node = currentNode + }; + var opcode = currentNode.OpCode; + var prim = VMPrimitiveRegistry.GetPrimitive(opcode); + if (prim != null) + { + var primReturn = prim.Execute(context); + + if (primReturn.Code == VMExitCode.Continue) + primReturn.Code = primReturn.ContinueHandler.Tick(); + + if (primReturn.Code == VMExitCode.Continue) + { + CurrentContinueHandler = primReturn.ContinueHandler; + return primReturn.Code; + } + else + { + CurrentContinueHandler = null; + return AdvanceNodeAndRunTick(primReturn.Code); + } + } + else + { + var newStackFrame = CreateStackFrameForNode(context); + if (newStackFrame != null) + { + Stack.Frames.Push(newStackFrame); + return newStackFrame.Tick(); + } + } + } + return VMExitCode.False; + } + + enum GoSubFormat + { + PassTemps, + TS1, + TS2, + CallerParams + } + + VMStackFrame CreateStackFrameForNode(VMContext ctx) + { + var bhav = GetBHAVForOpCode(ctx.Node.OpCode); + + if (bhav == null) + return null; + + var newStackFrame = new VMStackFrame(bhav, Stack); + newStackFrame.StackObjectID = ctx.StackFrame.StackObjectID; + + GoSubFormat format = GoSubFormat.PassTemps; + + if (ctx.Node.GetOperand(12) > 0) + { + format = GoSubFormat.TS2; + if (ctx.Node.GetOperand(12) == 2 && ctx.Node.Version > 0) + format = GoSubFormat.CallerParams; + } + else + { + for (var i = 0; i < 8; i++) + { + if (ctx.Node.Operands[i] != 0xFF) + { + format = GoSubFormat.TS1; + break; + } + } + } + + var argAmount = 0; + + switch(format) + { + case GoSubFormat.PassTemps: + argAmount = Math.Min(newStackFrame.Arguments.Length, Stack.Entity.Temps.Length); + for (var i=0;i /// Main initialization class for OpenTS2 unit testing. @@ -28,6 +29,7 @@ public static class TestMain Filesystem.Initialize(new TestPathProvider(), epManager); CodecAttribute.Initialize(); AssemblyHelper.InitializeLoadedAssemblies(); + VMPrimitiveRegistry.Initialize(); s_initialized = true; } diff --git a/TestAssets/SimAntics/bhav.package b/TestAssets/SimAntics/bhav.package new file mode 100644 index 0000000..8e7cf5f Binary files /dev/null and b/TestAssets/SimAntics/bhav.package differ