diff --git a/data/2.5D Physics.txt b/data/2.5D Physics.txt deleted file mode 100644 index 8a1d58795fa4a9435a9ed23d17bfee0b834d5327..0000000000000000000000000000000000000000 --- a/data/2.5D Physics.txt +++ /dev/null @@ -1,31 +0,0 @@ -Introduction -Using 2.5D Physics you are able to add Height, or thickness depending on your perspective, while still benefiting from most performance advantages available in 2D. N.B.: Use Vertical Transform has to be manually enabled in the SimulationConfig asset's Physics settings. - -Back To Top - - -2.5D Physics With Vertical Data -StaticCollider2D can have 'thickness' in the 3rd dimension using Quantum's 2.5D physics; simply set the Height: - -Adding Height to a Static Collider -Adding Height to a Static Collider. -For Entities, just add the Transform2DVertical component and set its Height and Position. On a Quantum XZ-oriented game, this adds height on the Y axis, for example. N.B.: Transform2DVertical requires the Transform2D component. - -var transform2dVertical = new Transform2DVertical(); -transform2dVertical.Height = FP._1; -transform2dVertical.Position = FP._1; - -f.Set(entity, transform2dVertical); -Adding Height to an Entity Prototype -Adding Height to an Entity Prototype. -If entities or statics have a 3rd dimension, the physics engine will take into consideration when solving collisions. This allows for 'aerial' entities to fly over 'ground-based' ones, etc. - -Back To Top - - -Physics Engine Implications - -Entity Separation -Important: When a collision is detected, the collision solver does not use the extra dimension information. This can result in entity bounce when separation is performed on the basic 2D plane of the physics engine. - -It is possible to simulate 3-dimensional gravity by manually applying speed and forces directly on Transform2DVertical.Position. The physics engine will use that information only for collision detection though. \ No newline at end of file diff --git a/data/AIAction.cs b/data/AIAction.cs deleted file mode 100644 index da7835232e35e1c865a683b569fe5d4482eb497b..0000000000000000000000000000000000000000 --- a/data/AIAction.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - public abstract unsafe partial class AIAction - { - public string Label; - public const int NEXT_ACTION_DEFAULT = -1; - - public abstract void Update(Frame frame, EntityRef entity); - public virtual int NextAction(Frame frame, EntityRef entity) { return NEXT_ACTION_DEFAULT; } - } -} diff --git a/data/AIAction.qtn b/data/AIAction.qtn deleted file mode 100644 index 74b1fa62f7d4a02cd94fcebbf1ccbc534b4ea21f..0000000000000000000000000000000000000000 --- a/data/AIAction.qtn +++ /dev/null @@ -1 +0,0 @@ -asset AIAction; diff --git a/data/AIBlackboard.cs b/data/AIBlackboard.cs deleted file mode 100644 index 39e73ae0dd92807883d9406f7ffd847a308fb1dc..0000000000000000000000000000000000000000 --- a/data/AIBlackboard.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Photon.Deterministic; -using System; -using System.Collections.Generic; - -namespace Quantum -{ - public unsafe partial class AIBlackboard - { - public AIBlackboardEntry[] Entries; - - [NonSerialized] public Dictionary Map; - - public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator) - { - base.Loaded(resourceManager, allocator); - - Map = new Dictionary(); - - for (Int32 i = 0; i < Entries.Length; i++) - { - Map.Add(Entries[i].Key.Key, i); - } - } - - public Int32 GetEntryID(string key) - { - Assert.Check(string.IsNullOrEmpty(key) == false, "The Key cannot be empty or null."); - Assert.Check(Map.ContainsKey(key) == true, $"Key {0} not present in the Blackboard", key); - - return Map[key]; - } - - public bool TryGetEntryID(string key, out Int32 id) - { - return Map.TryGetValue(key, out id); - } - - public string GetEntryName(Int32 id) - { - return Entries[id].Key.Key; - } - - public bool HasEntry(string key) - { - for (int i = 0; i < Entries.Length; i++) - { - if (Entries[i].Key.Key == key) - { - return true; - } - } - - return false; - } - - public AIBlackboardEntry GetEntry(string key) - { - for (int i = 0; i < Entries.Length; i++) - { - if (Entries[i].Key.Key == key) - { - return Entries[i]; - } - } - - return default; - } - } -} diff --git a/data/AIBlackboardComponent.cs b/data/AIBlackboardComponent.cs deleted file mode 100644 index 3bbf6b0baf47837ab28e2d47caaf893db062ed68..0000000000000000000000000000000000000000 --- a/data/AIBlackboardComponent.cs +++ /dev/null @@ -1,290 +0,0 @@ -using Photon.Deterministic; -using System; -using Quantum.Collections; - -namespace Quantum -{ - public unsafe partial struct AIBlackboardComponent - { - #region Init/Free - public void InitializeBlackboardComponent(Frame frame, AIBlackboard blackboardAsset) - { - Board = blackboardAsset; - - var assetEntries = blackboardAsset.Entries; - - if (Entries.Ptr != default) - { - FreeBlackboardComponent(frame); - } - - QList entriesList = frame.AllocateList(blackboardAsset.Entries.Length); - - for (int i = 0; i < assetEntries.Length; i++) - { - BlackboardValue newValue = CreateValueFromEntry(assetEntries[i]); - entriesList.Add(new BlackboardEntry { Value = newValue }); - //entriesList.Add(newValue); - } - - Entries = entriesList; - } - - public void FreeBlackboardComponent(Frame frame) - { - if (Entries.Ptr != default) - { - frame.FreeList(Entries); - Entries = default; - } - } - - private BlackboardValue CreateValueFromEntry(AIBlackboardEntry entry) - { - BlackboardValue newValue = new BlackboardValue(); - - if (entry.Type == AIBlackboardValueType.Boolean) - { - *newValue.BooleanValue = default; - } - - if (entry.Type == AIBlackboardValueType.Byte) - { - *newValue.ByteValue = default; - } - - if (entry.Type == AIBlackboardValueType.Integer) - { - *newValue.IntegerValue = default; - } - - if (entry.Type == AIBlackboardValueType.FP) - { - *newValue.FPValue = default; - } - - if (entry.Type == AIBlackboardValueType.Vector2) - { - *newValue.FPVector2Value = default; - } - - if (entry.Type == AIBlackboardValueType.Vector3) - { - *newValue.FPVector3Value = default; - } - - if (entry.Type == AIBlackboardValueType.EntityRef) - { - *newValue.EntityRefValue = default; - } - - return newValue; - } - #endregion - - #region Getters - public QBoolean GetBoolean(Frame frame, string key) - { - var bbValue = GetBlackboardValue(frame, key); - return *bbValue.BooleanValue; - } - - public byte GetByte(Frame frame, string key) - { - var bbValue = GetBlackboardValue(frame, key); - return *bbValue.ByteValue; - } - - public Int32 GetInteger(Frame frame, string key) - { - var bbValue = GetBlackboardValue(frame, key); - return *bbValue.IntegerValue; - } - - public FP GetFP(Frame frame, string key) - { - var bbValue = GetBlackboardValue(frame, key); - return *bbValue.FPValue; - } - - public FPVector2 GetVector2(Frame frame, string key) - { - var bbValue = GetBlackboardValue(frame, key); - return *bbValue.FPVector2Value; - } - - public FPVector3 GetVector3(Frame frame, string key) - { - var bbValue = GetBlackboardValue(frame, key); - return *bbValue.FPVector3Value; - } - - public EntityRef GetEntityRef(Frame frame, string key) - { - var bbValue = GetBlackboardValue(frame, key); - return *bbValue.EntityRefValue; - } - #endregion - - #region Setters - public BlackboardEntry* Set(Frame frame, string key, QBoolean value) - { - QList valueList = frame.ResolveList(Entries); - var ID = GetID(frame, key); - *valueList.GetPointer(ID)->Value.BooleanValue = value; - - return valueList.GetPointer(ID); - } - - public BlackboardEntry* Set(Frame frame, string key, byte value) - { - QList valueList = frame.ResolveList(Entries); - var ID = GetID(frame, key); - *valueList.GetPointer(ID)->Value.ByteValue = value; - - return valueList.GetPointer(ID); - } - - public BlackboardEntry* Set(Frame frame, string key, Int32 value) - { - QList valueList = frame.ResolveList(Entries); - var ID = GetID(frame, key); - *valueList.GetPointer(ID)->Value.IntegerValue = value; - - return valueList.GetPointer(ID); - } - - public BlackboardEntry* Set(Frame frame, string key, FP value) - { - QList valueList = frame.ResolveList(Entries); - var ID = GetID(frame, key); - *valueList.GetPointer(ID)->Value.FPValue = value; - - return valueList.GetPointer(ID); - } - - public BlackboardEntry* Set(Frame frame, string key, FPVector2 value) - { - QList valueList = frame.ResolveList(Entries); - var ID = GetID(frame, key); - *valueList.GetPointer(ID)->Value.FPVector2Value = value; - - return valueList.GetPointer(ID); - } - - public BlackboardEntry* Set(Frame frame, string key, FPVector3 value) - { - QList valueList = frame.ResolveList(Entries); - var ID = GetID(frame, key); - *valueList.GetPointer(ID)->Value.FPVector3Value = value; - - return valueList.GetPointer(ID); - - } - - public BlackboardEntry* Set(Frame frame, string key, EntityRef value) - { - QList valueList = frame.ResolveList(Entries); - var ID = GetID(frame, key); - *valueList.GetPointer(ID)->Value.EntityRefValue = value; - - return valueList.GetPointer(ID); - } - #endregion - - #region Helpers - public BlackboardEntry* GetBlackboardEntry(Frame frame, string key) - { - var bbAsset = frame.FindAsset(Board.Id); - var ID = bbAsset.GetEntryID(key); - var values = frame.ResolveList(Entries); - return values.GetPointer(ID); - } - - public BlackboardValue GetBlackboardValue(Frame frame, string key) - { - Assert.Check(string.IsNullOrEmpty(key) == false, "The Key cannot be empty or null."); - - var bbAsset = frame.FindAsset(Board.Id); - var ID = bbAsset.GetEntryID(key); - var values = frame.ResolveList(Entries); - - return values[ID].Value; - } - - public Int32 GetID(Frame frame, string key) - { - Assert.Check(string.IsNullOrEmpty(key) == false, "The Key cannot be empty or null."); - - var bbAsset = frame.FindAsset(Board.Id); - var ID = bbAsset.GetEntryID(key); - - return ID; - } - - public bool HasEntry(Frame frame, string key) - { - var boardAsset = frame.FindAsset(Board.Id); - return boardAsset.HasEntry(key); - } - #endregion - - #region BT Specific - public void RegisterReactiveDecorator(Frame frame, string key, BTDecorator decorator) - { - var blackboardEntry = GetBlackboardEntry(frame, key); - - QList reactiveDecorators; - if (blackboardEntry->ReactiveDecorators.Ptr == default) - { - reactiveDecorators = frame.AllocateList(); - } - else - { - reactiveDecorators = frame.ResolveList(blackboardEntry->ReactiveDecorators); - } - reactiveDecorators.Add(decorator); - - blackboardEntry->ReactiveDecorators = reactiveDecorators; - } - - public void UnregisterReactiveDecorator(Frame frame, string key, BTDecorator decorator) - { - var blackboardEntry = GetBlackboardEntry(frame, key); - - if (blackboardEntry->ReactiveDecorators.Ptr != default) - { - QList reactiveDecorators = frame.ResolveList(blackboardEntry->ReactiveDecorators); - reactiveDecorators.Remove(decorator); - blackboardEntry->ReactiveDecorators = reactiveDecorators; - } - } - #endregion - - #region Debug - public void Dump(Frame frame) - { - string dumpText = ""; - var bbAsset = frame.FindAsset(Board.Id); - dumpText += "Blackboard Path and ID: " + bbAsset.Path + " | " + Board.Id.Value; - - var valuesList = frame.ResolveList(Entries); - for (int i = 0; i < valuesList.Count; i++) - { - string value = "NONE"; - if (valuesList[i].Value.Field == BlackboardValue.BOOLEANVALUE) value = valuesList[i].Value.BooleanValue->Value.ToString(); - if (valuesList[i].Value.Field == BlackboardValue.BYTEVALUE) value = valuesList[i].Value.ByteValue->ToString(); - if (valuesList[i].Value.Field == BlackboardValue.INTEGERVALUE) value = valuesList[i].Value.IntegerValue->ToString(); - if (valuesList[i].Value.Field == BlackboardValue.FPVALUE) value = valuesList[i].Value.FPValue->ToString(); - if (valuesList[i].Value.Field == BlackboardValue.FPVECTOR2VALUE) value = valuesList[i].Value.FPVector2Value->ToString(); - if (valuesList[i].Value.Field == BlackboardValue.FPVECTOR3VALUE) value = valuesList[i].Value.FPVector3Value->ToString(); - if (valuesList[i].Value.Field == BlackboardValue.ENTITYREFVALUE) value = valuesList[i].Value.EntityRefValue->ToString(); - - dumpText += "\nName: " + bbAsset.GetEntryName(i) + ", Value: " + value; - } - - Log.Info(dumpText); - } - #endregion - } -} diff --git a/data/AIBlackboardEntry.cs b/data/AIBlackboardEntry.cs deleted file mode 100644 index 4429be56ab6f0bbd2441b194e242458bafc64545..0000000000000000000000000000000000000000 --- a/data/AIBlackboardEntry.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Quantum -{ - // This struct is stored on the blackboard asset - // It is NOT the one used on the Blackboard component - [Serializable] - public struct AIBlackboardEntry - { - public AIBlackboardValueType Type; - public AIBlackboardValueKey Key; - } -} diff --git a/data/AIBlackboardInitializer.cs b/data/AIBlackboardInitializer.cs deleted file mode 100644 index 1bb400020c34b9aefdb10b8372d4a03581b09843..0000000000000000000000000000000000000000 --- a/data/AIBlackboardInitializer.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - public unsafe partial class AIBlackboardInitializer - { - [Serializable] - public struct AIBlackboardInitialValue - { - public Boolean AsBoolean; - public Byte AsByte; - public Int32 AsInteger; - public FP AsFP; - public FPVector2 AsFPVector2; - public FPVector3 AsFPVector3; - public EntityRef AsEntityRef; - } - - [Serializable] - public struct AIBlackboardInitialValueEntry - { - public string Key; - public AIBlackboardInitialValue Value; - } - - public bool ReportMissingEntries = true; - - public AssetRefAIBlackboard AIBlackboard; - public AIBlackboardInitialValueEntry[] InitialValues; - - - public unsafe static void InitializeBlackboard(Frame frame, AIBlackboardComponent* blackboard, AIBlackboardInitializer blackboardInitializer, AIBlackboardInitialValueEntry[] blackboardOverrides = null) - { - AIBlackboard board = frame.FindAsset(blackboardInitializer.AIBlackboard.Id); - - blackboard->InitializeBlackboardComponent(frame, board); - - ApplyEntries(frame, blackboard, blackboardInitializer, blackboardInitializer.InitialValues); - ApplyEntries(frame, blackboard, blackboardInitializer, blackboardOverrides); - } - - public unsafe static void ApplyEntries(Frame frame, AIBlackboardComponent* blackboard, AIBlackboardInitializer blackboardInitializer, AIBlackboardInitialValueEntry[] values) - { - if (values == null) return; - - for (int i = 0; i < values.Length; i++) - { - string key = values[i].Key; - if (blackboard->HasEntry(frame, key) == false) - { - if (blackboardInitializer.ReportMissingEntries) - { - Quantum.Log.Warn($"Blackboard {blackboard->Board} does not have an entry with a key called '{key}'"); - } - continue; - } - - BlackboardValue value = blackboard->GetBlackboardValue(frame, key); - switch (value.Field) - { - case BlackboardValue.BOOLEANVALUE: - blackboard->Set(frame, key, values[i].Value.AsBoolean); - break; - case BlackboardValue.BYTEVALUE: - blackboard->Set(frame, key, values[i].Value.AsByte); - break; - case BlackboardValue.ENTITYREFVALUE: - blackboard->Set(frame, key, values[i].Value.AsEntityRef); - break; - case BlackboardValue.FPVALUE: - blackboard->Set(frame, key, values[i].Value.AsFP); - break; - case BlackboardValue.INTEGERVALUE: - blackboard->Set(frame, key, values[i].Value.AsInteger); - break; - case BlackboardValue.FPVECTOR2VALUE: - blackboard->Set(frame, key, values[i].Value.AsFPVector2); - break; - case BlackboardValue.FPVECTOR3VALUE: - blackboard->Set(frame, key, values[i].Value.AsFPVector3); - break; - } - } - } - } -} diff --git a/data/AIBlackboardValueKey.cs b/data/AIBlackboardValueKey.cs deleted file mode 100644 index 5422a6882a4e8db8b55c7da408f3642d9573fed5..0000000000000000000000000000000000000000 --- a/data/AIBlackboardValueKey.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Quantum -{ - // Wrapping the blackboard value key inside a struct gives us a nice way to overload the Unity inspector. - [Serializable] - public struct AIBlackboardValueKey - { - public String Key; - } -} diff --git a/data/AIBlackboardValueType.cs b/data/AIBlackboardValueType.cs deleted file mode 100644 index 47cd065eff109671e0c1ff4061bb4413696b2cfe..0000000000000000000000000000000000000000 --- a/data/AIBlackboardValueType.cs +++ /dev/null @@ -1,14 +0,0 @@ - -namespace Quantum -{ - public enum AIBlackboardValueType - { - Boolean, - Byte, - Integer, - FP, - Vector2, - Vector3, - EntityRef - } -} diff --git a/data/AIConfig.cs b/data/AIConfig.cs deleted file mode 100644 index ea98daa806facdc1e0d9e5ad94c119f770b43acf..0000000000000000000000000000000000000000 --- a/data/AIConfig.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Photon.Deterministic; -using System; -using System.Collections.Generic; - -namespace Quantum -{ - public partial class AIConfig : AssetObject - { - public enum EValueType - { - None, - Int, - Bool, - Byte, - FP, - FPVector2, - FPVector3, - String, - EntityRef, - } - - [Serializable] - public class KeyValuePair - { - public string Key; - public EValueType Type; - public Value Value; - } - - [Serializable] - public struct Value - { - public Int32 Integer; - public Boolean Boolean; - public Byte Byte; - public FP FP; - public FPVector2 FPVector2; - public FPVector3 FPVector3; - public string String; - public EntityRef EntityRef; - } - - public int Count { get { return KeyValuePairs.Count; } } - - public AssetRefAIConfig DefaultConfig; - public List KeyValuePairs = new List(32); - - public KeyValuePair Get(string key) - { - for (int i = 0; i < KeyValuePairs.Count; i++) - { - if (KeyValuePairs[i].Key == key) - return KeyValuePairs[i]; - } - - return null; - } - - public void Set(string key, T value) - { - if (string.IsNullOrEmpty(key) == true) - return; - - KeyValuePair pair = Get(key); - - if (pair == null) - { - pair = new KeyValuePair(); - pair.Key = key; - KeyValuePairs.Add(pair); - } - - Set(pair, value); - } - - private void Set(KeyValuePair pair, T value) - { - if (value is int intValue) - { - pair.Type = EValueType.Int; - pair.Value.Integer = intValue; - } - else if (value is bool boolValue) - { - pair.Type = EValueType.Bool; - pair.Value.Boolean = boolValue; - } - else if (value is Byte byteValue) - { - pair.Type = EValueType.Byte; - pair.Value.Byte = byteValue; - } - else if (value is FP fpValue) - { - pair.Type = EValueType.FP; - pair.Value.FP = fpValue; - } - else if (value is FPVector2 fpVector2Value) - { - pair.Type = EValueType.FPVector2; - pair.Value.FPVector2 = fpVector2Value; - } - else if (value is FPVector3 fpVector3Value) - { - pair.Type = EValueType.FPVector3; - pair.Value.FPVector3 = fpVector3Value; - } - else if (value is string stringValue) - { - pair.Type = EValueType.String; - pair.Value.String = stringValue; - } - else if (value is EntityRef entityRefValue) - { - pair.Type = EValueType.EntityRef; - pair.Value.EntityRef = entityRefValue; - } - else - { - throw new NotSupportedException(string.Format("AIConfig - Type not supported. Type: {0} Key: {1}", typeof(T), pair.Key)); - } - } - } -} diff --git a/data/AIConfig.qtn b/data/AIConfig.qtn deleted file mode 100644 index c7d17780c5b63e50c47ffacc6f8945b1360858e9..0000000000000000000000000000000000000000 --- a/data/AIConfig.qtn +++ /dev/null @@ -1 +0,0 @@ -asset AIConfig; \ No newline at end of file diff --git a/data/AIFunction.qtn b/data/AIFunction.qtn deleted file mode 100644 index c1d93bcf99fc7fdbd94a3a8d1c72c597fdb90eb0..0000000000000000000000000000000000000000 --- a/data/AIFunction.qtn +++ /dev/null @@ -1,7 +0,0 @@ -asset AIFunctionByte; -asset AIFunctionBool; -asset AIFunctionInt; -asset AIFunctionFP; -asset AIFunctionFPVector2; -asset AIFunctionFPVector3; -asset AIFunctionEntityRef; \ No newline at end of file diff --git a/data/AIFunctionAND.cs b/data/AIFunctionAND.cs deleted file mode 100644 index 315edac78fb432e1535893d94bdefd79c4a77b20..0000000000000000000000000000000000000000 --- a/data/AIFunctionAND.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Quantum -{ - [System.Serializable] - public unsafe partial class AIFunctionAND : AIFunctionBool - { - public AIParamBool ValueA; - public AIParamBool ValueB; - - public override bool Execute(Frame frame, EntityRef entity) - { - return ValueA.ResolveFunction(frame, entity) && ValueB.ResolveFunction(frame, entity); - } - } -} diff --git a/data/AIFunctionBool.cs b/data/AIFunctionBool.cs deleted file mode 100644 index 813dadf5f1c62bb8556480a1ba82062764a8bb1d..0000000000000000000000000000000000000000 --- a/data/AIFunctionBool.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Quantum -{ - public unsafe abstract partial class AIFunctionBool - { - public abstract bool Execute(Frame frame, EntityRef entity); - } - - [BotSDKHidden] - [System.Serializable] - public unsafe partial class DefaultAIFunctionBool : AIFunctionBool - { - public override bool Execute(Frame frame, EntityRef entity) - { - return false; - } - } -} diff --git a/data/AIFunctionByte.cs b/data/AIFunctionByte.cs deleted file mode 100644 index 383dfc89ac56e0c4166d6ddfecbdd093638af0ef..0000000000000000000000000000000000000000 --- a/data/AIFunctionByte.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Quantum -{ - public unsafe abstract partial class AIFunctionByte - { - public abstract byte Execute(Frame frame, EntityRef entity); - } - - [BotSDKHidden] - [System.Serializable] - public unsafe partial class DefaultAIFunctionByte : AIFunctionByte - { - public override byte Execute(Frame frame, EntityRef entity) - { - return 0; - } - } -} diff --git a/data/AIFunctionEntityRef.cs b/data/AIFunctionEntityRef.cs deleted file mode 100644 index 37c5866fae82cd1200277fba2cd8a97f42fb41f3..0000000000000000000000000000000000000000 --- a/data/AIFunctionEntityRef.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Quantum -{ - public unsafe abstract partial class AIFunctionEntityRef - { - public abstract EntityRef Execute(Frame frame, EntityRef entity); - } - - [BotSDKHidden] - [System.Serializable] - public unsafe partial class DefaultAIFunctionEntityRef : AIFunctionEntityRef - { - public override EntityRef Execute(Frame frame, EntityRef entity) - { - return default(EntityRef); - } - } -} diff --git a/data/AIFunctionFP.cs b/data/AIFunctionFP.cs deleted file mode 100644 index ade4524ac96c1dd601d43d838eac895e5a3a0d30..0000000000000000000000000000000000000000 --- a/data/AIFunctionFP.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Photon.Deterministic; - -namespace Quantum -{ - public abstract unsafe partial class AIFunctionFP - { - public abstract FP Execute(Frame frame, EntityRef entity); - } - - [BotSDKHidden] - [System.Serializable] - public unsafe partial class DefaultAIFunctionFP : AIFunctionFP - { - public override FP Execute(Frame frame, EntityRef entity) - { - return FP._0; - } - } -} diff --git a/data/AIFunctionFPVector2.cs b/data/AIFunctionFPVector2.cs deleted file mode 100644 index b1fd17c38c5fc50b2f3772d72bf0957c52f9a865..0000000000000000000000000000000000000000 --- a/data/AIFunctionFPVector2.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Photon.Deterministic; - -namespace Quantum -{ - public unsafe abstract partial class AIFunctionFPVector2 - { - public abstract FPVector2 Execute(Frame frame, EntityRef entity); - } - - [BotSDKHidden] - [System.Serializable] - public unsafe partial class DefaultAIFunctionFPVector2 : AIFunctionFPVector2 - { - public override FPVector2 Execute(Frame frame, EntityRef entity) - { - return FPVector2.Zero; - } - } -} diff --git a/data/AIFunctionFPVector3.cs b/data/AIFunctionFPVector3.cs deleted file mode 100644 index c2da0b9837355308f771177a982103f54bd0603e..0000000000000000000000000000000000000000 --- a/data/AIFunctionFPVector3.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Photon.Deterministic; - -namespace Quantum -{ - public unsafe abstract partial class AIFunctionFPVector3 - { - public abstract FPVector3 Execute(Frame frame, EntityRef entity); - } - - [BotSDKHidden] - [System.Serializable] - public unsafe partial class DefaultAIFunctionFPVector3 : AIFunctionFPVector3 - { - public override FPVector3 Execute(Frame frame, EntityRef entity) - { - return FPVector3.Zero; - } - } -} diff --git a/data/AIFunctionInt.cs b/data/AIFunctionInt.cs deleted file mode 100644 index 7b9390013811548557a718bc2c773ed6f2cdef44..0000000000000000000000000000000000000000 --- a/data/AIFunctionInt.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Quantum -{ - public unsafe abstract partial class AIFunctionInt - { - public abstract int Execute(Frame frame, EntityRef entity); - } - - [BotSDKHidden] - [System.Serializable] - public unsafe partial class DefaultAIFunctionInt : AIFunctionInt - { - public override int Execute(Frame frame, EntityRef entity) - { - return 0; - } - } -} diff --git a/data/AIFunctionNOT.cs b/data/AIFunctionNOT.cs deleted file mode 100644 index ac982b17ab65a2e9740a315900d1f0d4b65fce31..0000000000000000000000000000000000000000 --- a/data/AIFunctionNOT.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Quantum -{ - [System.Serializable] - public unsafe partial class AIFunctionNOT : AIFunctionBool - { - public AIParamBool Value; - - public override bool Execute(Frame frame, EntityRef entity) - { - return !Value.ResolveFunction(frame, entity); - } - } -} diff --git a/data/AIFunctionOR.cs b/data/AIFunctionOR.cs deleted file mode 100644 index ffa739267f938e3c3625ca694d7d1604ac8ed5c8..0000000000000000000000000000000000000000 --- a/data/AIFunctionOR.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Quantum -{ - [System.Serializable] - public unsafe partial class AIFunctionOR : AIFunctionBool - { - public AIParamBool ValueA; - public AIParamBool ValueB; - - public override bool Execute(Frame frame, EntityRef entity) - { - return ValueA.ResolveFunction(frame, entity) || ValueB.ResolveFunction(frame, entity); - } - } -} diff --git a/data/AIParam.Types.cs b/data/AIParam.Types.cs deleted file mode 100644 index 3fbe50bff1cfe02d156785388f90768364c96f91..0000000000000000000000000000000000000000 --- a/data/AIParam.Types.cs +++ /dev/null @@ -1,236 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - [System.Serializable] - public unsafe sealed class AIParamInt : AIParam - { - public static implicit operator AIParamInt(int value) { return new AIParamInt() { DefaultValue = value }; } - - public AssetRefAIFunctionInt FunctionRef; - - [NonSerialized] private AIFunctionInt _cachedFunction; - - protected override int GetBlackboardValue(BlackboardValue value) - { - return *value.IntegerValue; - } - - protected override int GetConfigValue(AIConfig.KeyValuePair configPair) - { - return configPair.Value.Integer; - } - - protected override int GetFunctionValue(Frame frame, EntityRef entity) - { - if (_cachedFunction == null) - { - _cachedFunction = frame.FindAsset(FunctionRef.Id); - } - - return _cachedFunction.Execute(frame, entity); - } - } - - [System.Serializable] - public unsafe sealed class AIParamBool : AIParam - { - public static implicit operator AIParamBool(bool value) { return new AIParamBool() { DefaultValue = value }; } - - public AssetRefAIFunctionBool FunctionRef; - - [NonSerialized] private AIFunctionBool _cachedFunction; - - protected override bool GetBlackboardValue(BlackboardValue value) - { - return *value.BooleanValue; - } - - protected override bool GetConfigValue(AIConfig.KeyValuePair configPair) - { - return configPair.Value.Boolean; - } - - protected override bool GetFunctionValue(Frame frame, EntityRef entity) - { - if (_cachedFunction == null) - { - _cachedFunction = frame.FindAsset(FunctionRef.Id); - } - - return _cachedFunction.Execute(frame, entity); - } - } - - [System.Serializable] - public unsafe sealed class AIParamByte : AIParam - { - public static implicit operator AIParamByte(byte value) { return new AIParamByte() { DefaultValue = value }; } - - public AssetRefAIFunctionByte FunctionRef; - - [NonSerialized] private AIFunctionByte _cachedFunction; - - protected override byte GetBlackboardValue(BlackboardValue value) - { - return *value.ByteValue; - } - - protected override byte GetConfigValue(AIConfig.KeyValuePair configPair) - { - return configPair.Value.Byte; - } - - protected override byte GetFunctionValue(Frame frame, EntityRef entity) - { - if (_cachedFunction == null) - { - _cachedFunction = frame.FindAsset(FunctionRef.Id); - } - - return _cachedFunction.Execute(frame, entity); - } - } - - [System.Serializable] - public unsafe sealed class AIParamFP : AIParam - { - public static implicit operator AIParamFP(FP value) { return new AIParamFP() { DefaultValue = value }; } - - public AssetRefAIFunctionFP FunctionRef; - - [NonSerialized] private AIFunctionFP _cachedFunction; - - protected override FP GetBlackboardValue(BlackboardValue value) - { - return *value.FPValue; - } - - protected override FP GetConfigValue(AIConfig.KeyValuePair configPair) - { - return configPair.Value.FP; - } - - protected override FP GetFunctionValue(Frame frame, EntityRef entity) - { - if (_cachedFunction == null) - { - _cachedFunction = frame.FindAsset(FunctionRef.Id); - } - - return _cachedFunction.Execute(frame, entity); - } - } - - [System.Serializable] - public unsafe sealed class AIParamFPVector2 : AIParam - { - public static implicit operator AIParamFPVector2(FPVector2 value) { return new AIParamFPVector2() { DefaultValue = value }; } - - public AssetRefAIFunctionFPVector2 FunctionRef; - - [NonSerialized] private AIFunctionFPVector2 _cachedFunction; - - protected override FPVector2 GetBlackboardValue(BlackboardValue value) - { - return *value.FPVector2Value; - } - - protected override FPVector2 GetConfigValue(AIConfig.KeyValuePair configPair) - { - return configPair.Value.FPVector2; - } - - protected override FPVector2 GetFunctionValue(Frame frame, EntityRef entity) - { - if (_cachedFunction == null) - { - _cachedFunction = frame.FindAsset(FunctionRef.Id); - } - - return _cachedFunction.Execute(frame, entity); - } - } - - [System.Serializable] - public unsafe sealed class AIParamFPVector3 : AIParam - { - public static implicit operator AIParamFPVector3(FPVector3 value) { return new AIParamFPVector3() { DefaultValue = value }; } - - public AssetRefAIFunctionFPVector3 FunctionRef; - - [NonSerialized] private AIFunctionFPVector3 _cachedFunction; - - protected override FPVector3 GetBlackboardValue(BlackboardValue value) - { - return *value.FPVector3Value; - } - - protected override FPVector3 GetConfigValue(AIConfig.KeyValuePair configPair) - { - return configPair.Value.FPVector3; - } - - protected override FPVector3 GetFunctionValue(Frame frame, EntityRef entity) - { - if (_cachedFunction == null) - { - _cachedFunction = frame.FindAsset(FunctionRef.Id); - } - - return _cachedFunction.Execute(frame, entity); - } - } - - [System.Serializable] - public unsafe sealed class AIParamString : AIParam - { - public static implicit operator AIParamString(string value) { return new AIParamString() { DefaultValue = value }; } - - protected override string GetBlackboardValue(BlackboardValue value) - { - throw new NotSupportedException("Blackboard variables as strings are not supported."); - } - - protected override string GetConfigValue(AIConfig.KeyValuePair configPair) - { - return configPair.Value.String; - } - - protected override string GetFunctionValue(Frame frame, EntityRef entity) - { - return default; - } - } - - [System.Serializable] - public unsafe sealed class AIParamEntityRef : AIParam - { - public static implicit operator AIParamEntityRef(EntityRef value) { return new AIParamEntityRef() { DefaultValue = value }; } - - public AssetRefAIFunctionEntityRef FunctionRef; - - [NonSerialized] private AIFunctionEntityRef _cachedFunction; - - protected override EntityRef GetBlackboardValue(BlackboardValue value) - { - return *value.EntityRefValue; - } - - protected override EntityRef GetConfigValue(AIConfig.KeyValuePair configPair) - { - return configPair.Value.EntityRef; - } - - protected override EntityRef GetFunctionValue(Frame frame, EntityRef entity) - { - if (_cachedFunction == null) - { - _cachedFunction = frame.FindAsset(FunctionRef.Id); - } - - return _cachedFunction.Execute(frame, entity); - } - } -} diff --git a/data/AIParam.cs b/data/AIParam.cs deleted file mode 100644 index de467ba8a11fa7d63a93b176f62990eeed91ef0e..0000000000000000000000000000000000000000 --- a/data/AIParam.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; - -namespace Quantum -{ - public enum AIParamSource - { - None, - Value, - Config, - Blackboard, - Function, - } - - [Serializable] - public abstract unsafe class AIParam - { - public AIParamSource Source = AIParamSource.Value; - public string Key; - public T DefaultValue; - - /// - /// Use this to solve the AIParam value when the source of the value is unkown - /// - public T Resolve(Frame frame, EntityRef entity, AIBlackboardComponent* blackboard, AIConfig aiConfig) - { - if (Source == AIParamSource.Value || (Source != AIParamSource.Function && string.IsNullOrEmpty(Key) == true)) - return DefaultValue; - - switch (Source) - { - case AIParamSource.Blackboard: - BlackboardValue blackboardValue = blackboard->GetBlackboardValue(frame, Key); - return GetBlackboardValue(blackboardValue); - - case AIParamSource.Config: - AIConfig.KeyValuePair configPair = aiConfig != null ? aiConfig.Get(Key) : null; - return configPair != null ? GetConfigValue(configPair) : DefaultValue; - - case AIParamSource.Function: - return GetFunctionValue(frame, entity); - } - - return default(T); - } - - /// - /// Use this if the it is known that the AIParam stores specifically a Blackboard value - /// - public unsafe T ResolveBlackboard(Frame frame, AIBlackboardComponent* blackboard) - { - BlackboardValue blackboardValue = blackboard->GetBlackboardValue(frame, Key); - return GetBlackboardValue(blackboardValue); - } - - /// - /// Use this if the it is known that the AIParam stores specifically a Config value - /// - public unsafe T ResolveConfig(Frame frame, AIConfig aiConfig) - { - AIConfig.KeyValuePair configPair = aiConfig != null ? aiConfig.Get(Key) : null; - return configPair != null ? GetConfigValue(configPair) : DefaultValue; - } - - /// - /// Use this if the it is known that the AIParam stores specifically a Func - /// - public unsafe T ResolveFunction(Frame frame, EntityRef entity) - { - return GetFunctionValue(frame, entity); - } - - protected abstract T GetBlackboardValue(BlackboardValue value); - protected abstract T GetConfigValue(AIConfig.KeyValuePair configPair); - protected abstract T GetFunctionValue(Frame frame, EntityRef entity); - } -} diff --git a/data/AIParamExtensions.cs b/data/AIParamExtensions.cs deleted file mode 100644 index a1168cadc5414f8f2be905d69651aef7cc9ff0b3..0000000000000000000000000000000000000000 --- a/data/AIParamExtensions.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Quantum -{ - public static unsafe partial class AIParamExtensions - { - public static T ResolveFromHFSM(this AIParam aiParam, Frame frame, EntityRef entity) - { - var aiConfigRef = aiParam.Source == AIParamSource.Config - ? frame.Unsafe.GetPointer(entity)->Config - : default; - - return aiParam.Resolve(frame, entity, aiConfigRef); - } - - public static T ResolveFromGOAP(this AIParam aiParam, Frame frame, EntityRef entity) - { - var aiConfigRef = aiParam.Source == AIParamSource.Config - ? frame.Unsafe.GetPointer(entity)->Config - : default; - - return aiParam.Resolve(frame, entity, aiConfigRef); - } - - public static T ResolveFromBT(this AIParam aiParam, Frame frame, EntityRef entity) - { - var aiConfigRef = aiParam.Source == AIParamSource.Config - ? frame.Unsafe.GetPointer(entity)->Config - : default; - - return aiParam.Resolve(frame, entity, aiConfigRef); - } - - public static T Resolve(this AIParam aiParam, Frame frame, EntityRef entity, AssetRefAIConfig aiConfigRef) - { - var blackboard = aiParam.Source == AIParamSource.Blackboard - ? frame.Unsafe.GetPointer(entity) - : null; - - var aiConfig = aiParam.Source == AIParamSource.Config - ? frame.FindAsset(aiConfigRef.Id) - : null; - - return aiParam.Resolve(frame, entity, blackboard, aiConfig); - } - } -} \ No newline at end of file diff --git a/data/AssemblyInfo.cs b/data/AssemblyInfo.cs deleted file mode 100644 index 365fcd9e06b12e2e096b94ff408caa4765b806d0..0000000000000000000000000000000000000000 --- a/data/AssemblyInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("quantum.code")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("quantum.code")] -[assembly: AssemblyCopyright("")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] -[assembly: Guid("fbf32099-b197-4ab9-8e5a-b44d9d3750bd")] -[assembly: AssemblyVersion("2.0.0.0")] -[assembly: AssemblyFileVersion("2.0.0.0")] -[assembly: AssemblyInformationalVersion("2.0.0 RC2N 501 2.0/develop (99c627e06)")] - diff --git a/data/BTAbort.cs b/data/BTAbort.cs deleted file mode 100644 index 02722ff1feca146f4133899c46ff67a038926e38..0000000000000000000000000000000000000000 --- a/data/BTAbort.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace Quantum -{ - public enum BTAbort - { - None, - Self, - LowerPriority, - Both - } - - public static class BTAbortExtensions - { - public static Boolean IsSelf(this BTAbort abort) - { - return abort == BTAbort.Self || abort == BTAbort.Both; - } - - public static Boolean IsLowerPriority(this BTAbort abort) - { - return abort == BTAbort.LowerPriority || abort == BTAbort.Both; - } - } -} diff --git a/data/BTAgent.User.Data.cs b/data/BTAgent.User.Data.cs deleted file mode 100644 index 3f626afcda832100f5144a54ca09944505718f0e..0000000000000000000000000000000000000000 --- a/data/BTAgent.User.Data.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - public unsafe partial struct BTAgent - { - #region Int and FP Data - // Getter / Setters of node FP and Int32 data - public void AddFPData(Frame frame, FP fpValue) - { - var nodesDataList = frame.ResolveList(BTDataValues); - BTDataValue newDataValue = new BTDataValue(); - *newDataValue.FPValue = fpValue; - nodesDataList.Add(newDataValue); - } - - public void AddIntData(Frame frame, Int32 intValue) - { - var nodesDataList = frame.ResolveList(BTDataValues); - BTDataValue newDataValue = new BTDataValue(); - *newDataValue.IntValue = intValue; - nodesDataList.Add(newDataValue); - } - - public void SetFPData(Frame frame, FP value, Int32 index) - { - var nodesDataList = frame.ResolveList(BTDataValues); - *nodesDataList.GetPointer(index)->FPValue = value; - } - - public void SetIntData(Frame frame, Int32 value, Int32 index) - { - var nodesDataList = frame.ResolveList(BTDataValues); - *nodesDataList.GetPointer(index)->IntValue = value; - } - - public FP GetFPData(Frame frame, Int32 index) - { - var nodesDataList = frame.ResolveList(BTDataValues); - return *nodesDataList.GetPointer(index)->FPValue; - } - - public Int32 GetIntData(Frame frame, Int32 index) - { - var nodesDataList = frame.ResolveList(BTDataValues); - return *nodesDataList.GetPointer(index)->IntValue; - } - #endregion - - // -- THREADSAFE - - #region THREADSAFE Int and FP Data - // Getter / Setters of node FP and Int32 data - public void AddFPData(FrameThreadSafe frame, FP fpValue) - { - var nodesDataList = frame.ResolveList(BTDataValues); - BTDataValue newDataValue = new BTDataValue(); - *newDataValue.FPValue = fpValue; - nodesDataList.Add(newDataValue); - } - - public void AddIntData(FrameThreadSafe frame, Int32 intValue) - { - var nodesDataList = frame.ResolveList(BTDataValues); - BTDataValue newDataValue = new BTDataValue(); - *newDataValue.IntValue = intValue; - nodesDataList.Add(newDataValue); - } - - public void SetFPData(FrameThreadSafe frame, FP value, Int32 index) - { - var nodesDataList = frame.ResolveList(BTDataValues); - *nodesDataList.GetPointer(index)->FPValue = value; - } - - public void SetIntData(FrameThreadSafe frame, Int32 value, Int32 index) - { - var nodesDataList = frame.ResolveList(BTDataValues); - *nodesDataList.GetPointer(index)->IntValue = value; - } - - public FP GetFPData(FrameThreadSafe frame, Int32 index) - { - var nodesDataList = frame.ResolveList(BTDataValues); - return *nodesDataList.GetPointer(index)->FPValue; - } - - public Int32 GetIntData(FrameThreadSafe frame, Int32 index) - { - var nodesDataList = frame.ResolveList(BTDataValues); - return *nodesDataList.GetPointer(index)->IntValue; - } - #endregion - } -} \ No newline at end of file diff --git a/data/BTAgent.User.cs b/data/BTAgent.User.cs deleted file mode 100644 index 939e57af829869e2977fe042170a04ec81bee228..0000000000000000000000000000000000000000 --- a/data/BTAgent.User.cs +++ /dev/null @@ -1,235 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - public unsafe partial struct BTAgent - { - // Used to setup info on the Unity debugger - public string GetTreeAssetName(Frame frame) => frame.FindAsset(Tree.Id).Path; - - public bool IsAborting => AbortNodeId != 0; - - public AIConfig GetConfig(Frame frame) - { - return frame.FindAsset(Config.Id); - } - - public void Initialize(Frame frame, EntityRef entityRef, BTAgent* agent, AssetRefBTNode tree, bool force = false) - { - if (this.Tree != default && force == false) - return; - - // -- Cache the tree - BTRoot treeAsset = frame.FindAsset(tree.Id); - this.Tree = treeAsset; - - // -- Allocate data - // Success/Fail/Running - NodesStatus = frame.AllocateList(treeAsset.NodesAmount); - - // Next tick in which each service shall be updated - ServicesEndTimes = frame.AllocateList(4); - - // Node data, such as FP for timers, Integers for IDs - BTDataValues = frame.AllocateList(4); - - // The Services contained in the current sub-tree, - // which should be updated considering its intervals - ActiveServices = frame.AllocateList(4); - - // The Dynamic Composites contained in the current sub-tree, - // which should be re-checked every tick - DynamicComposites = frame.AllocateList(4); - - // -- Cache the Blackboard (if any) - AIBlackboardComponent* blackboard = null; - if (frame.Has(entityRef)) - { - blackboard = frame.Unsafe.GetPointer(entityRef); - } - - // -- Initialize the tree - treeAsset.InitializeTree(frame, agent, blackboard); - - // -- Trigger the debugging event (mostly for the Unity side) - BTManager.OnSetupDebugger?.Invoke(entityRef, treeAsset.Path); - } - - public void Free(Frame frame) - { - Tree = default; - frame.FreeList(NodesStatus); - frame.FreeList(ServicesEndTimes); - frame.FreeList(BTDataValues); - frame.FreeList(ActiveServices); - frame.FreeList(DynamicComposites); - } - - #region Int and FP Data - // Getter / Setters of node FP and Int32 data - public void AddFPData(Frame frame, FP fpValue) - { - var nodesDataList = frame.ResolveList(BTDataValues); - BTDataValue newDataValue = new BTDataValue(); - *newDataValue.FPValue = fpValue; - nodesDataList.Add(newDataValue); - } - - public void AddIntData(Frame frame, Int32 intValue) - { - var nodesDataList = frame.ResolveList(BTDataValues); - BTDataValue newDataValue = new BTDataValue(); - *newDataValue.IntValue = intValue; - nodesDataList.Add(newDataValue); - } - - public void SetFPData(Frame frame, FP value, Int32 index) - { - var nodesDataList = frame.ResolveList(BTDataValues); - *nodesDataList.GetPointer(index)->FPValue = value; - } - - public void SetIntData(Frame frame, Int32 value, Int32 index) - { - var nodesDataList = frame.ResolveList(BTDataValues); - *nodesDataList.GetPointer(index)->IntValue = value; - } - - public FP GetFPData(Frame frame, Int32 index) - { - var nodesDataList = frame.ResolveList(BTDataValues); - return *nodesDataList.GetPointer(index)->FPValue; - } - - public Int32 GetIntData(Frame frame, Int32 index) - { - var nodesDataList = frame.ResolveList(BTDataValues); - return *nodesDataList.GetPointer(index)->IntValue; - } - #endregion - - public void Update(ref BTParams btParams) - { - if (btParams.Agent->Current == null) - { - btParams.Agent->Current = btParams.Agent->Tree; - } - - RunDynamicComposites(btParams); - - BTNode node = btParams.Frame.FindAsset(btParams.Agent->Current.Id); - UpdateSubtree(btParams, node); - - BTManager.ClearBTParams(btParams); - } - - // We run the dynamic composites contained on the current sub-tree (if any) - // If any of them result in "False", we abort the current sub-tree - // and take the execution back to the topmost decorator so the agent can choose another path - private void RunDynamicComposites(BTParams btParams) - { - var frame = btParams.Frame; - var dynamicComposites = frame.ResolveList(DynamicComposites); - - for (int i = 0; i < dynamicComposites.Count; i++) - { - var compositeRef = dynamicComposites.GetPointer(i); - var composite = frame.FindAsset(compositeRef->Id); - var dynamicResult = composite.OnDynamicRun(btParams); - - if (dynamicResult == false) - { - btParams.Agent->Current = composite.TopmostDecorator; - dynamicComposites.Remove(*compositeRef); - composite.OnReset(btParams); - return; - } - } - } - - private void UpdateSubtree(BTParams btParams, BTNode node, bool continuingAbort = false) - { - // Start updating the tree from the Current agent's node - var result = node.RunUpdate(btParams, continuingAbort); - - // If the current node completes, go up in the tree until we hit a composite - // Run that one. On success or fail continue going up. - while (result != BTStatus.Running && node.Parent != null) - { - // As we are traversing the tree up, we allow nodes to remove any - // data that is only needed locally - node.OnExit(btParams); - - node = node.Parent; - if (node.NodeType == BTNodeType.Composite) - { - ((BTComposite)node).ChildCompletedRunning(btParams, result); - result = node.RunUpdate(btParams, continuingAbort); - } - - if (node.NodeType == BTNodeType.Decorator) - { - ((BTDecorator)node).EvaluateAbortNode(btParams); - } - } - - BTService.TickServices(btParams); - - if (result != BTStatus.Running) - { - BTNode tree = btParams.Frame.FindAsset(btParams.Agent->Tree.Id); - tree.OnReset(btParams); - btParams.Agent->Current = btParams.Agent->Tree; - BTManager.OnTreeCompleted?.Invoke(btParams.Entity); - //Log.Info("Behaviour Tree completed with result '{0}'. It will re-start from '{1}'", result, btParams.Agent->Current.Id); - } - } - - public unsafe void AbortLowerPriority(BTParams btParams, BTNode node) - { - // Go up and find the next interesting node (composite or root) - var topNode = node; - while ( - topNode.NodeType != BTNodeType.Composite && - topNode.NodeType != BTNodeType.Root) - { - topNode = topNode.Parent; - } - - if (topNode.NodeType == BTNodeType.Root) - { - return; - } - - var nodeAsComposite = (topNode as BTComposite); - nodeAsComposite.AbortNodes(btParams, nodeAsComposite.GetCurrentChild(btParams.Frame, btParams.Agent) + 1); - } - - // Used to react to blackboard changes which are observed by Decorators - // This is triggered by the Blackboard Entry itself, which has a list of Decorators that observes it - public unsafe void OnDecoratorReaction(BTParams btParams, BTNode node, BTAbort abort, out bool abortSelf, out bool abortLowerPriotity) - { - abortSelf = false; - abortLowerPriotity = false; - - var status = node.GetStatus(btParams.Frame, btParams.Agent); - - if (abort.IsSelf() && (status == BTStatus.Running || status == BTStatus.Inactive)) - { - // Check condition again - if (node.DryRun(btParams) == false) - { - abortSelf = true; - node.OnAbort(btParams); - } - } - - if (abort.IsLowerPriority()) - { - AbortLowerPriority(btParams, node); - abortLowerPriotity = true; - } - } - } -} \ No newline at end of file diff --git a/data/BTBlackboardCompare.cs b/data/BTBlackboardCompare.cs deleted file mode 100644 index 02c32c4ccdce6af73317022d609270e153a1df49..0000000000000000000000000000000000000000 --- a/data/BTBlackboardCompare.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; - -namespace Quantum -{ - /// - /// Reactive Decorator sample. Listens to changes on two Blackboard entries. - /// - [Serializable] - public unsafe class BTBlackboardCompare : BTDecorator - { - // We let the user define, on the Visual Editor, which Blackboard entries - // shall be observed by this Decorator - public AIBlackboardValueKey BlackboardKeyA; - public AIBlackboardValueKey BlackboardKeyB; - - public override void OnEnter(BTParams btParams) - { - base.OnEnter(btParams); - - // Whenever we enter this Decorator... - // We register it as a Reactive Decorator so, whenever the entries are changed, - // the DryRun is executed again, possibly aborting the current execution - btParams.Blackboard->RegisterReactiveDecorator(btParams.Frame, BlackboardKeyA.Key, this); - btParams.Blackboard->RegisterReactiveDecorator(btParams.Frame, BlackboardKeyB.Key, this); - } - - public override void OnExit(BTParams btParams) - { - base.OnExit(btParams); - // Whenever the execution goes higher, it means that this Decorator isn't in the current subtree anymore - // So we unregister this Decorator from the Reactive list. This means that if the Blackboard entries - // get changed, this Decorator will not react anymore - btParams.Blackboard->UnregisterReactiveDecorator(btParams.Frame, BlackboardKeyA.Key, this); - btParams.Blackboard->UnregisterReactiveDecorator(btParams.Frame, BlackboardKeyB.Key, this); - } - - // We just check if A is greater than B. If that's the case - // PS: this gets called in THREE possible situations: - // 1 - When the execution is goign DOWN on the tree and this Decorator is found - // 2 - If changes to the observed blackboard entries happen - // 3 - If this is inside a Dynamic Composite node - public override Boolean DryRun(BTParams btParams) - { - var blackboard = btParams.Blackboard; - var A = blackboard->GetInteger(btParams.Frame, BlackboardKeyA.Key); - var B = blackboard->GetInteger(btParams.Frame, BlackboardKeyB.Key); - - return A > B; - } - } -} diff --git a/data/BTComposite.cs b/data/BTComposite.cs deleted file mode 100644 index f9ad47e0130a2c0bb64e3ab436307200506ef484..0000000000000000000000000000000000000000 --- a/data/BTComposite.cs +++ /dev/null @@ -1,178 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - public unsafe abstract partial class BTComposite : BTNode - { - public AssetRefBTNode[] Children; - public AssetRefBTService[] Services; - [BotSDKHidden] public AssetRefBTNode TopmostDecorator; - - public BTDataIndex CurrentChildIndex; - - protected BTNode[] _childInstances; - protected BTService[] _serviceInstances; - protected BTNode _topmostDecoratorInstance; - - public bool IsDynamic; - - public BTNode[] ChildInstances - { - get - { - return _childInstances; - } - } - - public BTService[] ServiceInstances - { - get - { - return _serviceInstances; - } - } - - public override BTNodeType NodeType - { - get - { - return BTNodeType.Composite; - } - } - - internal Int32 GetCurrentChild(Frame frame, BTAgent* agent) - { - Byte currentChild = (Byte)agent->GetIntData(frame, CurrentChildIndex.Index); - return currentChild; - } - - internal void SetCurrentChild(Frame frame, Int32 currentIndex, BTAgent* agent) - { - agent->SetIntData(frame, currentIndex, CurrentChildIndex.Index); - } - - /// - /// When a Composite node is Updated, it only increase the current child updated - /// when the child results in either FAIL/SUCCESS. So we need this callback - /// to be used when the child was RUNNING and then had some result, to properly increase the current - /// child ID - /// - /// - /// - internal virtual void ChildCompletedRunning(BTParams btParams, BTStatus childResult) - { - } - - public override void Init(Frame frame, AIBlackboardComponent* blackboard, BTAgent* agent) - { - base.Init(frame, blackboard, agent); - - agent->AddIntData(frame, 0); - - for (Int32 i = 0; i < Services.Length; i++) - { - BTService service = frame.FindAsset(Services[i].Id); - service.Init(frame, agent, blackboard); - } - } - - public override void OnEnter(BTParams btParams) - { - BTManager.OnNodeEnter?.Invoke(btParams.Entity, Guid.Value); - SetCurrentChild(btParams.Frame, 0, btParams.Agent); - } - - public override void OnEnterRunning(BTParams btParams) - { - var activeServicesList = btParams.Frame.ResolveList(btParams.Agent->ActiveServices); - - for (Int32 i = 0; i < _serviceInstances.Length; i++) - { - _serviceInstances[i].OnEnter(btParams); - - activeServicesList.Add(Services[i]); - } - - if (IsDynamic == true) - { - var dynamicComposites = btParams.Frame.ResolveList(btParams.Agent->DynamicComposites); - dynamicComposites.Add(this); - } - } - - public override void OnReset(BTParams btParams) - { - base.OnReset(btParams); - - OnExit(btParams); - - for (Int32 i = 0; i < _childInstances.Length; i++) - _childInstances[i].OnReset(btParams); - } - - public override void OnExit(BTParams btParams) - { - base.OnExit(btParams); - - BTManager.OnNodeExit?.Invoke(btParams.Entity, Guid.Value); - - var activeServicesList = btParams.Frame.ResolveList(btParams.Agent->ActiveServices); - for (Int32 i = 0; i < _serviceInstances.Length; i++) - { - activeServicesList.Remove(Services[i]); - } - - if (IsDynamic == true) - { - var dynamicComposites = btParams.Frame.ResolveList(btParams.Agent->DynamicComposites); - dynamicComposites.Remove(this); - } - } - - public override bool OnDynamicRun(BTParams btParams) - { - if (_topmostDecoratorInstance != null) - { - return _topmostDecoratorInstance.OnDynamicRun(btParams); - } - - return true; - } - - - public void AbortNodes(BTParams btParams, Int32 firstIndex = 0) - { - for (int i = firstIndex; i < _childInstances.Length; i++) - { - _childInstances[i].SetStatus(btParams.Frame, BTStatus.Abort, btParams.Agent); - } - } - - public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator) - { - base.Loaded(resourceManager, allocator); - - // Cache the child assets links - _childInstances = new BTNode[Children.Length]; - for (Int32 i = 0; i < Children.Length; i++) - { - _childInstances[i] = (BTNode)resourceManager.GetAsset(Children[i].Id); - _childInstances[i].Parent = this; - _childInstances[i].ParentIndex = i; - } - - // Cache the service assets links - _serviceInstances = new BTService[Services.Length]; - for (Int32 i = 0; i < Services.Length; i++) - { - _serviceInstances[i] = (BTService)resourceManager.GetAsset(Services[i].Id); - } - - if (TopmostDecorator != null) - { - _topmostDecoratorInstance = (BTDecorator)resourceManager.GetAsset(TopmostDecorator.Id); - } - } - } -} \ No newline at end of file diff --git a/data/BTCooldown.cs b/data/BTCooldown.cs deleted file mode 100644 index c12290bacf768d52f88cfe06fd0115d27b6550da..0000000000000000000000000000000000000000 --- a/data/BTCooldown.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - [Serializable] - public unsafe partial class BTCooldown : BTDecorator - { - // How many time should we wait - public FP CooldownTime; - - // An indexer so we know when the time started counting - public BTDataIndex StartTimeIndex; - - public override void Init(Frame frame, AIBlackboardComponent* blackboard, BTAgent* agent) - { - base.Init(frame, blackboard, agent); - - // We allocate space on the BTAgent so we can store the Start Time - agent->AddFPData(frame, 0); - } - - protected override BTStatus OnUpdate(BTParams btParams) - { - var result = base.OnUpdate(btParams); - - // We let the time check, which happens on the DryRun, happen - // If it results in success, then we store on the BTAgent the time value of the moment that it happened - if (result == BTStatus.Success) - { - var currentTime = btParams.Frame.DeltaTime * btParams.Frame.Number; - - var frame = btParams.Frame; - var entity = btParams.Entity; - btParams.Agent->SetFPData(frame, currentTime, StartTimeIndex.Index); - } - - return result; - } - - // We get the Start Time stored on the BTAgent, then we check if the time + cooldown is already over - // If it is not over, then we return False, blocking the execution of the children nodes - public override Boolean DryRun(BTParams btParams) - { - var frame = btParams.Frame; - var entity = btParams.Entity; - FP startTime = btParams.Agent->GetFPData(frame, StartTimeIndex.Index); - - var currentTime = btParams.Frame.DeltaTime * btParams.Frame.Number; - - return currentTime >= startTime + CooldownTime; - } - } -} diff --git a/data/BTDataIndex.User.cs b/data/BTDataIndex.User.cs deleted file mode 100644 index 405e9c915181148f71ac25ed23f71ad41928cb04..0000000000000000000000000000000000000000 --- a/data/BTDataIndex.User.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Quantum -{ - [BotSDKHidden] - [Serializable] - // Used so we can track it easier on the Visual Editor - partial struct BTDataIndex - { - } -} diff --git a/data/BTDecorator.cs b/data/BTDecorator.cs deleted file mode 100644 index b0538c300bb33d581b9dbdcc7e23e61d3557e438..0000000000000000000000000000000000000000 --- a/data/BTDecorator.cs +++ /dev/null @@ -1,97 +0,0 @@ -using Photon.Deterministic; - -namespace Quantum -{ - public abstract unsafe partial class BTDecorator : BTNode - { - [BotSDKHidden] public AssetRefBTNode Child; - protected BTNode _childInstance; - public BTAbort AbortType; - - public BTNode ChildInstance - { - get - { - return _childInstance; - } - } - - public override BTNodeType NodeType - { - get - { - return BTNodeType.Decorator; - } - } - - public override void OnReset(BTParams btParams) - { - base.OnReset(btParams); - - OnExit(btParams); - - if (_childInstance != null) - _childInstance.OnReset(btParams); - - BTManager.OnDecoratorReset?.Invoke(btParams.Entity, Guid.Value); - } - - public override void OnExit(BTParams btParams) - { - base.OnExit(btParams); - } - - protected override BTStatus OnUpdate(BTParams btParams) - { - if (DryRun(btParams) == true) - { - BTManager.OnDecoratorChecked?.Invoke(btParams.Entity, Guid.Value, true); - - if (_childInstance != null) - { - var childResult = _childInstance.RunUpdate(btParams); - if (childResult == BTStatus.Abort) - { - EvaluateAbortNode(btParams); - SetStatus(btParams.Frame, BTStatus.Abort, btParams.Agent); - return BTStatus.Abort; - } - - return childResult; - } - - return BTStatus.Success; - } - - BTManager.OnDecoratorChecked?.Invoke(btParams.Entity, Guid.Value, false); - - return BTStatus.Failure; - } - - public override bool OnDynamicRun(BTParams btParams) - { - var result = DryRun(btParams); - if (result == false) - { - return false; - } - else if (ChildInstance.NodeType != BTNodeType.Decorator) - { - return true; - } - else - { - return ChildInstance.OnDynamicRun(btParams); - } - } - - public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator) - { - base.Loaded(resourceManager, allocator); - - // Cache the child - _childInstance = (BTNode)resourceManager.GetAsset(Child.Id); - _childInstance.Parent = this; - } - } -} \ No newline at end of file diff --git a/data/BTForceResult.cs b/data/BTForceResult.cs deleted file mode 100644 index 96c3a7915787ced5882eaf4655ca4c56d9ac8df9..0000000000000000000000000000000000000000 --- a/data/BTForceResult.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace Quantum -{ - [Serializable] - public unsafe partial class BTForceResult : BTDecorator - { - public BTStatus Result; - - protected override BTStatus OnUpdate(BTParams btParams) - { - if (_childInstance != null) - _childInstance.RunUpdate(btParams); - - return Result; - } - - public override Boolean DryRun(BTParams btParams) - { - return true; - } - } -} diff --git a/data/BTLeaf.cs b/data/BTLeaf.cs deleted file mode 100644 index 932bf607a008b9a66ca64685560cc90072620a95..0000000000000000000000000000000000000000 --- a/data/BTLeaf.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - public unsafe abstract partial class BTLeaf : BTNode - { - public AssetRefBTService[] Services; - protected BTService[] _serviceInstances; - public BTService[] ServiceInstances - { - get - { - return _serviceInstances; - } - } - - public override BTNodeType NodeType - { - get - { - return BTNodeType.Leaf; - } - } - - public override unsafe void Init(Frame frame, AIBlackboardComponent* blackboard, BTAgent* agent) - { - base.Init(frame, blackboard, agent); - - for (int i = 0; i < Services.Length; i++) - { - BTService service = frame.FindAsset(Services[i].Id); - service.Init(frame, agent, blackboard); - } - } - - public override void OnEnterRunning(BTParams btParams) - { - var activeServicesList = btParams.Frame.ResolveList(btParams.Agent->ActiveServices); - for (int i = 0; i < _serviceInstances.Length; i++) - { - _serviceInstances[i].OnEnter(btParams); - activeServicesList.Add(Services[i]); - } - } - - public override void OnEnter(BTParams btParams) - { - base.OnEnter(btParams); - BTManager.OnNodeEnter?.Invoke(btParams.Entity, Guid.Value); - } - - public override void OnExit(BTParams btParams) - { - var activeServicesList = btParams.Frame.ResolveList(btParams.Agent->ActiveServices); - for (Int32 i = 0; i < _serviceInstances.Length; i++) - { - activeServicesList.Remove(Services[i]); - } - - BTManager.OnNodeExit?.Invoke(btParams.Entity, Guid.Value); - } - - public override void OnReset(BTParams btParams) - { - base.OnReset(btParams); - OnExit(btParams); - } - - public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator) - { - base.Loaded(resourceManager, allocator); - - // Cache the service assets links - _serviceInstances = new BTService[Services.Length]; - for (int i = 0; i < Services.Length; i++) - { - _serviceInstances[i] = (BTService)resourceManager.GetAsset(Services[i].Id); - } - } - } -} \ No newline at end of file diff --git a/data/BTLoop.cs b/data/BTLoop.cs deleted file mode 100644 index 7f244ed555cecd76d970d1b4e78fa7408a8aea16..0000000000000000000000000000000000000000 --- a/data/BTLoop.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - [Serializable] - public unsafe partial class BTLoop : BTDecorator - { - public Int32 LoopIterations; - public Boolean LoopForever; - public FP LoopTimeout = -FP._1; - - public BTDataIndex StartTimeIndex; - public BTDataIndex IterationCountIndex; - - public override void Init(Frame frame, AIBlackboardComponent* blackboard, BTAgent* agent) - { - base.Init(frame, blackboard, agent); - - agent->AddFPData(frame, 0); - agent->AddIntData(frame, 0); - } - - public override void OnEnter(BTParams btParams) - { - base.OnEnter(btParams); - - var frame = btParams.Frame; - var currentTime = frame.DeltaTime * frame.Number; - - btParams.Agent->SetFPData(frame, currentTime, StartTimeIndex.Index); - btParams.Agent->SetIntData(frame, 0, IterationCountIndex.Index); - } - - protected override BTStatus OnUpdate(BTParams btParams) - { - var frame = btParams.Frame; - - int iteration = btParams.Agent->GetIntData(frame, IterationCountIndex.Index) + 1; - btParams.Agent->SetIntData(frame, iteration, IterationCountIndex.Index); - - if (DryRun(btParams) == false) - { - return BTStatus.Success; - } - - var childResult = BTStatus.Failure; - if (_childInstance != null) - { - _childInstance.SetStatus(btParams.Frame, BTStatus.Inactive, btParams.Agent); - childResult = _childInstance.RunUpdate(btParams); - } - - return childResult; - } - - public override Boolean DryRun(BTParams btParams) - { - if (LoopForever && LoopTimeout < FP._0) - { - return true; - } - else if (LoopForever) - { - var frame = btParams.Frame; - FP startTime = btParams.Agent->GetFPData(frame, StartTimeIndex.Index); - - var currentTime = frame.DeltaTime * frame.Number; - if (currentTime < startTime + LoopTimeout) - { - return true; - } - } - else - { - var frame = btParams.Frame; - int iteration = btParams.Agent->GetIntData(frame, IterationCountIndex.Index); - if (iteration <= LoopIterations) - { - return true; - } - } - - return false; - } - } -} diff --git a/data/BTNode.cs b/data/BTNode.cs deleted file mode 100644 index 32aaa1c793e15e484402187c2038566673cc46f5..0000000000000000000000000000000000000000 --- a/data/BTNode.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; - -namespace Quantum -{ - - public unsafe abstract partial class BTNode - { - - [BotSDKHidden] public String Label; - [BotSDKHidden] public Int32 Id; - - [NonSerialized] internal BTNode Parent; - [NonSerialized] internal Int32 ParentIndex; - - public abstract BTNodeType NodeType { get; } - - /// - /// Called once, for every Node, when the BT is being initialized - /// - public virtual void Init(Frame frame, AIBlackboardComponent* blackboard, BTAgent* agent) - { - var statusList = frame.ResolveList(agent->NodesStatus); - statusList.Add(0); - } - - // -- STATUS -- - public BTStatus GetStatus(Frame frame, BTAgent* agent) - { - var nodesAndStatus = frame.ResolveList(agent->NodesStatus); - return (BTStatus)nodesAndStatus[Id]; - } - - public void SetStatus(Frame frame, BTStatus status, BTAgent* agent) - { - var nodesAndStatus = frame.ResolveList(agent->NodesStatus); - nodesAndStatus[Id] = (Byte)status; - } - - /// - /// Called whenever the BT execution includes this node as part of the current context - /// - /// - public virtual void OnEnter(BTParams btParams) { } - - public virtual void OnEnterRunning(BTParams btParams) { } - - /// - /// Called when traversing the tree upwards and the node is already finished with its job. - /// Used by Composites and Leafs to remove their Services from the list of active services - /// as it is not anymore part of the current subtree. - /// Dynamic Composites also remove themselves - /// - /// - public virtual void OnExit(BTParams btParams) { } - - public virtual void OnAbort(BTParams btParams) - { - } - - /// - /// Called when getting out of a sub-branch and this node is being discarded - /// - /// - public unsafe virtual void OnReset(BTParams btParams) - { - SetStatus(btParams.Frame, BTStatus.Inactive, btParams.Agent); - } - - public void EvaluateAbortNode(BTParams btParams) - { - if (btParams.Agent->AbortNodeId == Id) - { - btParams.Agent->AbortNodeId = 0; - } - } - - public BTStatus RunUpdate(BTParams btParams, bool continuingAbort = false) - { - var oldStatus = GetStatus(btParams.Frame, btParams.Agent); - - if (oldStatus == BTStatus.Success || oldStatus == BTStatus.Failure) - { - return oldStatus; - } - - if (oldStatus == BTStatus.Abort) - { - if (btParams.Agent->IsAborting == true) - { - EvaluateAbortNode(btParams); - } - return oldStatus; - } - - // If this node was inactive, this means that we're entering on it for the first time, so we call OnEnter - // An exception from this rule is when we chose this node to continue an abort process. In that case, - // we already executed OnEnter before, so we don't repeat it - if (oldStatus == BTStatus.Inactive && continuingAbort == false) - { - OnEnter(btParams); - } - - var newStatus = BTStatus.Failure; - try - { - newStatus = OnUpdate(btParams); - - if (btParams.Agent->IsAborting) - { - newStatus = BTStatus.Abort; - } - - // Used for debugging purposes - if (newStatus == BTStatus.Success) - { - BTManager.OnNodeSuccess?.Invoke(btParams.Entity, Guid.Value); - BTManager.OnNodeExit?.Invoke(btParams.Entity, Guid.Value); - } - - if (newStatus == BTStatus.Failure) - { - BTManager.OnNodeFailure?.Invoke(btParams.Entity, Guid.Value); - BTManager.OnNodeExit?.Invoke(btParams.Entity, Guid.Value); - } - } - catch (Exception e) - { - Log.Error("Exception in Behaviour Tree node '{0}' ({1}) - setting node status to Failure", Label, Guid); - Log.Exception(e); - } - - SetStatus(btParams.Frame, newStatus, btParams.Agent); - - if ((newStatus == BTStatus.Running || newStatus == BTStatus.Success) && - (oldStatus == BTStatus.Failure || oldStatus == BTStatus.Inactive)) - { - OnEnterRunning(btParams); - } - - if (newStatus == BTStatus.Running && NodeType == BTNodeType.Leaf) - { - // If we are a leaf, we can store the current node - // We know that there has only one leaf node running at any time, no parallel branches possible - // The Run() method also return a tuple - btParams.Agent->Current = this; - } - - return newStatus; - } - - /// - /// Used by Decorators to evaluate if a condition succeeds or not. - /// Upon success, allow the flow to continue. - /// Upon failure, blocks the execution so another path is taken - /// - /// - /// - public virtual Boolean DryRun(BTParams btParams) - { - return false; - } - - public virtual Boolean OnDynamicRun(BTParams btParams) - { - return true; - } - - /// - /// Called every tick while this Node is part of the current sub-tree. - /// Returning "Success/Failure" will make the tree continue its execution. - /// Returning "Running" will store this Node as the Current Node and re-execute it on the next frame - /// unless something else interrputs - /// - /// - /// - protected abstract BTStatus OnUpdate(BTParams btParams); - } -} \ No newline at end of file diff --git a/data/BTNodeType.cs b/data/BTNodeType.cs deleted file mode 100644 index 4beed57a5881615e1ae244cfea232f4bd6370f32..0000000000000000000000000000000000000000 --- a/data/BTNodeType.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Quantum -{ - public enum BTNodeType - { - Root, - Leaf, - Decorator, - Composite, - Service - } -} diff --git a/data/BTParams.cs b/data/BTParams.cs deleted file mode 100644 index dbb4ecd3319106eadf8f3f4b46bb4ecfa510a770..0000000000000000000000000000000000000000 --- a/data/BTParams.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Quantum -{ - [StructLayout(LayoutKind.Auto)] - public unsafe partial struct BTParams - { - private Frame _frame; - private BTAgent* _agent; - private EntityRef _entity; - private AIBlackboardComponent* _blackboard; - - private BTParamsUser _userParams; - - public Frame Frame { get => _frame; } - public BTAgent* Agent { get => _agent; } - public EntityRef Entity { get => _entity; } - public AIBlackboardComponent* Blackboard { get => _blackboard; } - - public BTParamsUser UserParams { get => _userParams; set => _userParams = value; } - - public void SetDefaultParams(Frame frame, BTAgent* agent, EntityRef entity, AIBlackboardComponent* blackboard = null) - { - _frame = frame; - _agent = agent; - _entity = entity; - _blackboard = blackboard; - } - - public void Reset(Frame frame) - { - _frame = default; - _agent = default; - _entity = default; - _blackboard = default; - - _userParams = default; - } - } - - public partial struct BTParamsUser - { - } -} \ No newline at end of file diff --git a/data/BTRoot.cs b/data/BTRoot.cs deleted file mode 100644 index 309e949742a951b7db614c9a23112b7d94104b7e..0000000000000000000000000000000000000000 --- a/data/BTRoot.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Photon.Deterministic; -using System; -using System.Collections.Generic; - -namespace Quantum -{ - [Serializable] - public unsafe partial class BTRoot : BTDecorator - { - [BotSDKHidden] public Int32 NodesAmount; - - public override BTNodeType NodeType - { - get - { - return BTNodeType.Root; - } - } - - protected unsafe override BTStatus OnUpdate(BTParams btParams) - { - - btParams.Agent->Current = this; - - if (_childInstance != null) - { - return _childInstance.RunUpdate(btParams); - } - - return BTStatus.Success; - } - - public void InitializeTree(Frame frame, BTAgent* agent, AIBlackboardComponent* blackboard) - { - InitNodesRecursively(frame, this, agent, blackboard); - } - - private static void InitNodesRecursively(Frame frame, BTNode node, BTAgent* agent, AIBlackboardComponent* blackboard) - { - node.Init(frame, blackboard, agent); - - if (node is BTDecorator decoratorNode) - { - BTNode childNode = frame.FindAsset(decoratorNode.Child.Id); - InitNodesRecursively(frame, childNode, agent, blackboard); - } - - if (node is BTComposite compositeNode) - { - foreach (var child in compositeNode.Children) - { - BTNode childNode = frame.FindAsset(child.Id); - InitNodesRecursively(frame, childNode, agent, blackboard); - } - } - } - } -} \ No newline at end of file diff --git a/data/BTSelector.cs b/data/BTSelector.cs deleted file mode 100644 index 5fedaea6e4533b9223527e304dccce4862eb0109..0000000000000000000000000000000000000000 --- a/data/BTSelector.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; - -namespace Quantum -{ - - /// - /// The selector task is similar to an or operation. It will return success as soon as one of its child tasks return success. - /// If a child task returns failure then it will sequentially run the next task. If no child task returns success then it will return failure. - /// - [Serializable] - public unsafe partial class BTSelector : BTComposite - { - protected override BTStatus OnUpdate(BTParams btParams) - { - BTStatus status = BTStatus.Success; - - while (GetCurrentChild(btParams.Frame, btParams.Agent) < _childInstances.Length) - { - var currentChildId = GetCurrentChild(btParams.Frame, btParams.Agent); - var child = _childInstances[currentChildId]; - status = child.RunUpdate(btParams); - - if (status == BTStatus.Abort && btParams.Agent->IsAborting == true) - { - return BTStatus.Abort; - } - - if (status == BTStatus.Failure || status == BTStatus.Abort) - { - SetCurrentChild(btParams.Frame, currentChildId + 1, btParams.Agent); - } - else - break; - } - - return status; - } - - internal override void ChildCompletedRunning(BTParams btParams, BTStatus childResult) - { - if (childResult == BTStatus.Abort) - { - return; - } - - if (childResult == BTStatus.Failure) - { - var currentChild = GetCurrentChild(btParams.Frame, btParams.Agent); - SetCurrentChild(btParams.Frame, currentChild + 1, btParams.Agent); - } - else - { - SetCurrentChild(btParams.Frame, _childInstances.Length, btParams.Agent); - - // If the child succeeded, then we already know that this sequence succeeded, so we can force it - SetStatus(btParams.Frame, BTStatus.Success, btParams.Agent); - - // Trigger the debugging callbacks - BTManager.OnNodeSuccess?.Invoke(btParams.Entity, Guid.Value); - BTManager.OnNodeExit?.Invoke(btParams.Entity, Guid.Value); - } - } - } -} \ No newline at end of file diff --git a/data/BTSequence.cs b/data/BTSequence.cs deleted file mode 100644 index deefa3674565505d4bccd50ac5a7d69675781684..0000000000000000000000000000000000000000 --- a/data/BTSequence.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; - -namespace Quantum -{ - /// - /// The sequence task is similar to an and operation. It will return failure as soon as one of its child tasks return failure. - /// If a child task returns success then it will sequentially run the next task. If all child tasks return success then it will return success. - /// - [Serializable] - public unsafe partial class BTSequence : BTComposite - { - protected override BTStatus OnUpdate(BTParams btParams) - { - BTStatus status = BTStatus.Success; - - while (GetCurrentChild(btParams.Frame, btParams.Agent) < _childInstances.Length) - { - var currentChildId = GetCurrentChild(btParams.Frame, btParams.Agent); - var child = _childInstances[currentChildId]; - status = child.RunUpdate(btParams); - - if (status == BTStatus.Abort) - { - if (btParams.Agent->IsAborting == true) - { - return BTStatus.Abort; - } - else - { - return BTStatus.Failure; - } - } - - if (status == BTStatus.Success) - { - SetCurrentChild(btParams.Frame, currentChildId + 1, btParams.Agent); - } - else - { - break; - } - } - - return status; - } - - internal override void ChildCompletedRunning(BTParams btParams, BTStatus childResult) - { - if (childResult == BTStatus.Abort) - { - return; - } - - if (childResult == BTStatus.Failure) - { - SetCurrentChild(btParams.Frame, _childInstances.Length, btParams.Agent); - - // If the child failed, then we already know that this sequence failed, so we can force it - SetStatus(btParams.Frame, BTStatus.Failure, btParams.Agent); - - // Trigger the debugging callbacks - BTManager.OnNodeFailure?.Invoke(btParams.Entity, Guid.Value); - BTManager.OnNodeExit?.Invoke(btParams.Entity, Guid.Value); - } - else - { - var currentChild = GetCurrentChild(btParams.Frame, btParams.Agent); - SetCurrentChild(btParams.Frame, currentChild + 1, btParams.Agent); - } - } - } -} \ No newline at end of file diff --git a/data/BTService.cs b/data/BTService.cs deleted file mode 100644 index 2aa85a95189c5c88f27dcda5c100bd2c9122fdbe..0000000000000000000000000000000000000000 --- a/data/BTService.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - public unsafe abstract partial class BTService - { - public FP IntervalInSec; - - [BotSDKHidden] public Int32 Id; - - public virtual void Init(Frame frame, BTAgent* agent, AIBlackboardComponent* blackboard) - { - var endTimesList = frame.ResolveList(agent->ServicesEndTimes); - endTimesList.Add(0); - } - - public void SetEndTime(Frame frame, BTAgent* agent) - { - var endTimesList = frame.ResolveList(agent->ServicesEndTimes); - endTimesList[Id] = frame.BotSDKGameTime + IntervalInSec; - } - - public FP GetEndTime(Frame frame, BTAgent* agent) - { - var endTime = frame.ResolveList(agent->ServicesEndTimes); - return endTime[Id]; - } - - public virtual void RunUpdate(BTParams btParams) - { - var endTime = GetEndTime(btParams.Frame, btParams.Agent); - if (btParams.Frame.BotSDKGameTime >= endTime) - { - OnUpdate(btParams); - SetEndTime(btParams.Frame, btParams.Agent); - } - } - - public virtual void OnEnter(BTParams btParams) - { - SetEndTime(btParams.Frame, btParams.Agent); - } - - /// - /// Called whenever the Service is part of the current subtree - /// and its waiting time is already over - /// - protected abstract void OnUpdate(BTParams btParams); - - public static void TickServices(BTParams btParams) - { - var activeServicesList = btParams.Frame.ResolveList(btParams.Agent->ActiveServices); - - for (int i = 0; i < activeServicesList.Count; i++) - { - var service = btParams.Frame.FindAsset(activeServicesList[i].Id); - try - { - service.RunUpdate(btParams); - } - catch (Exception e) - { - Log.Error("Exception in Behaviour Tree service '{0}' ({1}) - setting node status to Failure", service.GetType().ToString(), service.Guid); - Log.Exception(e); - } - } - } - } -} diff --git a/data/BTStatus.cs b/data/BTStatus.cs deleted file mode 100644 index a92ccf6216e53a4add2d5ab2e170175e354204c4..0000000000000000000000000000000000000000 --- a/data/BTStatus.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Quantum -{ - public enum BTStatus - { - Inactive, - Success, - Failure, - Running, - Abort - } -} diff --git a/data/BallPoolSpec.cs b/data/BallPoolSpec.cs deleted file mode 100644 index ad2aa7fcd2b21942da62740ccbe4c967c8244793..0000000000000000000000000000000000000000 --- a/data/BallPoolSpec.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - unsafe partial class BallPoolSpec - { - public FP SpinMultiplier; - public FP EndOfMovementVelocityThreshold; - public Int32 EndOfMovementWaitingInTicks; - - public Int32 Layer { get; set; } - } -} diff --git a/data/BehaviourTree.Manager.cs b/data/BehaviourTree.Manager.cs deleted file mode 100644 index 155d20154887eead4b2d7f60f291eeef2797d028..0000000000000000000000000000000000000000 --- a/data/BehaviourTree.Manager.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - public static unsafe partial class BTManager - { - public static Action OnSetupDebugger; - - public static Action OnNodeEnter; - public static Action OnNodeExit; - public static Action OnNodeSuccess; - public static Action OnNodeFailure; - public static Action OnDecoratorChecked; - public static Action OnDecoratorReset; - public static Action OnTreeCompleted; - - /// - /// Call this once, to initialize the BTAgent. - /// This method internally looks for a Blackboard Component on the entity - /// and passes it down the pipeline. - /// - /// - /// - /// - public static void Init(Frame frame, EntityRef entity, BTRoot root) - { - if (frame.Unsafe.TryGetPointer(entity, out BTAgent* agent)) - { - agent->Initialize(frame, entity, agent, root, true); - } - else - { - Log.Error("[Bot SDK] Tried to initialize an entity which has no BTAgent component"); - } - } - - /// - /// Made for internal use only. - /// - public static void ClearBTParams(BTParams btParams) - { - btParams.Reset(btParams.Frame); - } - - /// - /// Call this method every frame to update your BT Agent. - /// You can optionally pass a Blackboard Component to it, if your Agent use it - /// - public static void Update(Frame frame, EntityRef entity, AIBlackboardComponent* blackboard = null) - { - var agent = frame.Unsafe.GetPointer(entity); - BTParams btParams = new BTParams(); - btParams.SetDefaultParams(frame, agent, entity, blackboard); - - agent->Update(ref btParams); - } - - /// - /// CAUTION: Use this overload with care.
- /// It allows the definition of custom parameters which are passed through the entire BT pipeline, for easy access.
- /// The user parameters struct needs to be created from scratch every time BEFORE calling the BT Update method.
- /// Make sure to also implement BTParamsUser.ClearUser(frame). - ///
- /// Used to define custom user data. It needs to be created from scratch every time before calling this method. - public static void Update(Frame frame, EntityRef entity, ref BTParamsUser userParams, AIBlackboardComponent* blackboard = null) - { - var agent = frame.Unsafe.GetPointer(entity); - BTParams btParams = new BTParams(); - btParams.SetDefaultParams(frame, agent, entity, blackboard); - btParams.UserParams = userParams; - - agent->Update(ref btParams); - } - } -} diff --git a/data/Blackboard.qtn b/data/Blackboard.qtn deleted file mode 100644 index 808cf6696b6a9604e429e83252af79432f526d5d..0000000000000000000000000000000000000000 --- a/data/Blackboard.qtn +++ /dev/null @@ -1,23 +0,0 @@ -asset AIBlackboard; -asset AIBlackboardInitializer; - -union BlackboardValue { - QBoolean BooleanValue; - byte ByteValue; - Int32 IntegerValue; - FP FPValue; - FPVector2 FPVector2Value; - FPVector3 FPVector3Value; - entity_ref EntityRefValue; -} - -component AIBlackboardComponent { - asset_ref Board; - list Entries; -} - - -struct BlackboardEntry{ - BlackboardValue Value; - list ReactiveDecorators; -} \ No newline at end of file diff --git a/data/BlackboardEntry.cs b/data/BlackboardEntry.cs deleted file mode 100644 index da9c654cc0d35b6efc3e7489b6eb56af58356b20..0000000000000000000000000000000000000000 --- a/data/BlackboardEntry.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Quantum.Collections; - -namespace Quantum -{ - public unsafe partial struct BlackboardEntry - { - /// - /// Iterate through all Decorators that watches this Blackboard entry - /// Re-check the Decorators so it can check if an abort is needed - /// - /// - public void TriggerDecorators(BTParams btParams) - { - var frame = btParams.Frame; - - // If the reactive decorators list was already allocated... - if (ReactiveDecorators.Ptr != default) - { - // Solve it and trigger the decorators checks - var reactiveDecorators = frame.ResolveList(ReactiveDecorators); - for (int i = 0; i < reactiveDecorators.Count; i++) - { - var reactiveDecoratorRef = reactiveDecorators[i]; - var decoratorInstance = frame.FindAsset(reactiveDecoratorRef.Id); - btParams.Agent->OnDecoratorReaction(btParams, decoratorInstance, decoratorInstance.AbortType, out bool abortSelf, out bool abortLowerPriority); - - // If at least one Decorator resulted in abort, we stop and return already - if (abortSelf == true) - { - btParams.Agent->AbortNodeId = decoratorInstance.Id; - } - } - } - } - } -} diff --git a/data/BlackboardValue.cs b/data/BlackboardValue.cs deleted file mode 100644 index e4295de84119eb60215bd73db727e2074c6efee3..0000000000000000000000000000000000000000 --- a/data/BlackboardValue.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; - -namespace Quantum -{ - public unsafe partial struct BlackboardValue - { - public String ValueToString() - { - switch (Field) - { - case BlackboardValue.BOOLEANVALUE: return string.Format("{0}", _BooleanValue); - case BlackboardValue.BYTEVALUE: return string.Format("{0}", _ByteValue); - case BlackboardValue.INTEGERVALUE: return string.Format("{0}", _IntegerValue); - case BlackboardValue.FPVALUE: return string.Format("{0}", _FPValue); - case BlackboardValue.FPVECTOR2VALUE: return string.Format("{0}", _FPVector2Value); - case BlackboardValue.FPVECTOR3VALUE: return string.Format("{0}", _FPVector3Value); - case BlackboardValue.ENTITYREFVALUE: return string.Format("{0}", _EntityRefValue); - } - - return base.ToString(); - } - } - - public unsafe partial struct BlackboardValue - { - public String TypeToString() - { - switch (Field) - { - case BlackboardValue.BOOLEANVALUE: return "Boolean"; - case BlackboardValue.BYTEVALUE: return "Byte"; - case BlackboardValue.INTEGERVALUE: return "Integer"; - case BlackboardValue.FPVALUE: return "FP"; - case BlackboardValue.FPVECTOR2VALUE: return "Vector2"; - case BlackboardValue.FPVECTOR3VALUE: return "Vector3"; - case BlackboardValue.ENTITYREFVALUE: return "EntityRef"; - } - - return base.ToString(); - } - } -} diff --git a/data/BotSDK.Frame.User.cs b/data/BotSDK.Frame.User.cs deleted file mode 100644 index 6c70abd4b86b294528783f071ce70c9d8fc97b28..0000000000000000000000000000000000000000 --- a/data/BotSDK.Frame.User.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Photon.Deterministic; - -namespace Quantum -{ - public unsafe partial class Frame - { - // If you are interested, check BotSDKTimerSystem in order to see how the time counter logic is implemented - internal FP BotSDKGameTime - { - get - { - // Try to get a game time value from a user implementation - // If the value is not greater thane zero, either because there is no implementation - // or because it calculates the time wrongly, it will use the default Bot SDK - // time polling calculus - FP gameTime = -FP._1; - CalculateBotSDKGameTime(ref gameTime); - if (gameTime >= FP._0) - { - return gameTime; - } - - // We use division of integers in order to avoid accuracy issues with multiplications with DeltaTime - return (FP)Global->BotSDKData.ElapsedTicks / SessionConfig.UpdateFPS; - } - } - - // Method meant to be used for user implementation of the time counter, if needed - // Store the calculation result on the gameTime variable. The result has to be greater than zero - partial void CalculateBotSDKGameTime(ref FP gameTime); - } -} diff --git a/data/BotSDK.qtn b/data/BotSDK.qtn deleted file mode 100644 index b5ccd117fbdfacf6557ea13b336b4428c78a8920..0000000000000000000000000000000000000000 --- a/data/BotSDK.qtn +++ /dev/null @@ -1,11 +0,0 @@ -struct BotSDKData -{ - FP OriginalDeltaTime; - Int32 ElapsedTicks; - FP ElapsedPartialTicks; -} - -global -{ - BotSDKData BotSDKData; -} \ No newline at end of file diff --git a/data/BotSDKCompilerCallbacks.cs b/data/BotSDKCompilerCallbacks.cs deleted file mode 100644 index e67161974b4ee4f703df4af3d29c5c5563702cc9..0000000000000000000000000000000000000000 --- a/data/BotSDKCompilerCallbacks.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Quantum -{ - public static class BotSDKCompilerCallbacks - { - public static System.Action HFSMCompiled; - public static System.Action BTCompiled; - public static System.Action UTCompiled; - public static System.Action GOAPCompiled; - } -} diff --git a/data/BotSDKDebuggerSystem.cs b/data/BotSDKDebuggerSystem.cs deleted file mode 100644 index 488100b0f16017e68d1bc7ae48f9dd203d2dcce5..0000000000000000000000000000000000000000 --- a/data/BotSDKDebuggerSystem.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; - -namespace Quantum -{ - /// - /// Using this system is optional. It is only used to aim the Debugger on the Unity side. - /// It is also safe to copy logic from this system into your own systems, if it better suits your architecture. - /// - public class BotSDKDebuggerSystem : SystemMainThread - { - // Used for DEBUGGING purposes only - public static Action OnVerifiedFrame; - public static Action SetEntityDebugLabel; - - /// - /// Use this to add an entity to the Debugger Window on Unity. - /// You can provide a custom label of your preference if you want to identify your bots in a custom way. - /// Use the separator '/' on the custom label if you want to create an Hierarchy on the Debugger Window. - /// - public static void AddToDebugger(EntityRef entity, string customLabel = default) - { - if (SetEntityDebugLabel != null) - { - SetEntityDebugLabel(entity, customLabel); - } - } - - public override void Update(Frame frame) - { - if (frame.IsVerified) - { - OnVerifiedFrame?.Invoke(frame); - } - } - } -} diff --git a/data/BotSDKHiddenAttribute.cs b/data/BotSDKHiddenAttribute.cs deleted file mode 100644 index 2e9f6545a3be0e86843598d440013b7c647be604..0000000000000000000000000000000000000000 --- a/data/BotSDKHiddenAttribute.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Quantum -{ - [System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Class | System.AttributeTargets.Struct)] - public class BotSDKHiddenAttribute : System.Attribute - { - } -} diff --git a/data/BotSDKSystem.cs b/data/BotSDKSystem.cs deleted file mode 100644 index c172dc7b952f6208104c7fe36b3bf1448ee6055a..0000000000000000000000000000000000000000 --- a/data/BotSDKSystem.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; - -namespace Quantum -{ - /// - /// Using this system is optional. It is only used to aim the Debugger on the Unity side. - /// It is also safe to copy logic from this system into your own systems, if it better suits your architecture. - /// - public unsafe class BotSDKSystem : SystemSignalsOnly, ISignalOnComponentAdded, - ISignalOnComponentAdded, ISignalOnComponentRemoved, - ISignalOnComponentAdded, ISignalOnComponentRemoved, - ISignalOnComponentAdded, ISignalOnComponentRemoved, - ISignalOnComponentRemoved - { - // -- HFSM - public void OnAdded(Frame frame, EntityRef entity, HFSMAgent* component) - { - HFSMData* hfsmData = &component->Data; - if (hfsmData->Root == default) - return; - - HFSMRoot rootAsset = frame.FindAsset(hfsmData->Root.Id); - HFSMManager.Init(frame, entity, rootAsset); - } - - // -- BT - public void OnAdded(Frame frame, EntityRef entity, BTAgent* component) - { - // Mainly used to automatically initialize entity prototypes - // If the prototype's Tree reference is not default and the BTAgent - // is not initialized yet, then it is initialized here; - if (component->Tree != default) - { - component->Initialize(frame, entity, component, component->Tree, false); - } - } - - public void OnRemoved(Frame frame, EntityRef entity, BTAgent* component) - { - component->Free(frame); - } - - // -- UT - - public void OnAdded(Frame frame, EntityRef entity, UTAgent* component) - { - UTManager.Init(frame, &component->UtilityReasoner, component->UtilityReasoner.UTRoot, entity); - } - - public void OnRemoved(Frame frame, EntityRef entity, UTAgent* component) - { - component->UtilityReasoner.Free(frame); - } - - // -- GOAP - - void ISignalOnComponentAdded.OnAdded(Frame frame, EntityRef entity, GOAPAgent* component) - { - if (component->Root == default) - return; - - var rootAsset = frame.FindAsset(component->Root.Id); - GOAPManager.Initialize(frame, entity, rootAsset); - } - - void ISignalOnComponentRemoved.OnRemoved(Frame frame, EntityRef entity, GOAPAgent* component) - { - GOAPManager.Deinitialize(frame, entity); - } - - // -- Blackboard - - public void OnRemoved(Frame frame, EntityRef entity, AIBlackboardComponent* component) - { - component->FreeBlackboardComponent(frame); - } - } -} diff --git a/data/BotSDKTimerSystem.cs b/data/BotSDKTimerSystem.cs deleted file mode 100644 index ca2acd96afa533f9308e5813d7d87e2c79a43a1d..0000000000000000000000000000000000000000 --- a/data/BotSDKTimerSystem.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Photon.Deterministic; - -namespace Quantum -{ - public unsafe class BotSDKTimerSystem : SystemMainThread - { - public override void OnInit(Frame frame) - { - // We store the game's initial delta time so we can use it to further know - // if the delta time was changed (i.e to create slowdown or speedups) - frame.Global->BotSDKData.OriginalDeltaTime = FP._1 / frame.SessionConfig.UpdateFPS; - } - - public override void Update(Frame frame) - { - BotSDKData* botSDKData = &frame.Global->BotSDKData; - - // If the delta time was not changed, this tick counts as 1 - if (botSDKData->OriginalDeltaTime == frame.DeltaTime) - { - botSDKData->ElapsedTicks += 1; - } - else - { - // If the delta time was changed, we accumulate it as a "partial tick" value - // Once the partial tick value reaches at least 1 tick, we get the integer part of it - // to add to actual elapsed ticks - // Basically, if the simulation is running at half of the initial delta time, it will take double the time for one - // elapsed tick to be counted - botSDKData->ElapsedPartialTicks += frame.DeltaTime * frame.SessionConfig.UpdateFPS; - if (botSDKData->ElapsedPartialTicks >= 1) - { - var integerPart = FPMath.FloorToInt(botSDKData->ElapsedPartialTicks); - botSDKData->ElapsedTicks += integerPart; - botSDKData->ElapsedPartialTicks -= integerPart; - } - } - } - } -} diff --git a/data/Buff.cs b/data/Buff.cs deleted file mode 100644 index bc324687cfabd3233e2033799ad130bd01848433..0000000000000000000000000000000000000000 --- a/data/Buff.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace Quantum -{ - using Photon.Deterministic; - - public unsafe partial struct Buff - { - // PUBLIC MEMBERS - - public bool IsFinished { get { return Flags.IsBitSet(0); } set { Flags = Flags.SetBit(0, value); } } - - // PUBLIC METHODS - - public void Initialize(Frame frame, EntityRef entity, Buff* buff, byte level) - { - var behaviors = frame.ResolveList(Behaviors); - - for (int idx = 0, count = behaviors.Count; idx < count; idx++) - { - behaviors.GetPointer(idx)->Initialize(frame, entity, buff, level); - } - } - - public void Deinitialize(Frame frame, EntityRef entity, Buff* buff) - { - var behaviors = frame.ResolveList(Behaviors); - - for (int idx = 0, count = behaviors.Count; idx < count; idx++) - { - behaviors.GetPointer(idx)->Deinitialize(frame, entity, buff); - } - } - - public void Refresh(Frame frame, EntityRef entity, Buff* buff) - { - var behaviors = frame.ResolveList(Behaviors); - - for (int idx = 0, count = behaviors.Count; idx < count; idx++) - { - behaviors.GetPointer(idx)->Refresh(buff); - } - } - - public void Update(Frame frame, Buff* buff) - { - var behaviors = frame.ResolveList(Behaviors); - - for (int idx = 0, count = behaviors.Count; idx < count; idx++) - { - behaviors.GetPointer(idx)->Update(frame, buff); - } - } - - public (FP, FP) GetDuration(Frame frame) - { - var behaviors = frame.ResolveList(Behaviors); - - for (int idx = 0, count = behaviors.Count; idx < count; idx++) - { - var (duration, maxDuration) = behaviors[idx].GetDuration(); - if (maxDuration > FP._0) - return (duration, maxDuration); - } - - return (default, default); - } - } -} diff --git a/data/Buffs.cs b/data/Buffs.cs deleted file mode 100644 index 173cd9c2be116252121b392a4406cf4896d0751c..0000000000000000000000000000000000000000 --- a/data/Buffs.cs +++ /dev/null @@ -1,153 +0,0 @@ -namespace Quantum -{ - using Quantum.Collections; - - public unsafe partial struct Buffs - { - // PUBLIC METHODS - - public void Initialize(Frame frame) - { - BuffList = frame.AllocateList(); - } - - public void Deinitalize(Frame frame) - { - var buffs = frame.ResolveList(BuffList); - for (int idx = buffs.Count; idx --> 0;) - { - var buffEntity = buffs[idx]; - var buff = frame.Unsafe.GetPointer(buffEntity); - - buff->Deinitialize(frame, buffEntity, buff); - } - - frame.FreeList(BuffList); - BuffList = default; - } - - public void Update(Frame frame, EntityRef entity) - { - var buffs = frame.ResolveList(BuffList); - for (int idx = buffs.Count; idx --> 0;) - { - var buffEntity = buffs[idx]; - var buff = frame.Unsafe.GetPointer(buffEntity); - - buff->Update(frame, buff); - - if (buff->IsFinished == true) - { - buff->Deinitialize(frame, buffEntity, buff); - buffs.RemoveAtUnordered(idx); - - frame.Destroy(buffEntity); - } - } - } - - public bool AddBuff(Frame frame, EntityRef owner, EntityRef target, AssetRefEntityPrototype prototype, byte level) - { - var buffs = frame.ResolveList(BuffList); - - if (TryRefresh(frame, owner, prototype.Id.Value, buffs) == true) - return true; - - var buffEntity = frame.Create(prototype); - var buff = frame.Unsafe.GetPointer(buffEntity); - - buff->ID = prototype.Id.Value; - buff->Owner = owner; - buff->Target = target; - - buff->Initialize(frame, buffEntity, buff, level); - - buffs.Add(buffEntity); - - return true; - } - - public bool RemoveBuff(Frame frame, EntityRef owner, AssetRefEntityPrototype prototype) - { - var buffID = prototype.Id.Value; - var buffs = frame.ResolveList(BuffList); - - for (int idx = buffs.Count; idx --> 0;) - { - var buffEntity = buffs[idx]; - var buff = frame.Unsafe.GetPointer(buffEntity); - - if (buff->ID != buffID) - continue; - if (buff->Owner != owner) - continue; - - buff->Deinitialize(frame, buffEntity, buff); - - buffs.RemoveAtUnordered(idx); - frame.Destroy(buffEntity); - - return true; - } - - return false; - } - - public bool RemoveBuff(Frame frame, EntityRef buffEntity) - { - var buffs = frame.ResolveList(BuffList); - - for (int idx = buffs.Count; idx --> 0;) - { - if (buffs[idx] == buffEntity) - { - var buff = frame.Unsafe.GetPointer(buffEntity); - buff->Deinitialize(frame, buffEntity, buff); - - buffs.RemoveAtUnordered(idx); - frame.Destroy(buffEntity); - return true; - } - } - - return false; - } - - public void RemoveAll(Frame frame) - { - var buffs = frame.ResolveList(BuffList); - - for (int idx = buffs.Count; idx --> 0;) - { - var buffEntity = buffs[idx]; - var buff = frame.Unsafe.GetPointer(buffEntity); - buff->Deinitialize(frame, buffEntity, buff); - - frame.Destroy(buffEntity); - } - - buffs.Clear(); - } - - // PRIVATE MEMBERS - - private bool TryRefresh(Frame frame, EntityRef owner, long buffID, QList buffs) - { - for (int idx = buffs.Count; idx --> 0;) - { - var buffEntity = buffs[idx]; - var buff = frame.Unsafe.GetPointer(buffEntity); - - if (buff->ID != buffID) - continue; - if (buff->Owner != owner) - continue; - - buff->Refresh(frame, buffEntity, buff); - return true; - } - - return false; - } - } -} diff --git a/data/Buffs.qtn b/data/Buffs.qtn deleted file mode 100644 index 94a54f4929637bc3c08d38dd3d4f0bdbf04630a2..0000000000000000000000000000000000000000 --- a/data/Buffs.qtn +++ /dev/null @@ -1,47 +0,0 @@ -component Buffs -{ - [ExcludeFromPrototype] list BuffList; -} - -component Buff -{ - [ExcludeFromPrototype] long ID; - [ExcludeFromPrototype] byte Flags; - [ExcludeFromPrototype] EntityRef Owner; - [ExcludeFromPrototype] EntityRef Target; - - list Behaviors; -} - -union BuffBehavior -{ - BuffBehavior_Duration Duration; - BuffBehavior_HealthOverTime HealthOverTime; - BuffBehavior_Stats Stats; -} - -struct BuffBehavior_Duration -{ - [ExcludeFromPrototype] FP Duration; - FP MaxDuration; - FP DurationPerLevelIncrease; -} - -struct BuffBehavior_HealthOverTime -{ - [ExcludeFromPrototype] FP TimeToTick; - [ExcludeFromPrototype] byte RemainingTicks; - FP ValuePerTick; - FP ValuePerLevelIncrease; - EHealthAction Action; - FP TickTime; - byte TickCount; -} - -struct BuffBehavior_Stats -{ - EStatType StatType; - FP AbsoluteValue; - FP PercentValue; - FP ValuePerLevelIncrease; -} \ No newline at end of file diff --git a/data/CameraBehavior.cs b/data/CameraBehavior.cs deleted file mode 100644 index 6e5fa8ea361b5c218746c436cf6deeb77756d917..0000000000000000000000000000000000000000 --- a/data/CameraBehavior.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using Quantum; - -public class CameraBehavior : QuantumCallbacks -{ - private bool _isInverted = false; - - public override void OnUpdateView(QuantumGame game) - { - var player = game.Session.LocalPlayerIndices[0]; - if (player != 0 && !_isInverted) - { - transform.eulerAngles = new Vector3(transform.eulerAngles.x, transform.eulerAngles.y +180, transform.eulerAngles.z); - - for (int i = 0; i < ChessViewUpdater.Instance.Pieces.Length; i++) - { - var p = ChessViewUpdater.Instance.Pieces[i]; - p.transform.eulerAngles = new Vector3(p.transform.eulerAngles.x, p.transform.eulerAngles.y, p.transform.eulerAngles.z + 180); - } - _isInverted = true; - } - } -} diff --git a/data/CameraFollow.cs b/data/CameraFollow.cs deleted file mode 100644 index 1b5c8f66e8bf83fc34d9b939efe6abca705db046..0000000000000000000000000000000000000000 --- a/data/CameraFollow.cs +++ /dev/null @@ -1,161 +0,0 @@ -using Photon.Deterministic; -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -[RequireComponent(typeof(Camera))] -public class CameraFollow : MonoBehaviour -{ - public Transform Target; - public Quantum.LayerMask OcclusionQueryMask = -1; - [SerializeField, Range(2f, 15f)] - float distance = 5f; - [SerializeField, Range(0.1f, 5f)] - float scrollSpeed = 1f; - [SerializeField, Min(0f)] - float focusRadius = 1f; - [SerializeField, Range(0f, 1f)] - float focusCentering = 0.5f; - - [SerializeField, Range(1f, 360f)] - float rotationSpeed = 90f; - [SerializeField, Range(-30f, 89f)] - float minVerticalAngle = -30f, maxVerticalAngle = 60f; - - Vector3 focusPoint; - Vector2 orbitAngles = new Vector2(45f, 0f); - - private Camera _camera; - void Awake() - { - //focusPoint = focus.position; - transform.localRotation = Quaternion.Euler(orbitAngles); - _camera = GetComponent(); - } - void LateUpdate() - { - if (Target == null) return; - UpdateDistance(); - UpdateFocusPoint(); - Quaternion lookRotation; - if (ManualRotation()) - { - ConstrainAngles(); - lookRotation = Quaternion.Euler(orbitAngles); - } - else - { - lookRotation = transform.localRotation; - } - - Vector3 lookDirection = lookRotation * Vector3.forward; - Vector3 lookPosition = focusPoint - lookDirection * distance; - - if (Cast(focusPoint, lookRotation, -lookDirection, out var hitDistance, distance - _camera.nearClipPlane)) - { - lookPosition = focusPoint - lookDirection * (hitDistance + _camera.nearClipPlane); - } - - transform.SetPositionAndRotation(lookPosition, lookRotation); - } - - private unsafe bool Cast(Vector3 origin, Quaternion rotation, Vector3 direction, out float hitDistance, float distance) - { - hitDistance = 0; - - var frame = QuantumRunner.Default?.Game?.Frames.Verified; - if (frame != null) - { - var shape = Quantum.Shape3D.CreateBox(CameraPlaneExtends.ToFPVector3()); - var options = Quantum.QueryOptions.ComputeDetailedInfo | Quantum.QueryOptions.HitAll; - var hit = frame.Physics3D.ShapeCast(origin.ToFPVector3(), rotation.ToFPQuaternion(), &shape, (direction * distance).ToFPVector3(), OcclusionQueryMask, options); - if (hit.HasValue) - { - hitDistance = (origin - hit.Value.Point.ToUnityVector3()).magnitude; - return true; - } - } - return false; - } - - Vector3 CameraPlaneExtends - { - get - { - Vector3 halfExtends; - halfExtends.y = - _camera.nearClipPlane * - Mathf.Tan(0.5f * Mathf.Deg2Rad * _camera.fieldOfView) * 1.25f; - halfExtends.x = halfExtends.y * _camera.aspect; - halfExtends.z = 0.01f; - return halfExtends; - } - } - - void UpdateDistance() - { - distance += Input.GetAxisRaw("Mouse ScrollWheel") * scrollSpeed; - distance = Mathf.Clamp(distance, 2, 15); - } - - bool ManualRotation() - { - Vector2 input = new Vector2( - -Input.GetAxis("Mouse Y"), - Input.GetAxis("Mouse X") - ); - const float e = 0.001f; - if (input.x < e || input.x > e || input.y < e || input.y > e) - { - orbitAngles += rotationSpeed * Time.unscaledDeltaTime * input; - return true; - } - return false; - } - - void UpdateFocusPoint() - { - Vector3 targetPoint = Target.position; - if (focusRadius > 0f) - { - float distance = Vector3.Distance(targetPoint, focusPoint); - float t = 1f; - if (distance > 0.01f && focusCentering > 0f) - { - t = Mathf.Pow(1f - focusCentering, Time.unscaledDeltaTime); - } - if (distance > focusRadius) - { - t = Mathf.Min(t, focusRadius / distance); - } - focusPoint = Vector3.Lerp(targetPoint, focusPoint, t); - } - else - { - focusPoint = targetPoint; - } - } - - void ConstrainAngles() - { - orbitAngles.x = - Mathf.Clamp(orbitAngles.x, minVerticalAngle, maxVerticalAngle); - - if (orbitAngles.y < 0f) - { - orbitAngles.y += 360f; - } - else if (orbitAngles.y >= 360f) - { - orbitAngles.y -= 360f; - } - } - - void OnValidate() - { - if (maxVerticalAngle < minVerticalAngle) - { - maxVerticalAngle = minVerticalAngle; - } - } -} diff --git a/data/CardManager.cs b/data/CardManager.cs deleted file mode 100644 index 6ffbe9e1ee122a5596e739edfc035b55cace6917..0000000000000000000000000000000000000000 --- a/data/CardManager.cs +++ /dev/null @@ -1,176 +0,0 @@ -namespace Quantum -{ - using Photon.Deterministic; - - unsafe partial struct CardManager - { - // CONSTANTS - - public const byte AVAILABLE_CARDS_COUNT = 4; - - // PUBLIC METHODS - - public void Initialize(Frame frame, CardInfo[] cards, GameplaySettings settings) - { - var availableCards = frame.AllocateList(AVAILABLE_CARDS_COUNT); - var cardQueue = frame.AllocateList(cards.Length); - - CardQueue = cardQueue; - AvailableCards = availableCards; - - for (int idx = 0, count = cards.Length; idx < count; idx++) - { - cardQueue.Add(cards[idx]); - } - - cardQueue.Shuffle(frame.RNG); - - for (int idx = 0; idx < AVAILABLE_CARDS_COUNT; idx++) - { - availableCards.Add(cardQueue[idx]); - } - - QueueHeadIndex = AVAILABLE_CARDS_COUNT; - - CurrentEnergy = (int)settings.StartEnergy; - MaxEnergy = (int)settings.MaxEnergy; - } - - public void SetFillRate(FP energyFillRate) - { - EnergyFillRate = energyFillRate; - } - - public void Deinitialize(Frame frame) - { - frame.FreeList(AvailableCards); - frame.FreeList(CardQueue); - - AvailableCards = default; - CardQueue = default; - } - - public void Update(Frame frame, EntityRef entity) - { - if (CardQueue.Ptr.Offset == 0) - return; - - CurrentEnergy = FPMath.Clamp(CurrentEnergy + frame.DeltaTime * EnergyFillRate, FP._0, MaxEnergy); - NextFillTime -= frame.DeltaTime; - - if (EmptySlots > 0 && NextFillTime <= FP._0) - { - FillCardSlot(frame, entity); - } - } - - public void UseCard(Frame frame, EntityRef entity, PlayerRef owner, byte cardIndex, FPVector2 position, FP rotation) - { - if (cardIndex < 0 || cardIndex >= AVAILABLE_CARDS_COUNT) - { - Log.Error($"Out of range card use request Index: {cardIndex}"); - return; - } - - var availableCards = frame.ResolveList(AvailableCards); - var card = availableCards[cardIndex]; - - if (card.CardSettings.Id.IsValid == false) - { - Log.Error($"Invalid card use request. Index: {cardIndex}"); - return; - } - - var settings = frame.FindAsset(card.CardSettings.Id); - if (settings.EnergyCost > CurrentEnergy) - { - Log.Error($"Not enough energy card request. Index: {cardIndex} Cost: {settings.EnergyCost} Current: {CurrentEnergy}"); - return; - } - - if (settings is UnitSettings) - { - var gameplay = frame.Unsafe.GetPointerSingleton(); - if (gameplay->IsValidUnitPosition(frame, owner, position) == false) - { - Log.Error($"Invalid unit spawn position. Position: {position}"); - return; - } - } - - frame.SpawnCard(settings, owner, position, rotation, card.Level); - - AddCardToQueue(frame, card); - - CurrentEnergy -= settings.EnergyCost; - availableCards[cardIndex] = default; - EmptySlots += 1; - - frame.Events.CardsChanged(entity); - } - - // PRIVATE METHODS - - private void FillCardSlot(Frame frame, EntityRef entity) - { - var availableCards = frame.ResolveList(AvailableCards); - - for (int idx = 0; idx < AVAILABLE_CARDS_COUNT; idx++) - { - if (availableCards[idx].CardSettings.Id.IsValid == true) - continue; - - var cardQueue = frame.ResolveList(CardQueue); - - availableCards[idx] = cardQueue[QueueHeadIndex]; - QueueHeadIndex = (byte)((QueueHeadIndex + 1) % cardQueue.Count); - EmptySlots -= 1; - NextFillTime = FP._2; - - frame.Events.CardsChanged(entity); - break; - } - } - - private void AddCardToQueue(Frame frame, CardInfo card) - { - var cardQueue = frame.ResolveList(CardQueue); - - cardQueue[QueueTailIndex] = card; - QueueTailIndex = (byte)((QueueTailIndex + 1) % cardQueue.Count); - } - - private FPVector2 TransformPosition(FPVector2 position, int unitCount, int unitIndex) - { - if (unitCount == 1) - return position; - - if (unitCount <= 4) - { - var rotationOffset = FP.Rad_180 * FP._2 / unitCount * unitIndex; - position += FPVector2.Rotate(FPVector2.Right * FP._0_50, rotationOffset); - } - - if (unitCount <= 12) - { - if (unitIndex < 4) - { - var rotationOffset = FP.Rad_180 * FP._2 / 4 * unitIndex; - position += FPVector2.Rotate(FPVector2.Right * FP._0_50, rotationOffset); - } - else - { - var rotationOffset = FP.Rad_180 * FP._2 / (unitCount - 4) * unitIndex; - position += FPVector2.Rotate(FPVector2.Right, rotationOffset); - } - } - - return position; - } - } - - [System.Serializable] - unsafe partial struct CardInfo - { - } -} diff --git a/data/CardManager.qtn b/data/CardManager.qtn deleted file mode 100644 index e3df3519213860268e691a1146fe4b7c9ccd8d0d..0000000000000000000000000000000000000000 --- a/data/CardManager.qtn +++ /dev/null @@ -1,36 +0,0 @@ -asset CardSettings; - -[ExcludeFromPrototype] -component CardManager -{ - Byte EmptySlots; - Byte QueueHeadIndex; - Byte QueueTailIndex; - FP CurrentEnergy; - FP EnergyFillRate; - FP MaxEnergy; - FP NextFillTime; - list CardQueue; - list AvailableCards; -} - -struct CardInfo -{ - AssetRefCardSettings CardSettings; - Byte Level; -} - -[PreserveInPrototype] -enum ERarity : byte -{ - Common, - Uncommon, - Rare, - Epic, - Legendary, -} - -synced event CardsChanged -{ - EntityRef Entity; -} diff --git a/data/CardManagerSystem.cs b/data/CardManagerSystem.cs deleted file mode 100644 index 41a604f2cab1c3f06d4cc6b71e8853cdedbb2bb3..0000000000000000000000000000000000000000 --- a/data/CardManagerSystem.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Quantum -{ - using Photon.Deterministic; - - unsafe class CardManagerSystem : SystemMainThread - { - public override void Update(Frame frame) - { - foreach (var pair in frame.Unsafe.GetComponentBlockIterator()) - { - pair.Component->Update(frame, pair.Entity); - } - - var gameplay = frame.Unsafe.GetPointerSingleton(); - if (gameplay->IsActive == false) - return; - - foreach (var pair in frame.Unsafe.GetComponentBlockIterator()) - { - var command = frame.GetPlayerCommand(pair.Component->PlayerRef); - switch (command) - { - case UseCardCommand useCard: - ProcessCommand(frame, pair.Entity, pair.Component->PlayerRef, useCard); - break; - } - } - } - - // PRIVATE METHODS - - private void ProcessCommand(Frame frame, EntityRef entity, PlayerRef playerRef, UseCardCommand useCard) - { - var cardManager = frame.Unsafe.GetPointer(entity); - cardManager->UseCard(frame, entity, playerRef, useCard.CardIndex, useCard.Position, playerRef * FP.Rad_180); - } - } -} diff --git a/data/CardSettings.cs b/data/CardSettings.cs deleted file mode 100644 index a0791c9f760cdc839b1365838520e397b0d981e1..0000000000000000000000000000000000000000 --- a/data/CardSettings.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Quantum -{ - using Photon.Deterministic; - using Quantum.Inspector; - - public abstract partial class CardSettings - { - [Header("Card")] - public AssetRefEntityPrototype Prefab; - public ERarity Rarity; - public byte EnergyCost; - public FP ActivationDelay; - } -} diff --git a/data/Chain.User.cs b/data/Chain.User.cs deleted file mode 100644 index 2a711b6156c9709fec8d169c209fe6f3c53c27d8..0000000000000000000000000000000000000000 --- a/data/Chain.User.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using Photon.Deterministic; - -namespace Quantum -{ - unsafe partial struct Chain - { - public void Update(FrameThreadSafe frame, ref ChainSystem.Filter filter) - { - FPVector3 direction = filter.Transform->Position - LastPosition; - Alpha = direction.Magnitude / DistanceThreshold; - - // cycle element - if (Alpha > FP._1) - { - Alpha = Alpha % FP._1; - AddBreadCrumb(direction); - } - - filter.Body->Drag = MinDrag + Count * DragPerItem; - if (filter.Drivable->Grounded == false) filter.Body->GravityScale = FP._1; - filter.Drivable->Grounded = false; -#if DEBUG - for (int i = 0; i < Positions.Length; i++) - { - Draw.Sphere(Positions[i], FP._0_10, ColorRGBA.ColliderBlue); - } -#endif - } - - public void AddBreadCrumb(FPVector3 direction) - { - FPVector3 newPosition = LastPosition + direction.Normalized * DistanceThreshold; - *Positions.GetPointer(Current) = newPosition; - Current = (Current + 1) % Positions.Length; - LastPosition = newPosition; - } - } -} diff --git a/data/ChainItem.cs b/data/ChainItem.cs deleted file mode 100644 index 2b57ad23e8d7d457b62861dfabc0a31c8532c4e7..0000000000000000000000000000000000000000 --- a/data/ChainItem.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - unsafe partial struct ChainItem - { - public void Update(FrameThreadSafe frame, ref ChainItemSystem.Filter filter) - { - // READ-ONLY for this component, PLEASE - if (frame.TryGetPointer(Chain, out var chain)) - { - if (chain->Count <= Index) - { - Destroy = true; - return; - } - - int indexFrom = (chain->Current - Index + chain->Positions.Length - 2) % chain->Positions.Length; - int indexTo = (indexFrom + 1) % chain->Positions.Length; - FPVector3 from = chain->Positions[indexFrom]; - FPVector3 to = chain->Positions[indexTo]; - FPVector3 position = FPVector3.Lerp(from, to, chain->Alpha); - filter.Transform->Position = position; - - FPVector3 direction = (to - from).Normalized; - FPQuaternion desiredRotation = FPQuaternion.LookRotation(direction); - FP angle = FPQuaternion.Angle(desiredRotation, filter.Transform->Rotation); - if (angle < SnapAngle) - { - filter.Transform->Rotation = FPQuaternion.RotateTowards(filter.Transform->Rotation, desiredRotation, RotationSpeed * frame.DeltaTime); - } - else - { - filter.Transform->Rotation = desiredRotation; - } - - } - } - } -} diff --git a/data/ChainItemSystem.cs b/data/ChainItemSystem.cs deleted file mode 100644 index 43d16b5b28a2277d862dd5a73bc75d68fcf11585..0000000000000000000000000000000000000000 --- a/data/ChainItemSystem.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Photon.Deterministic; -using Quantum.Task; - -namespace Quantum -{ - public unsafe class ChainItemSystem : SystemThreadedFilter - { - public struct Filter - { - public EntityRef Entity; - public Transform3D* Transform; - public ChainItem* Item; - } - public override void Update(FrameThreadSafe frame, ref Filter filter) - { - filter.Item->Update(frame, ref filter); - } - } -} diff --git a/data/ChainSystem.cs b/data/ChainSystem.cs deleted file mode 100644 index b12364c0678a788bed9fd6f31fba1aecd395254f..0000000000000000000000000000000000000000 --- a/data/ChainSystem.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using Photon.Deterministic; -using Quantum.Task; - -namespace Quantum -{ - public unsafe class ChainSystem : SystemThreadedFilter, ISignalOnTrigger3D, ISignalOnCollision3D, ISignalOnComponentAdded - { - public struct Filter - { - public EntityRef Entity; - public Transform3D* Transform; - public PhysicsBody3D* Body; - public Chain* Chain; - public Drivable* Drivable; - } - - public void OnAdded(Frame frame, EntityRef entity, Chain* chain) - { - Transform3D transform = frame.Get(entity); - chain->LastPosition = transform.Position; - for (int i = 0; i < 5; i++) - { - chain->AddBreadCrumb(transform.Back); - } - } - - public void OnTrigger3D(Frame frame, TriggerInfo3D info) - { - if (frame.Unsafe.TryGetPointer(info.Other, out var pickup) && frame.Unsafe.TryGetPointer(info.Entity, out var chain)) - { - EntityRef entity = frame.Create(pickup->ItemPrototype); - if (frame.Unsafe.TryGetPointer(entity, out var item)) - { - int index = chain->Count++; - item->Chain = info.Entity; - item->Index = index; - } - if (frame.Unsafe.TryGetPointerSingleton(out var spawner)) - { - spawner->Count--; - } - frame.Destroy(info.Other); - } - } - - public void OnCollision3D(Frame frame, CollisionInfo3D info) - { - if (frame.Unsafe.TryGetPointer(info.Entity, out var drivable) && frame.Unsafe.TryGetPointer(info.Entity, out var body)) - { - FP angle = FPVector3.Angle(info.ContactNormal, FPVector3.Up); - if (angle < 45) - { - drivable->Grounded = true; - drivable->SurfaceNormal = info.ContactNormal; - body->GravityScale = FP._0; - } - } - - if (frame.Unsafe.TryGetPointer(info.Other, out var item) && frame.Unsafe.TryGetPointer(item->Chain, out var chain)) - { - chain->Count = Math.Min(item->Index, chain->Count); - info.IgnoreCollision = true; - } - } - - public override void Update(FrameThreadSafe frame, ref Filter filter) - { - filter.Chain->Update(frame, ref filter); - } - } -} diff --git a/data/CheckIndicatorView.cs b/data/CheckIndicatorView.cs deleted file mode 100644 index 01fa87f5951fdc055e120edee622ee80a13cf815..0000000000000000000000000000000000000000 --- a/data/CheckIndicatorView.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using Quantum; - -public class CheckIndicatorView : QuantumCallbacks { - - public GameObject Indicator; - public PieceView Piece; - - void Start () { - Indicator.SetActive(false); - QuantumEvent.Subscribe(this, SetCheckIndicator); - QuantumEvent.Subscribe(this, ResetCheckIndicator); - } - - private void SetCheckIndicator(EventPlayerInCheck e) - { - if (e.Color == Piece.Color) - { - Indicator.SetActive(true); - } - } - - private void ResetCheckIndicator(EventTurnEnded e) - { - Indicator.SetActive(false); - } - - protected override void OnDisable() - { - QuantumEvent.UnsubscribeListener(this); - } -} diff --git a/data/ChecksumVerification.cs b/data/ChecksumVerification.cs deleted file mode 100644 index 69c2187887f59b237530c54993e035bd583c641d..0000000000000000000000000000000000000000 --- a/data/ChecksumVerification.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Quantum { - public class ChecksumVerification : IDisposable { - private Dictionary _checksums; - private Quantum.CallbackDispatcher _gameCallbacks; - private bool _verbose; - - public ChecksumVerification(string pathToChecksumFile, Quantum.CallbackDispatcher callbacks, bool verbose = false) { - _checksums = JsonConvert.DeserializeObject(File.ReadAllText(pathToChecksumFile), ReplayJsonSerializerSettings.GetSettings()).ToDictionary(); - _gameCallbacks = callbacks; - _gameCallbacks.Subscribe(this, (CallbackSimulateFinished callback) => OnSimulateFinished(callback.Game, callback.Frame)); - _verbose = verbose; - } - - private void OnSimulateFinished(QuantumGame game, Frame frame) { - if (frame != null) { - var f = frame.Number; - var cs = ChecksumFileHelper.UlongToLong(frame.CalculateChecksum()); - - if (_checksums != null) { - - if (_checksums.ContainsKey(f)) { - Console.Write($"{f,6} {cs,25} "); - if (cs != _checksums[f].ChecksumAsLong) { - Console.ForegroundColor = ConsoleColor.Red; - Console.Write("(failed)"); - } else { - Console.ForegroundColor = ConsoleColor.Green; - Console.Write("(verified)"); - } - Console.Write("\n"); - } else if (_verbose) { - Console.Write($"{f,6} {cs,25} "); - Console.Write("(skipped)"); - } - Console.ForegroundColor = ConsoleColor.Gray; - } - } - } - - public void Dispose() { - if (_gameCallbacks != null) { - _gameCallbacks.UnsubscribeListener(this); - _gameCallbacks = null; - } - } - } -} diff --git a/data/ChessViewUpdater.cs b/data/ChessViewUpdater.cs deleted file mode 100644 index 7512a75dbfadca8522600310fdad014b4ff59c9e..0000000000000000000000000000000000000000 --- a/data/ChessViewUpdater.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using Quantum; - -public unsafe class ChessViewUpdater : QuantumCallbacks -{ - public static ChessViewUpdater Instance; - - public PieceView[] Pieces = new PieceView[32]; - - public Vector3 PieceOffset = new Vector3(.5f, 0, .5f); - - public GameObject LastMoveIdicatorInitial; - public GameObject LastMoveIdicatorTarget; - - public Sprite WhiteQueenSprite; - public Sprite WhiteRookSprite; - public Sprite WhiteKnightSprite; - public Sprite WhiteBishopSprite; - // - public Sprite BlackQueenSprite; - public Sprite BlackRookSprite; - public Sprite BlackKnightSprite; - public Sprite BlackBishopSprite; - - public DeadPiecesManager DeadPieces; - - private bool _initialized = false; - - - public void Start() - { - if (Instance == null) - { - Instance = this; - } - - QuantumEvent.Subscribe(this, UpdatePiece); - QuantumEvent.Subscribe(this, RemovePiece); - QuantumEvent.Subscribe(this, PiecePromotion); - } - - public override void OnUpdateView(QuantumGame game) - { - if (!_initialized) - { - _initialized = true; - var f = game.Frames.Verified; - for (int i = 0; i < f.Global->Board.Cells.Length; i++) - { - int index = GetPieceIndex(i); - if (index != -1) - { - var p = Pieces[index]; - var position = BoardHelper.GetCordinatesByIndex(p.IndexOnBoard); - p.SetTargetPosition(new Vector3((float)position.X + PieceOffset.x, PieceOffset.x, (float)position.Y + PieceOffset.z)); - } - } - } - } - - public void PiecePromotion(EventPiecePromotion e) - { - var index = GetPieceIndex(e.Index); - Pieces[index].Type = e.NewType; - switch (e.NewType) - { - case PieceType.Bishop: - if (Pieces[index].Color == PieceColor.White) - Pieces[index].GetComponent().sprite = WhiteBishopSprite; - else - Pieces[index].GetComponent().sprite = BlackBishopSprite; - break; - case PieceType.Knight: - if (Pieces[index].Color == PieceColor.White) - Pieces[index].GetComponent().sprite = WhiteKnightSprite; - else - Pieces[index].GetComponent().sprite = BlackKnightSprite; - break; - case PieceType.Queen: - if (Pieces[index].Color == PieceColor.White) - Pieces[index].GetComponent().sprite = WhiteQueenSprite; - else - Pieces[index].GetComponent().sprite = BlackQueenSprite; - break; - case PieceType.Rook: - if (Pieces[index].Color == PieceColor.White) - Pieces[index].GetComponent().sprite = WhiteRookSprite; - else - Pieces[index].GetComponent().sprite = BlackRookSprite; - break; - } - } - - public void UpdatePiece(EventChangePiecePosition e) - { - var origin = (int)e.Index.X; - var target = (int)e.Index.Y; - - SetObjectByIndex(LastMoveIdicatorInitial, origin); - SetObjectByIndex(LastMoveIdicatorTarget, target); - - int index = GetPieceIndex(origin); - if (index != -1) - { - var p = Pieces[index]; - p.IndexOnBoard = target; - var position = BoardHelper.GetCordinatesByIndex(p.IndexOnBoard); - p.SetTargetPosition(new Vector3((float)position.X + PieceOffset.x, PieceOffset.x, (float)position.Y + PieceOffset.z)); - } - } - - public void SetObjectByIndex(GameObject go, int index) - { - var position = BoardHelper.GetCordinatesByIndex(index); - go.transform.position = new Vector3((float)position.X + PieceOffset.x, PieceOffset.y, (float)position.Y + PieceOffset.z); - go.SetActive(true); - } - - public void RemovePiece(EventRemovePiece e) - { - var index = GetPieceIndex(e.Index); - DeadPieces.StoreDeadPiece(Pieces[index], e.Color); - Pieces[index].IndexOnBoard = -1; - } - - public int GetPieceIndex(int index) - { - for (int i = 0; i < Pieces.Length; i++) - { - if (Pieces[i] == null) - { - continue; - } - if (Pieces[i].IndexOnBoard == index) - { - return i; - } - } - return -1; - } - - public void OnDisable() - { - QuantumEvent.UnsubscribeListener(this); - } -} diff --git a/data/CodeGen.cs b/data/CodeGen.cs deleted file mode 100644 index d602b34ef25abcd45416a4c1ca2a9db5733e6e3b..0000000000000000000000000000000000000000 --- a/data/CodeGen.cs +++ /dev/null @@ -1,1995 +0,0 @@ -// -// This code was auto-generated by a tool, every time -// the tool executes this code will be reset. -// -// If you need to extend the classes generated to add -// fields or methods to them, please create partial -// declarations in another file. -// -#pragma warning disable 0649 -#pragma warning disable 1522 -#pragma warning disable 0414 -#pragma warning disable 0219 -#pragma warning disable 0109 - -namespace Quantum { - using System; - using System.Collections.Generic; - using System.Runtime.InteropServices; - using Photon.Deterministic; - using Quantum.Core; - using Quantum.Collections; - using Quantum.Inspector; - using Quantum.Physics2D; - using Quantum.Physics3D; - using Optional = Quantum.Inspector.OptionalAttribute; - using MethodImplAttribute = System.Runtime.CompilerServices.MethodImplAttribute; - using MethodImplOptions = System.Runtime.CompilerServices.MethodImplOptions; - - public enum TurnEndReason : int { - Time, - Skip, - Play, - Resolved, - } - public enum TurnStatus : int { - Inactive, - Active, - Resolving, - } - public enum TurnType : int { - Play, - Countdown, - } - [System.FlagsAttribute()] - public enum InputButtons : int { - } - public static unsafe partial class InputButtons_ext { - public static Boolean IsFlagSet(this InputButtons self, InputButtons flag) { - return (self & flag) == flag; - } - public static InputButtons SetFlag(this InputButtons self, InputButtons flag) { - return self | flag; - } - public static InputButtons ClearFlag(this InputButtons self, InputButtons flag) { - return self & ~flag; - } - } - [StructLayout(LayoutKind.Explicit)] - public unsafe partial struct BitSet1024 { - public const Int32 SIZE = 128; - public const Int32 ALIGNMENT = 8; - [FieldOffset(0)] - private fixed UInt64 bits[16]; - public const Int32 BitsSize = 1024; - public Int32 Length { - get { - return 1024; - } - } - public static void Print(void* ptr, FramePrinter printer) { - var p = (BitSet1024*)ptr; - printer.ScopeBegin(); - UnmanagedUtils.PrintBytesBits((byte*)&p->bits, 1024, 64, printer); - printer.ScopeEnd(); - } - [System.ObsoleteAttribute("Use instance Set method instead")] - public static void Set(BitSet1024* set, Int32 bit) { - set->bits[bit/64] |= (1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance Clear method instead")] - public static void Clear(BitSet1024* set, Int32 bit) { - set->bits[bit/64] &= ~(1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance ClearAll method instead")] - public static void ClearAll(BitSet1024* set) { - Native.Utils.Clear(&set->bits[0], 128); - } - [System.ObsoleteAttribute("Use instance IsSet method instead")] - public static Boolean IsSet(BitSet1024* set, Int32 bit) { - return (set->bits[bit/64]&(1UL<<(bit%64))) != 0UL; - } - public static BitSet1024 FromArray(UInt64[] values) { - Assert.Always(16 == values.Length); - BitSet1024 result = default; - for (int i = 0; i < 16; ++i) { - result.bits[i] = values[i]; - } - return result; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Set(Int32 bit) { - Assert.Check(bit >= 0 && bit < 1024); - fixed (UInt64* p = bits) (p[bit/64]) |= (1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear(Int32 bit) { - Assert.Check(bit >= 0 && bit < 1024); - fixed (UInt64* p = bits) (p[bit/64]) &= ~(1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ClearAll() { - fixed (UInt64* p = bits) Native.Utils.Clear(p, 128); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Boolean IsSet(Int32 bit) { - fixed (UInt64* p = bits) return ((p[bit/64])&(1UL<<(bit%64))) != 0UL; - } - public override Int32 GetHashCode() { - unchecked { - var hash = 37; - fixed (UInt64* p = bits) hash = hash * 31 + HashCodeUtils.GetArrayHashCode(p, 16); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (BitSet1024*)ptr; - serializer.Stream.SerializeBuffer(&p->bits[0], 16); - } - } - [StructLayout(LayoutKind.Explicit)] - public unsafe partial struct BitSet128 { - public const Int32 SIZE = 16; - public const Int32 ALIGNMENT = 8; - [FieldOffset(0)] - private fixed UInt64 bits[2]; - public const Int32 BitsSize = 128; - public Int32 Length { - get { - return 128; - } - } - public static void Print(void* ptr, FramePrinter printer) { - var p = (BitSet128*)ptr; - printer.ScopeBegin(); - UnmanagedUtils.PrintBytesBits((byte*)&p->bits, 128, 64, printer); - printer.ScopeEnd(); - } - [System.ObsoleteAttribute("Use instance Set method instead")] - public static void Set(BitSet128* set, Int32 bit) { - set->bits[bit/64] |= (1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance Clear method instead")] - public static void Clear(BitSet128* set, Int32 bit) { - set->bits[bit/64] &= ~(1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance ClearAll method instead")] - public static void ClearAll(BitSet128* set) { - Native.Utils.Clear(&set->bits[0], 16); - } - [System.ObsoleteAttribute("Use instance IsSet method instead")] - public static Boolean IsSet(BitSet128* set, Int32 bit) { - return (set->bits[bit/64]&(1UL<<(bit%64))) != 0UL; - } - public static BitSet128 FromArray(UInt64[] values) { - Assert.Always(2 == values.Length); - BitSet128 result = default; - for (int i = 0; i < 2; ++i) { - result.bits[i] = values[i]; - } - return result; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Set(Int32 bit) { - Assert.Check(bit >= 0 && bit < 128); - fixed (UInt64* p = bits) (p[bit/64]) |= (1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear(Int32 bit) { - Assert.Check(bit >= 0 && bit < 128); - fixed (UInt64* p = bits) (p[bit/64]) &= ~(1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ClearAll() { - fixed (UInt64* p = bits) Native.Utils.Clear(p, 16); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Boolean IsSet(Int32 bit) { - fixed (UInt64* p = bits) return ((p[bit/64])&(1UL<<(bit%64))) != 0UL; - } - public override Int32 GetHashCode() { - unchecked { - var hash = 41; - fixed (UInt64* p = bits) hash = hash * 31 + HashCodeUtils.GetArrayHashCode(p, 2); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (BitSet128*)ptr; - serializer.Stream.SerializeBuffer(&p->bits[0], 2); - } - } - [StructLayout(LayoutKind.Explicit)] - public unsafe partial struct BitSet2 { - public const Int32 SIZE = 8; - public const Int32 ALIGNMENT = 8; - [FieldOffset(0)] - private fixed UInt64 bits[1]; - public const Int32 BitsSize = 2; - public Int32 Length { - get { - return 2; - } - } - public static void Print(void* ptr, FramePrinter printer) { - var p = (BitSet2*)ptr; - printer.ScopeBegin(); - UnmanagedUtils.PrintBytesBits((byte*)&p->bits, 2, 64, printer); - printer.ScopeEnd(); - } - [System.ObsoleteAttribute("Use instance Set method instead")] - public static void Set(BitSet2* set, Int32 bit) { - set->bits[bit/64] |= (1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance Clear method instead")] - public static void Clear(BitSet2* set, Int32 bit) { - set->bits[bit/64] &= ~(1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance ClearAll method instead")] - public static void ClearAll(BitSet2* set) { - Native.Utils.Clear(&set->bits[0], 8); - } - [System.ObsoleteAttribute("Use instance IsSet method instead")] - public static Boolean IsSet(BitSet2* set, Int32 bit) { - return (set->bits[bit/64]&(1UL<<(bit%64))) != 0UL; - } - public static BitSet2 FromArray(UInt64[] values) { - Assert.Always(1 == values.Length); - BitSet2 result = default; - for (int i = 0; i < 1; ++i) { - result.bits[i] = values[i]; - } - return result; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Set(Int32 bit) { - Assert.Check(bit >= 0 && bit < 2); - fixed (UInt64* p = bits) (p[bit/64]) |= (1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear(Int32 bit) { - Assert.Check(bit >= 0 && bit < 2); - fixed (UInt64* p = bits) (p[bit/64]) &= ~(1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ClearAll() { - fixed (UInt64* p = bits) Native.Utils.Clear(p, 8); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Boolean IsSet(Int32 bit) { - fixed (UInt64* p = bits) return ((p[bit/64])&(1UL<<(bit%64))) != 0UL; - } - public override Int32 GetHashCode() { - unchecked { - var hash = 43; - fixed (UInt64* p = bits) hash = hash * 31 + HashCodeUtils.GetArrayHashCode(p, 1); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (BitSet2*)ptr; - serializer.Stream.SerializeBuffer(&p->bits[0], 1); - } - } - [StructLayout(LayoutKind.Explicit)] - public unsafe partial struct BitSet2048 { - public const Int32 SIZE = 256; - public const Int32 ALIGNMENT = 8; - [FieldOffset(0)] - private fixed UInt64 bits[32]; - public const Int32 BitsSize = 2048; - public Int32 Length { - get { - return 2048; - } - } - public static void Print(void* ptr, FramePrinter printer) { - var p = (BitSet2048*)ptr; - printer.ScopeBegin(); - UnmanagedUtils.PrintBytesBits((byte*)&p->bits, 2048, 64, printer); - printer.ScopeEnd(); - } - [System.ObsoleteAttribute("Use instance Set method instead")] - public static void Set(BitSet2048* set, Int32 bit) { - set->bits[bit/64] |= (1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance Clear method instead")] - public static void Clear(BitSet2048* set, Int32 bit) { - set->bits[bit/64] &= ~(1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance ClearAll method instead")] - public static void ClearAll(BitSet2048* set) { - Native.Utils.Clear(&set->bits[0], 256); - } - [System.ObsoleteAttribute("Use instance IsSet method instead")] - public static Boolean IsSet(BitSet2048* set, Int32 bit) { - return (set->bits[bit/64]&(1UL<<(bit%64))) != 0UL; - } - public static BitSet2048 FromArray(UInt64[] values) { - Assert.Always(32 == values.Length); - BitSet2048 result = default; - for (int i = 0; i < 32; ++i) { - result.bits[i] = values[i]; - } - return result; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Set(Int32 bit) { - Assert.Check(bit >= 0 && bit < 2048); - fixed (UInt64* p = bits) (p[bit/64]) |= (1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear(Int32 bit) { - Assert.Check(bit >= 0 && bit < 2048); - fixed (UInt64* p = bits) (p[bit/64]) &= ~(1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ClearAll() { - fixed (UInt64* p = bits) Native.Utils.Clear(p, 256); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Boolean IsSet(Int32 bit) { - fixed (UInt64* p = bits) return ((p[bit/64])&(1UL<<(bit%64))) != 0UL; - } - public override Int32 GetHashCode() { - unchecked { - var hash = 47; - fixed (UInt64* p = bits) hash = hash * 31 + HashCodeUtils.GetArrayHashCode(p, 32); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (BitSet2048*)ptr; - serializer.Stream.SerializeBuffer(&p->bits[0], 32); - } - } - [StructLayout(LayoutKind.Explicit)] - public unsafe partial struct BitSet256 { - public const Int32 SIZE = 32; - public const Int32 ALIGNMENT = 8; - [FieldOffset(0)] - private fixed UInt64 bits[4]; - public const Int32 BitsSize = 256; - public Int32 Length { - get { - return 256; - } - } - public static void Print(void* ptr, FramePrinter printer) { - var p = (BitSet256*)ptr; - printer.ScopeBegin(); - UnmanagedUtils.PrintBytesBits((byte*)&p->bits, 256, 64, printer); - printer.ScopeEnd(); - } - [System.ObsoleteAttribute("Use instance Set method instead")] - public static void Set(BitSet256* set, Int32 bit) { - set->bits[bit/64] |= (1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance Clear method instead")] - public static void Clear(BitSet256* set, Int32 bit) { - set->bits[bit/64] &= ~(1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance ClearAll method instead")] - public static void ClearAll(BitSet256* set) { - Native.Utils.Clear(&set->bits[0], 32); - } - [System.ObsoleteAttribute("Use instance IsSet method instead")] - public static Boolean IsSet(BitSet256* set, Int32 bit) { - return (set->bits[bit/64]&(1UL<<(bit%64))) != 0UL; - } - public static BitSet256 FromArray(UInt64[] values) { - Assert.Always(4 == values.Length); - BitSet256 result = default; - for (int i = 0; i < 4; ++i) { - result.bits[i] = values[i]; - } - return result; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Set(Int32 bit) { - Assert.Check(bit >= 0 && bit < 256); - fixed (UInt64* p = bits) (p[bit/64]) |= (1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear(Int32 bit) { - Assert.Check(bit >= 0 && bit < 256); - fixed (UInt64* p = bits) (p[bit/64]) &= ~(1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ClearAll() { - fixed (UInt64* p = bits) Native.Utils.Clear(p, 32); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Boolean IsSet(Int32 bit) { - fixed (UInt64* p = bits) return ((p[bit/64])&(1UL<<(bit%64))) != 0UL; - } - public override Int32 GetHashCode() { - unchecked { - var hash = 53; - fixed (UInt64* p = bits) hash = hash * 31 + HashCodeUtils.GetArrayHashCode(p, 4); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (BitSet256*)ptr; - serializer.Stream.SerializeBuffer(&p->bits[0], 4); - } - } - [StructLayout(LayoutKind.Explicit)] - public unsafe partial struct BitSet4096 { - public const Int32 SIZE = 512; - public const Int32 ALIGNMENT = 8; - [FieldOffset(0)] - private fixed UInt64 bits[64]; - public const Int32 BitsSize = 4096; - public Int32 Length { - get { - return 4096; - } - } - public static void Print(void* ptr, FramePrinter printer) { - var p = (BitSet4096*)ptr; - printer.ScopeBegin(); - UnmanagedUtils.PrintBytesBits((byte*)&p->bits, 4096, 64, printer); - printer.ScopeEnd(); - } - [System.ObsoleteAttribute("Use instance Set method instead")] - public static void Set(BitSet4096* set, Int32 bit) { - set->bits[bit/64] |= (1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance Clear method instead")] - public static void Clear(BitSet4096* set, Int32 bit) { - set->bits[bit/64] &= ~(1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance ClearAll method instead")] - public static void ClearAll(BitSet4096* set) { - Native.Utils.Clear(&set->bits[0], 512); - } - [System.ObsoleteAttribute("Use instance IsSet method instead")] - public static Boolean IsSet(BitSet4096* set, Int32 bit) { - return (set->bits[bit/64]&(1UL<<(bit%64))) != 0UL; - } - public static BitSet4096 FromArray(UInt64[] values) { - Assert.Always(64 == values.Length); - BitSet4096 result = default; - for (int i = 0; i < 64; ++i) { - result.bits[i] = values[i]; - } - return result; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Set(Int32 bit) { - Assert.Check(bit >= 0 && bit < 4096); - fixed (UInt64* p = bits) (p[bit/64]) |= (1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear(Int32 bit) { - Assert.Check(bit >= 0 && bit < 4096); - fixed (UInt64* p = bits) (p[bit/64]) &= ~(1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ClearAll() { - fixed (UInt64* p = bits) Native.Utils.Clear(p, 512); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Boolean IsSet(Int32 bit) { - fixed (UInt64* p = bits) return ((p[bit/64])&(1UL<<(bit%64))) != 0UL; - } - public override Int32 GetHashCode() { - unchecked { - var hash = 59; - fixed (UInt64* p = bits) hash = hash * 31 + HashCodeUtils.GetArrayHashCode(p, 64); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (BitSet4096*)ptr; - serializer.Stream.SerializeBuffer(&p->bits[0], 64); - } - } - [StructLayout(LayoutKind.Explicit)] - public unsafe partial struct BitSet512 { - public const Int32 SIZE = 64; - public const Int32 ALIGNMENT = 8; - [FieldOffset(0)] - private fixed UInt64 bits[8]; - public const Int32 BitsSize = 512; - public Int32 Length { - get { - return 512; - } - } - public static void Print(void* ptr, FramePrinter printer) { - var p = (BitSet512*)ptr; - printer.ScopeBegin(); - UnmanagedUtils.PrintBytesBits((byte*)&p->bits, 512, 64, printer); - printer.ScopeEnd(); - } - [System.ObsoleteAttribute("Use instance Set method instead")] - public static void Set(BitSet512* set, Int32 bit) { - set->bits[bit/64] |= (1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance Clear method instead")] - public static void Clear(BitSet512* set, Int32 bit) { - set->bits[bit/64] &= ~(1UL<<(bit%64)); - } - [System.ObsoleteAttribute("Use instance ClearAll method instead")] - public static void ClearAll(BitSet512* set) { - Native.Utils.Clear(&set->bits[0], 64); - } - [System.ObsoleteAttribute("Use instance IsSet method instead")] - public static Boolean IsSet(BitSet512* set, Int32 bit) { - return (set->bits[bit/64]&(1UL<<(bit%64))) != 0UL; - } - public static BitSet512 FromArray(UInt64[] values) { - Assert.Always(8 == values.Length); - BitSet512 result = default; - for (int i = 0; i < 8; ++i) { - result.bits[i] = values[i]; - } - return result; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Set(Int32 bit) { - Assert.Check(bit >= 0 && bit < 512); - fixed (UInt64* p = bits) (p[bit/64]) |= (1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear(Int32 bit) { - Assert.Check(bit >= 0 && bit < 512); - fixed (UInt64* p = bits) (p[bit/64]) &= ~(1UL<<(bit%64)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ClearAll() { - fixed (UInt64* p = bits) Native.Utils.Clear(p, 64); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Boolean IsSet(Int32 bit) { - fixed (UInt64* p = bits) return ((p[bit/64])&(1UL<<(bit%64))) != 0UL; - } - public override Int32 GetHashCode() { - unchecked { - var hash = 61; - fixed (UInt64* p = bits) hash = hash * 31 + HashCodeUtils.GetArrayHashCode(p, 8); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (BitSet512*)ptr; - serializer.Stream.SerializeBuffer(&p->bits[0], 8); - } - } - [StructLayout(LayoutKind.Explicit)] - [Quantum.AssetRefAttribute(typeof(BallPoolSpec))] - [System.SerializableAttribute()] - public unsafe partial struct AssetRefBallPoolSpec : IEquatable, IAssetRef { - public const Int32 SIZE = 8; - public const Int32 ALIGNMENT = 8; - [FieldOffset(0)] - public AssetGuid Id; - public override String ToString() { - return AssetRef.ToString(Id); - } - public static implicit operator AssetRefBallPoolSpec(BallPoolSpec value) { - var r = default(AssetRefBallPoolSpec); - if (value != null) { - r.Id = value.Guid; - } - return r; - } - public override Boolean Equals(Object obj) { - return obj is AssetRefBallPoolSpec other && Equals(other); - } - public Boolean Equals(AssetRefBallPoolSpec other) { - return Id.Equals(other.Id); - } - public static Boolean operator ==(AssetRefBallPoolSpec a, AssetRefBallPoolSpec b) { - return a.Id == b.Id; - } - public static Boolean operator !=(AssetRefBallPoolSpec a, AssetRefBallPoolSpec b) { - return a.Id != b.Id; - } - public override Int32 GetHashCode() { - unchecked { - var hash = 67; - hash = hash * 31 + Id.GetHashCode(); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (AssetRefBallPoolSpec*)ptr; - AssetGuid.Serialize(&p->Id, serializer); - } - } - [StructLayout(LayoutKind.Explicit)] - [Quantum.AssetRefAttribute(typeof(ConfigAssets))] - [System.SerializableAttribute()] - public unsafe partial struct AssetRefConfigAssets : IEquatable, IAssetRef { - public const Int32 SIZE = 8; - public const Int32 ALIGNMENT = 8; - [FieldOffset(0)] - public AssetGuid Id; - public override String ToString() { - return AssetRef.ToString(Id); - } - public static implicit operator AssetRefConfigAssets(ConfigAssets value) { - var r = default(AssetRefConfigAssets); - if (value != null) { - r.Id = value.Guid; - } - return r; - } - public override Boolean Equals(Object obj) { - return obj is AssetRefConfigAssets other && Equals(other); - } - public Boolean Equals(AssetRefConfigAssets other) { - return Id.Equals(other.Id); - } - public static Boolean operator ==(AssetRefConfigAssets a, AssetRefConfigAssets b) { - return a.Id == b.Id; - } - public static Boolean operator !=(AssetRefConfigAssets a, AssetRefConfigAssets b) { - return a.Id != b.Id; - } - public override Int32 GetHashCode() { - unchecked { - var hash = 71; - hash = hash * 31 + Id.GetHashCode(); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (AssetRefConfigAssets*)ptr; - AssetGuid.Serialize(&p->Id, serializer); - } - } - [StructLayout(LayoutKind.Explicit)] - [Quantum.AssetRefAttribute(typeof(GameConfig))] - [System.SerializableAttribute()] - public unsafe partial struct AssetRefGameConfig : IEquatable, IAssetRef { - public const Int32 SIZE = 8; - public const Int32 ALIGNMENT = 8; - [FieldOffset(0)] - public AssetGuid Id; - public override String ToString() { - return AssetRef.ToString(Id); - } - public static implicit operator AssetRefGameConfig(GameConfig value) { - var r = default(AssetRefGameConfig); - if (value != null) { - r.Id = value.Guid; - } - return r; - } - public override Boolean Equals(Object obj) { - return obj is AssetRefGameConfig other && Equals(other); - } - public Boolean Equals(AssetRefGameConfig other) { - return Id.Equals(other.Id); - } - public static Boolean operator ==(AssetRefGameConfig a, AssetRefGameConfig b) { - return a.Id == b.Id; - } - public static Boolean operator !=(AssetRefGameConfig a, AssetRefGameConfig b) { - return a.Id != b.Id; - } - public override Int32 GetHashCode() { - unchecked { - var hash = 73; - hash = hash * 31 + Id.GetHashCode(); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (AssetRefGameConfig*)ptr; - AssetGuid.Serialize(&p->Id, serializer); - } - } - [StructLayout(LayoutKind.Explicit)] - [Quantum.AssetRefAttribute(typeof(TurnConfig))] - [System.SerializableAttribute()] - public unsafe partial struct AssetRefTurnConfig : IEquatable, IAssetRef { - public const Int32 SIZE = 8; - public const Int32 ALIGNMENT = 8; - [FieldOffset(0)] - public AssetGuid Id; - public override String ToString() { - return AssetRef.ToString(Id); - } - public static implicit operator AssetRefTurnConfig(TurnConfig value) { - var r = default(AssetRefTurnConfig); - if (value != null) { - r.Id = value.Guid; - } - return r; - } - public override Boolean Equals(Object obj) { - return obj is AssetRefTurnConfig other && Equals(other); - } - public Boolean Equals(AssetRefTurnConfig other) { - return Id.Equals(other.Id); - } - public static Boolean operator ==(AssetRefTurnConfig a, AssetRefTurnConfig b) { - return a.Id == b.Id; - } - public static Boolean operator !=(AssetRefTurnConfig a, AssetRefTurnConfig b) { - return a.Id != b.Id; - } - public override Int32 GetHashCode() { - unchecked { - var hash = 79; - hash = hash * 31 + Id.GetHashCode(); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (AssetRefTurnConfig*)ptr; - AssetGuid.Serialize(&p->Id, serializer); - } - } - [StructLayout(LayoutKind.Explicit)] - [Quantum.AssetRefAttribute(typeof(UserMap))] - [System.SerializableAttribute()] - public unsafe partial struct AssetRefUserMap : IEquatable, IAssetRef { - public const Int32 SIZE = 8; - public const Int32 ALIGNMENT = 8; - [FieldOffset(0)] - public AssetGuid Id; - public override String ToString() { - return AssetRef.ToString(Id); - } - public static implicit operator AssetRefUserMap(UserMap value) { - var r = default(AssetRefUserMap); - if (value != null) { - r.Id = value.Guid; - } - return r; - } - public override Boolean Equals(Object obj) { - return obj is AssetRefUserMap other && Equals(other); - } - public Boolean Equals(AssetRefUserMap other) { - return Id.Equals(other.Id); - } - public static Boolean operator ==(AssetRefUserMap a, AssetRefUserMap b) { - return a.Id == b.Id; - } - public static Boolean operator !=(AssetRefUserMap a, AssetRefUserMap b) { - return a.Id != b.Id; - } - public override Int32 GetHashCode() { - unchecked { - var hash = 83; - hash = hash * 31 + Id.GetHashCode(); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (AssetRefUserMap*)ptr; - AssetGuid.Serialize(&p->Id, serializer); - } - } - [StructLayout(LayoutKind.Explicit)] - public unsafe partial struct BallPoolPlayer { - public const Int32 SIZE = 48; - public const Int32 ALIGNMENT = 8; - [FieldOffset(0)] - public PlayerRef Ref; - [FieldOffset(4)] - public QBoolean StripedBalls; - [FieldOffset(8)] - public TurnData TurnStats; - public override Int32 GetHashCode() { - unchecked { - var hash = 89; - hash = hash * 31 + Ref.GetHashCode(); - hash = hash * 31 + StripedBalls.GetHashCode(); - hash = hash * 31 + TurnStats.GetHashCode(); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (BallPoolPlayer*)ptr; - PlayerRef.Serialize(&p->Ref, serializer); - QBoolean.Serialize(&p->StripedBalls, serializer); - Quantum.TurnData.Serialize(&p->TurnStats, serializer); - } - } - [StructLayout(LayoutKind.Explicit)] - public unsafe partial struct Input { - public const Int32 SIZE = 48; - public const Int32 ALIGNMENT = 8; - [FieldOffset(8)] - public FPVector2 BallPosition; - [FieldOffset(24)] - public FPVector3 Direction; - [FieldOffset(0)] - public FP ForceBarMarkPos; - public const int MAX_COUNT = 2; - public override Int32 GetHashCode() { - unchecked { - var hash = 97; - hash = hash * 31 + BallPosition.GetHashCode(); - hash = hash * 31 + Direction.GetHashCode(); - hash = hash * 31 + ForceBarMarkPos.GetHashCode(); - return hash; - } - } - public static Input Read(FrameSerializer serializer) { - Input i = new Input(); - Serialize(&i, serializer); - return i; - } - public static void Write(FrameSerializer serializer, Input i) { - Serialize(&i, serializer); - } - public Boolean IsDown(InputButtons button) { - switch (button) { - } - return false; - } - public Boolean WasPressed(InputButtons button) { - switch (button) { - } - return false; - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (Input*)ptr; - FP.Serialize(&p->ForceBarMarkPos, serializer); - FPVector2.Serialize(&p->BallPosition, serializer); - FPVector3.Serialize(&p->Direction, serializer); - } - } - [StructLayout(LayoutKind.Explicit)] - public unsafe partial struct TurnData { - public const Int32 SIZE = 40; - public const Int32 ALIGNMENT = 8; - [FieldOffset(24)] - public AssetRefTurnConfig ConfigRef; - [FieldOffset(32)] - public EntityRef Entity; - [FieldOffset(0)] - public Int32 Number; - [FieldOffset(8)] - public PlayerRef Player; - [FieldOffset(12)] - public TurnStatus Status; - [FieldOffset(4)] - public Int32 Ticks; - [FieldOffset(16)] - public TurnType Type; - public override Int32 GetHashCode() { - unchecked { - var hash = 101; - hash = hash * 31 + ConfigRef.GetHashCode(); - hash = hash * 31 + Entity.GetHashCode(); - hash = hash * 31 + Number.GetHashCode(); - hash = hash * 31 + Player.GetHashCode(); - hash = hash * 31 + (Int32)Status; - hash = hash * 31 + Ticks.GetHashCode(); - hash = hash * 31 + (Int32)Type; - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (TurnData*)ptr; - serializer.Stream.Serialize(&p->Number); - serializer.Stream.Serialize(&p->Ticks); - PlayerRef.Serialize(&p->Player, serializer); - serializer.Stream.Serialize((Int32*)&p->Status); - serializer.Stream.Serialize((Int32*)&p->Type); - Quantum.AssetRefTurnConfig.Serialize(&p->ConfigRef, serializer); - EntityRef.Serialize(&p->Entity, serializer); - } - } - [StructLayout(LayoutKind.Explicit)] - public unsafe partial struct _globals_ { - public const Int32 SIZE = 776; - public const Int32 ALIGNMENT = 8; - [FieldOffset(4)] - public QBoolean CapturedEighthBall; - [FieldOffset(120)] - public TurnData CurrentTurn; - [FieldOffset(48)] - public FP DeltaTime; - [FieldOffset(80)] - public FrameMetaData FrameMetaData; - [FieldOffset(8)] - public QBoolean HasFirstCaptured; - [FieldOffset(12)] - public QBoolean IsFirstTurn; - [FieldOffset(32)] - public AssetRefMap Map; - [FieldOffset(56)] - public NavMeshRegionMask NavMeshRegions; - [FieldOffset(480)] - public PhysicsSceneSettings PhysicsSettings; - [FieldOffset(40)] - public BitSet2 PlayerLastConnectionState; - [FieldOffset(16)] - public QBoolean PlayerScoreThisTurn; - [FieldOffset(160)] - [FramePrinter.FixedArrayAttribute(typeof(BallPoolPlayer), 2)] - private fixed Byte _Players_[96]; - [FieldOffset(20)] - public QBoolean ReplaceWhiteBall; - [FieldOffset(64)] - public RNGSession RngSession; - [FieldOffset(352)] - public BitSet1024 Systems; - [FieldOffset(0)] - public Int32 TicksResolving; - [FieldOffset(24)] - public QBoolean WhiteBallFirstContact; - [FieldOffset(256)] - [FramePrinter.FixedArrayAttribute(typeof(Input), 2)] - private fixed Byte _input_[96]; - public FixedArray Players { - get { - fixed (byte* p = _Players_) { return new FixedArray(p, 48, 2); } - } - } - public FixedArray input { - get { - fixed (byte* p = _input_) { return new FixedArray(p, 48, 2); } - } - } - public override Int32 GetHashCode() { - unchecked { - var hash = 103; - hash = hash * 31 + CapturedEighthBall.GetHashCode(); - hash = hash * 31 + CurrentTurn.GetHashCode(); - hash = hash * 31 + DeltaTime.GetHashCode(); - hash = hash * 31 + FrameMetaData.GetHashCode(); - hash = hash * 31 + HasFirstCaptured.GetHashCode(); - hash = hash * 31 + IsFirstTurn.GetHashCode(); - hash = hash * 31 + Map.GetHashCode(); - hash = hash * 31 + NavMeshRegions.GetHashCode(); - hash = hash * 31 + PhysicsSettings.GetHashCode(); - hash = hash * 31 + PlayerLastConnectionState.GetHashCode(); - hash = hash * 31 + PlayerScoreThisTurn.GetHashCode(); - hash = hash * 31 + HashCodeUtils.GetArrayHashCode(Players); - hash = hash * 31 + ReplaceWhiteBall.GetHashCode(); - hash = hash * 31 + RngSession.GetHashCode(); - hash = hash * 31 + Systems.GetHashCode(); - hash = hash * 31 + TicksResolving.GetHashCode(); - hash = hash * 31 + WhiteBallFirstContact.GetHashCode(); - hash = hash * 31 + HashCodeUtils.GetArrayHashCode(input); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (_globals_*)ptr; - serializer.Stream.Serialize(&p->TicksResolving); - QBoolean.Serialize(&p->CapturedEighthBall, serializer); - QBoolean.Serialize(&p->HasFirstCaptured, serializer); - QBoolean.Serialize(&p->IsFirstTurn, serializer); - QBoolean.Serialize(&p->PlayerScoreThisTurn, serializer); - QBoolean.Serialize(&p->ReplaceWhiteBall, serializer); - QBoolean.Serialize(&p->WhiteBallFirstContact, serializer); - AssetRefMap.Serialize(&p->Map, serializer); - Quantum.BitSet2.Serialize(&p->PlayerLastConnectionState, serializer); - FP.Serialize(&p->DeltaTime, serializer); - NavMeshRegionMask.Serialize(&p->NavMeshRegions, serializer); - RNGSession.Serialize(&p->RngSession, serializer); - FrameMetaData.Serialize(&p->FrameMetaData, serializer); - Quantum.TurnData.Serialize(&p->CurrentTurn, serializer); - FixedArray.Serialize(p->Players, serializer, StaticDelegates.SerializeBallPoolPlayer); - FixedArray.Serialize(p->input, serializer, StaticDelegates.SerializeInput); - Quantum.BitSet1024.Serialize(&p->Systems, serializer); - PhysicsSceneSettings.Serialize(&p->PhysicsSettings, serializer); - } - } - [StructLayout(LayoutKind.Explicit)] - public unsafe partial struct BallFields : Quantum.IComponent { - public const Int32 SIZE = 40; - public const Int32 ALIGNMENT = 8; - [FieldOffset(4)] - public QBoolean InTable; - [FieldOffset(0)] - public Int32 Number; - [FieldOffset(16)] - public AssetRefBallPoolSpec Spec; - [FieldOffset(24)] - public FPVector2 Spin; - [FieldOffset(8)] - public QBoolean Striped; - public override Int32 GetHashCode() { - unchecked { - var hash = 107; - hash = hash * 31 + InTable.GetHashCode(); - hash = hash * 31 + Number.GetHashCode(); - hash = hash * 31 + Spec.GetHashCode(); - hash = hash * 31 + Spin.GetHashCode(); - hash = hash * 31 + Striped.GetHashCode(); - return hash; - } - } - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (BallFields*)ptr; - serializer.Stream.Serialize(&p->Number); - QBoolean.Serialize(&p->InTable, serializer); - QBoolean.Serialize(&p->Striped, serializer); - Quantum.AssetRefBallPoolSpec.Serialize(&p->Spec, serializer); - FPVector2.Serialize(&p->Spin, serializer); - } - } - public unsafe partial class Frame { - private ISignalOnBallPoolShot[] _ISignalOnBallPoolShotSystems; - private ISignalOnBallPoolHitHole[] _ISignalOnBallPoolHitHoleSystems; - private ISignalOnTurnEnded[] _ISignalOnTurnEndedSystems; - private ISignalOnPlayCommandReceived[] _ISignalOnPlayCommandReceivedSystems; - private ISignalOnSkipCommandReceived[] _ISignalOnSkipCommandReceivedSystems; - partial void AllocGen() { - _globals = (_globals_*)Context.Allocator.AllocAndClear(sizeof(_globals_)); - } - partial void FreeGen() { - Context.Allocator.Free(_globals); - } - partial void CopyFromGen(Frame frame) { - Native.Utils.Copy(_globals, frame._globals, sizeof(_globals_)); - } - static partial void InitStaticGen() { - ComponentTypeId.Setup(() => { - ComponentTypeId.Add(Quantum.BallFields.Serialize, null, null, ComponentFlags.None); - }); - } - partial void InitGen() { - Initialize(this, this.SimulationConfig.Entities); - _ISignalOnBallPoolShotSystems = BuildSignalsArray(); - _ISignalOnBallPoolHitHoleSystems = BuildSignalsArray(); - _ISignalOnTurnEndedSystems = BuildSignalsArray(); - _ISignalOnPlayCommandReceivedSystems = BuildSignalsArray(); - _ISignalOnSkipCommandReceivedSystems = BuildSignalsArray(); - _ComponentSignalsOnAdded = new ComponentReactiveCallbackInvoker[ComponentTypeId.Type.Length]; - _ComponentSignalsOnRemoved = new ComponentReactiveCallbackInvoker[ComponentTypeId.Type.Length]; - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - BuildSignalsArrayOnComponentAdded(); - BuildSignalsArrayOnComponentRemoved(); - } - public void SetPlayerInput(Int32 player, Input input) { - if ((uint)player >= (uint)_globals->input.Length) { throw new System.ArgumentOutOfRangeException("player"); } - var i = _globals->input.GetPointer(player); - i->Direction = input.Direction; - i->ForceBarMarkPos = input.ForceBarMarkPos; - i->BallPosition = input.BallPosition; - } - public Input* GetPlayerInput(Int32 player) { - if ((uint)player >= (uint)_globals->input.Length) { throw new System.ArgumentOutOfRangeException("player"); } - return _globals->input.GetPointer(player); - } - public unsafe partial struct FrameSignals { - public void OnBallPoolShot(PlayerRef player) { - var array = _f._ISignalOnBallPoolShotSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnBallPoolShot(_f, player); - } - } - } - public void OnBallPoolHitHole(EntityRef ball) { - var array = _f._ISignalOnBallPoolHitHoleSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnBallPoolHitHole(_f, ball); - } - } - } - public void OnTurnEnded(TurnData data, TurnEndReason reason) { - var array = _f._ISignalOnTurnEndedSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnTurnEnded(_f, data, reason); - } - } - } - public void OnPlayCommandReceived(PlayerRef player, PlayCommandData data) { - var array = _f._ISignalOnPlayCommandReceivedSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnPlayCommandReceived(_f, player, data); - } - } - } - public void OnSkipCommandReceived(PlayerRef player, SkipCommandData data) { - var array = _f._ISignalOnSkipCommandReceivedSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnSkipCommandReceived(_f, player, data); - } - } - } - } - public unsafe partial struct FrameEvents { - public const Int32 EVENT_TYPE_COUNT = 18; - public static Int32 GetParentEventID(Int32 eventID) { - switch (eventID) { - case EventTurnTypeChanged.ID: return EventTurnEvent.ID; - case EventTurnStatusChanged.ID: return EventTurnEvent.ID; - case EventTurnEnded.ID: return EventTurnEvent.ID; - case EventTurnTimerReset.ID: return EventTurnEvent.ID; - case EventTurnActivated.ID: return EventTurnEvent.ID; - case EventPlayCommandReceived.ID: return EventCommandEvent.ID; - case EventSkipCommandReceived.ID: return EventCommandEvent.ID; - default: return -1; - } - } - public static System.Type GetEventType(Int32 eventID) { - switch (eventID) { - case EventMessage.ID: return typeof(EventMessage); - case EventRemoveBall.ID: return typeof(EventRemoveBall); - case EventEndGame.ID: return typeof(EventEndGame); - case EventReplaceBall.ID: return typeof(EventReplaceBall); - case EventCueHitBall.ID: return typeof(EventCueHitBall); - case EventBallsCollision.ID: return typeof(EventBallsCollision); - case EventBallsHitWall.ID: return typeof(EventBallsHitWall); - case EventBallsHitHole.ID: return typeof(EventBallsHitHole); - case EventGameplayEnded.ID: return typeof(EventGameplayEnded); - case EventTurnEvent.ID: return typeof(EventTurnEvent); - case EventTurnTypeChanged.ID: return typeof(EventTurnTypeChanged); - case EventTurnStatusChanged.ID: return typeof(EventTurnStatusChanged); - case EventTurnEnded.ID: return typeof(EventTurnEnded); - case EventTurnTimerReset.ID: return typeof(EventTurnTimerReset); - case EventTurnActivated.ID: return typeof(EventTurnActivated); - case EventCommandEvent.ID: return typeof(EventCommandEvent); - case EventPlayCommandReceived.ID: return typeof(EventPlayCommandReceived); - case EventSkipCommandReceived.ID: return typeof(EventSkipCommandReceived); - default: throw new System.ArgumentOutOfRangeException("eventID"); - } - } - public EventMessage Message(String Header, String Text) { - var ev = _f.Context.AcquireEvent(EventMessage.ID); - ev.Header = Header; - ev.Text = Text; - _f.AddEvent(ev); - return ev; - } - public EventRemoveBall RemoveBall(Int32 Num) { - var ev = _f.Context.AcquireEvent(EventRemoveBall.ID); - ev.Num = Num; - _f.AddEvent(ev); - return ev; - } - public EventEndGame EndGame(PlayerRef Winner) { - var ev = _f.Context.AcquireEvent(EventEndGame.ID); - ev.Winner = Winner; - _f.AddEvent(ev); - return ev; - } - public EventReplaceBall ReplaceBall() { - var ev = _f.Context.AcquireEvent(EventReplaceBall.ID); - _f.AddEvent(ev); - return ev; - } - public EventCueHitBall CueHitBall() { - var ev = _f.Context.AcquireEvent(EventCueHitBall.ID); - _f.AddEvent(ev); - return ev; - } - public EventBallsCollision BallsCollision() { - var ev = _f.Context.AcquireEvent(EventBallsCollision.ID); - _f.AddEvent(ev); - return ev; - } - public EventBallsHitWall BallsHitWall() { - var ev = _f.Context.AcquireEvent(EventBallsHitWall.ID); - _f.AddEvent(ev); - return ev; - } - public EventBallsHitHole BallsHitHole() { - var ev = _f.Context.AcquireEvent(EventBallsHitHole.ID); - _f.AddEvent(ev); - return ev; - } - public EventGameplayEnded GameplayEnded() { - if (_f.IsPredicted) return null; - var ev = _f.Context.AcquireEvent(EventGameplayEnded.ID); - _f.AddEvent(ev); - return ev; - } - public EventTurnTypeChanged TurnTypeChanged(TurnData Turn, TurnType PreviousType) { - if (_f.IsPredicted) return null; - var ev = _f.Context.AcquireEvent(EventTurnTypeChanged.ID); - ev.Turn = Turn; - ev.PreviousType = PreviousType; - _f.AddEvent(ev); - return ev; - } - public EventTurnStatusChanged TurnStatusChanged(TurnData Turn, TurnStatus PreviousStatus) { - if (_f.IsPredicted) return null; - var ev = _f.Context.AcquireEvent(EventTurnStatusChanged.ID); - ev.Turn = Turn; - ev.PreviousStatus = PreviousStatus; - _f.AddEvent(ev); - return ev; - } - public EventTurnEnded TurnEnded(TurnData Turn, TurnEndReason Reason) { - if (_f.IsPredicted) return null; - var ev = _f.Context.AcquireEvent(EventTurnEnded.ID); - ev.Turn = Turn; - ev.Reason = Reason; - _f.AddEvent(ev); - return ev; - } - public EventTurnTimerReset TurnTimerReset(TurnData Turn) { - if (_f.IsPredicted) return null; - var ev = _f.Context.AcquireEvent(EventTurnTimerReset.ID); - ev.Turn = Turn; - _f.AddEvent(ev); - return ev; - } - public EventTurnActivated TurnActivated(TurnData Turn) { - if (_f.IsPredicted) return null; - var ev = _f.Context.AcquireEvent(EventTurnActivated.ID); - ev.Turn = Turn; - _f.AddEvent(ev); - return ev; - } - public EventPlayCommandReceived PlayCommandReceived(PlayerRef Player, PlayCommandData Data) { - var ev = _f.Context.AcquireEvent(EventPlayCommandReceived.ID); - ev.Player = Player; - ev.Data = Data; - _f.AddEvent(ev); - return ev; - } - public EventSkipCommandReceived SkipCommandReceived(PlayerRef Player, SkipCommandData Data) { - var ev = _f.Context.AcquireEvent(EventSkipCommandReceived.ID); - ev.Player = Player; - ev.Data = Data; - _f.AddEvent(ev); - return ev; - } - } - public unsafe partial struct FrameAssets { - public BallPoolSpec BallPoolSpec(AssetRefBallPoolSpec assetRef) { - return _f.FindAsset(assetRef.Id); - } - public ConfigAssets ConfigAssets(AssetRefConfigAssets assetRef) { - return _f.FindAsset(assetRef.Id); - } - public GameConfig GameConfig(AssetRefGameConfig assetRef) { - return _f.FindAsset(assetRef.Id); - } - public UserMap UserMap(AssetRefUserMap assetRef) { - return _f.FindAsset(assetRef.Id); - } - public TurnConfig TurnConfig(AssetRefTurnConfig assetRef) { - return _f.FindAsset(assetRef.Id); - } - } - } - public unsafe interface ISignalOnBallPoolShot : ISignal { - void OnBallPoolShot(Frame f, PlayerRef player); - } - public unsafe interface ISignalOnBallPoolHitHole : ISignal { - void OnBallPoolHitHole(Frame f, EntityRef ball); - } - public unsafe interface ISignalOnTurnEnded : ISignal { - void OnTurnEnded(Frame f, TurnData data, TurnEndReason reason); - } - public unsafe interface ISignalOnPlayCommandReceived : ISignal { - void OnPlayCommandReceived(Frame f, PlayerRef player, PlayCommandData data); - } - public unsafe interface ISignalOnSkipCommandReceived : ISignal { - void OnSkipCommandReceived(Frame f, PlayerRef player, SkipCommandData data); - } - public unsafe partial class EventMessage : EventBase { - public new const Int32 ID = 0; - public String Header; - public String Text; - protected EventMessage(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventMessage() : - base(0, EventFlags.Server|EventFlags.Client) { - } - public new QuantumGame Game { - get { - return (QuantumGame)base.Game; - } - set { - base.Game = value; - } - } - public override Int32 GetHashCode() { - unchecked { - var hash = 37; - hash = hash * 31 + Header.GetHashCode(); - hash = hash * 31 + Text.GetHashCode(); - return hash; - } - } - } - public unsafe partial class EventRemoveBall : EventBase { - public new const Int32 ID = 1; - public Int32 Num; - protected EventRemoveBall(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventRemoveBall() : - base(1, EventFlags.Server|EventFlags.Client) { - } - public new QuantumGame Game { - get { - return (QuantumGame)base.Game; - } - set { - base.Game = value; - } - } - public override Int32 GetHashCode() { - unchecked { - var hash = 41; - hash = hash * 31 + Num.GetHashCode(); - return hash; - } - } - } - public unsafe partial class EventEndGame : EventBase { - public new const Int32 ID = 2; - public PlayerRef Winner; - protected EventEndGame(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventEndGame() : - base(2, EventFlags.Server|EventFlags.Client) { - } - public new QuantumGame Game { - get { - return (QuantumGame)base.Game; - } - set { - base.Game = value; - } - } - public override Int32 GetHashCode() { - unchecked { - var hash = 43; - hash = hash * 31 + Winner.GetHashCode(); - return hash; - } - } - } - public unsafe partial class EventReplaceBall : EventBase { - public new const Int32 ID = 3; - protected EventReplaceBall(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventReplaceBall() : - base(3, EventFlags.Server|EventFlags.Client) { - } - public new QuantumGame Game { - get { - return (QuantumGame)base.Game; - } - set { - base.Game = value; - } - } - public override Int32 GetHashCode() { - unchecked { - var hash = 47; - return hash; - } - } - } - public unsafe partial class EventCueHitBall : EventBase { - public new const Int32 ID = 4; - protected EventCueHitBall(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventCueHitBall() : - base(4, EventFlags.Server|EventFlags.Client) { - } - public new QuantumGame Game { - get { - return (QuantumGame)base.Game; - } - set { - base.Game = value; - } - } - public override Int32 GetHashCode() { - unchecked { - var hash = 53; - return hash; - } - } - } - public unsafe partial class EventBallsCollision : EventBase { - public new const Int32 ID = 5; - protected EventBallsCollision(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventBallsCollision() : - base(5, EventFlags.Server|EventFlags.Client) { - } - public new QuantumGame Game { - get { - return (QuantumGame)base.Game; - } - set { - base.Game = value; - } - } - public override Int32 GetHashCode() { - unchecked { - var hash = 59; - return hash; - } - } - } - public unsafe partial class EventBallsHitWall : EventBase { - public new const Int32 ID = 6; - protected EventBallsHitWall(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventBallsHitWall() : - base(6, EventFlags.Server|EventFlags.Client) { - } - public new QuantumGame Game { - get { - return (QuantumGame)base.Game; - } - set { - base.Game = value; - } - } - public override Int32 GetHashCode() { - unchecked { - var hash = 61; - return hash; - } - } - } - public unsafe partial class EventBallsHitHole : EventBase { - public new const Int32 ID = 7; - protected EventBallsHitHole(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventBallsHitHole() : - base(7, EventFlags.Server|EventFlags.Client) { - } - public new QuantumGame Game { - get { - return (QuantumGame)base.Game; - } - set { - base.Game = value; - } - } - public override Int32 GetHashCode() { - unchecked { - var hash = 67; - return hash; - } - } - } - public unsafe partial class EventGameplayEnded : EventBase { - public new const Int32 ID = 8; - protected EventGameplayEnded(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventGameplayEnded() : - base(8, EventFlags.Server|EventFlags.Client|EventFlags.Synced) { - } - public new QuantumGame Game { - get { - return (QuantumGame)base.Game; - } - set { - base.Game = value; - } - } - public override Int32 GetHashCode() { - unchecked { - var hash = 71; - return hash; - } - } - } - public abstract unsafe partial class EventTurnEvent : EventBase { - public new const Int32 ID = 9; - public TurnData Turn; - protected EventTurnEvent(Int32 id, EventFlags flags) : - base(id, flags) { - } - public new QuantumGame Game { - get { - return (QuantumGame)base.Game; - } - set { - base.Game = value; - } - } - public override Int32 GetHashCode() { - unchecked { - var hash = 73; - hash = hash * 31 + Turn.GetHashCode(); - return hash; - } - } - } - public unsafe partial class EventTurnTypeChanged : EventTurnEvent { - public new const Int32 ID = 10; - public TurnType PreviousType; - protected EventTurnTypeChanged(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventTurnTypeChanged() : - base(10, EventFlags.Server|EventFlags.Client|EventFlags.Synced) { - } - public override Int32 GetHashCode() { - unchecked { - var hash = 79; - hash = hash * 31 + Turn.GetHashCode(); - hash = hash * 31 + PreviousType.GetHashCode(); - return hash; - } - } - } - public unsafe partial class EventTurnStatusChanged : EventTurnEvent { - public new const Int32 ID = 11; - public TurnStatus PreviousStatus; - protected EventTurnStatusChanged(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventTurnStatusChanged() : - base(11, EventFlags.Server|EventFlags.Client|EventFlags.Synced) { - } - public override Int32 GetHashCode() { - unchecked { - var hash = 83; - hash = hash * 31 + Turn.GetHashCode(); - hash = hash * 31 + PreviousStatus.GetHashCode(); - return hash; - } - } - } - public unsafe partial class EventTurnEnded : EventTurnEvent { - public new const Int32 ID = 12; - public TurnEndReason Reason; - protected EventTurnEnded(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventTurnEnded() : - base(12, EventFlags.Server|EventFlags.Client|EventFlags.Synced) { - } - public override Int32 GetHashCode() { - unchecked { - var hash = 89; - hash = hash * 31 + Turn.GetHashCode(); - hash = hash * 31 + Reason.GetHashCode(); - return hash; - } - } - } - public unsafe partial class EventTurnTimerReset : EventTurnEvent { - public new const Int32 ID = 13; - protected EventTurnTimerReset(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventTurnTimerReset() : - base(13, EventFlags.Server|EventFlags.Client|EventFlags.Synced) { - } - public override Int32 GetHashCode() { - unchecked { - var hash = 97; - hash = hash * 31 + Turn.GetHashCode(); - return hash; - } - } - } - public unsafe partial class EventTurnActivated : EventTurnEvent { - public new const Int32 ID = 14; - protected EventTurnActivated(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventTurnActivated() : - base(14, EventFlags.Server|EventFlags.Client|EventFlags.Synced) { - } - public override Int32 GetHashCode() { - unchecked { - var hash = 101; - hash = hash * 31 + Turn.GetHashCode(); - return hash; - } - } - } - public abstract unsafe partial class EventCommandEvent : EventBase { - public new const Int32 ID = 15; - public PlayerRef Player; - protected EventCommandEvent(Int32 id, EventFlags flags) : - base(id, flags) { - } - public new QuantumGame Game { - get { - return (QuantumGame)base.Game; - } - set { - base.Game = value; - } - } - public override Int32 GetHashCode() { - unchecked { - var hash = 103; - hash = hash * 31 + Player.GetHashCode(); - return hash; - } - } - } - public unsafe partial class EventPlayCommandReceived : EventCommandEvent { - public new const Int32 ID = 16; - public PlayCommandData Data; - protected EventPlayCommandReceived(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventPlayCommandReceived() : - base(16, EventFlags.Server|EventFlags.Client) { - } - public override Int32 GetHashCode() { - unchecked { - var hash = 107; - hash = hash * 31 + Player.GetHashCode(); - hash = hash * 31 + Data.GetHashCode(); - return hash; - } - } - } - public unsafe partial class EventSkipCommandReceived : EventCommandEvent { - public new const Int32 ID = 17; - public SkipCommandData Data; - protected EventSkipCommandReceived(Int32 id, EventFlags flags) : - base(id, flags) { - } - public EventSkipCommandReceived() : - base(17, EventFlags.Server|EventFlags.Client) { - } - public override Int32 GetHashCode() { - unchecked { - var hash = 109; - hash = hash * 31 + Player.GetHashCode(); - hash = hash * 31 + Data.GetHashCode(); - return hash; - } - } - } - public static unsafe partial class BitStreamExtensions { - public static void Serialize(this IBitStream stream, ref AssetRefBallPoolSpec value) { - stream.Serialize(ref value.Id.Value); - } - public static void Serialize(this IBitStream stream, ref AssetRefConfigAssets value) { - stream.Serialize(ref value.Id.Value); - } - public static void Serialize(this IBitStream stream, ref AssetRefGameConfig value) { - stream.Serialize(ref value.Id.Value); - } - public static void Serialize(this IBitStream stream, ref AssetRefTurnConfig value) { - stream.Serialize(ref value.Id.Value); - } - public static void Serialize(this IBitStream stream, ref AssetRefUserMap value) { - stream.Serialize(ref value.Id.Value); - } - } - [System.SerializableAttribute()] - public unsafe partial class BallPoolSpec : AssetObject { - } - [System.SerializableAttribute()] - public unsafe partial class ConfigAssets : AssetObject { - } - [System.SerializableAttribute()] - public unsafe partial class GameConfig : AssetObject { - } - [System.SerializableAttribute()] - public unsafe partial class UserMap : AssetObject { - } - [System.SerializableAttribute()] - public unsafe partial class TurnConfig : AssetObject { - } - public unsafe partial class ComponentPrototypeVisitor : Prototypes.ComponentPrototypeVisitorBase { - public virtual void Visit(Prototypes.BallFields_Prototype prototype) { - VisitFallback(prototype); - } - } - public static unsafe partial class Constants { - public const Int32 MAX_PLAYERS = 2; - } - public static unsafe partial class StaticDelegates { - public static FrameSerializer.Delegate SerializeBallPoolPlayer; - public static FrameSerializer.Delegate SerializeInput; - static partial void InitGen() { - SerializeBallPoolPlayer = Quantum.BallPoolPlayer.Serialize; - SerializeInput = Quantum.Input.Serialize; - } - } - public unsafe partial class TypeRegistry { - partial void AddGenerated() { - Register(typeof(AssetGuid), AssetGuid.SIZE); - Register(typeof(Quantum.AssetRefBallPoolSpec), Quantum.AssetRefBallPoolSpec.SIZE); - Register(typeof(AssetRefCharacterController2DConfig), AssetRefCharacterController2DConfig.SIZE); - Register(typeof(AssetRefCharacterController3DConfig), AssetRefCharacterController3DConfig.SIZE); - Register(typeof(Quantum.AssetRefConfigAssets), Quantum.AssetRefConfigAssets.SIZE); - Register(typeof(AssetRefEntityPrototype), AssetRefEntityPrototype.SIZE); - Register(typeof(AssetRefEntityView), AssetRefEntityView.SIZE); - Register(typeof(Quantum.AssetRefGameConfig), Quantum.AssetRefGameConfig.SIZE); - Register(typeof(AssetRefMap), AssetRefMap.SIZE); - Register(typeof(AssetRefNavMesh), AssetRefNavMesh.SIZE); - Register(typeof(AssetRefNavMeshAgentConfig), AssetRefNavMeshAgentConfig.SIZE); - Register(typeof(AssetRefPhysicsMaterial), AssetRefPhysicsMaterial.SIZE); - Register(typeof(AssetRefPolygonCollider), AssetRefPolygonCollider.SIZE); - Register(typeof(AssetRefTerrainCollider), AssetRefTerrainCollider.SIZE); - Register(typeof(Quantum.AssetRefTurnConfig), Quantum.AssetRefTurnConfig.SIZE); - Register(typeof(Quantum.AssetRefUserMap), Quantum.AssetRefUserMap.SIZE); - Register(typeof(Quantum.BallFields), Quantum.BallFields.SIZE); - Register(typeof(Quantum.BallPoolPlayer), Quantum.BallPoolPlayer.SIZE); - Register(typeof(Quantum.BitSet1024), Quantum.BitSet1024.SIZE); - Register(typeof(Quantum.BitSet128), Quantum.BitSet128.SIZE); - Register(typeof(Quantum.BitSet2), Quantum.BitSet2.SIZE); - Register(typeof(Quantum.BitSet2048), Quantum.BitSet2048.SIZE); - Register(typeof(Quantum.BitSet256), Quantum.BitSet256.SIZE); - Register(typeof(Quantum.BitSet4096), Quantum.BitSet4096.SIZE); - Register(typeof(Quantum.BitSet512), Quantum.BitSet512.SIZE); - Register(typeof(Button), Button.SIZE); - Register(typeof(CharacterController2D), CharacterController2D.SIZE); - Register(typeof(CharacterController3D), CharacterController3D.SIZE); - Register(typeof(ColorRGBA), ColorRGBA.SIZE); - Register(typeof(ComponentPrototypeRef), ComponentPrototypeRef.SIZE); - Register(typeof(DistanceJoint), DistanceJoint.SIZE); - Register(typeof(DistanceJoint3D), DistanceJoint3D.SIZE); - Register(typeof(EntityPrototypeRef), EntityPrototypeRef.SIZE); - Register(typeof(EntityRef), EntityRef.SIZE); - Register(typeof(FP), FP.SIZE); - Register(typeof(FPBounds2), FPBounds2.SIZE); - Register(typeof(FPBounds3), FPBounds3.SIZE); - Register(typeof(FPMatrix2x2), FPMatrix2x2.SIZE); - Register(typeof(FPMatrix3x3), FPMatrix3x3.SIZE); - Register(typeof(FPMatrix4x4), FPMatrix4x4.SIZE); - Register(typeof(FPQuaternion), FPQuaternion.SIZE); - Register(typeof(FPVector2), FPVector2.SIZE); - Register(typeof(FPVector3), FPVector3.SIZE); - Register(typeof(FrameMetaData), FrameMetaData.SIZE); - Register(typeof(HingeJoint), HingeJoint.SIZE); - Register(typeof(HingeJoint3D), HingeJoint3D.SIZE); - Register(typeof(Hit), Hit.SIZE); - Register(typeof(Hit3D), Hit3D.SIZE); - Register(typeof(Quantum.Input), Quantum.Input.SIZE); - Register(typeof(Quantum.InputButtons), 4); - Register(typeof(Joint), Joint.SIZE); - Register(typeof(Joint3D), Joint3D.SIZE); - Register(typeof(LayerMask), LayerMask.SIZE); - Register(typeof(MapEntityId), MapEntityId.SIZE); - Register(typeof(MapEntityLink), MapEntityLink.SIZE); - Register(typeof(NavMeshAvoidanceAgent), NavMeshAvoidanceAgent.SIZE); - Register(typeof(NavMeshAvoidanceObstacle), NavMeshAvoidanceObstacle.SIZE); - Register(typeof(NavMeshPathfinder), NavMeshPathfinder.SIZE); - Register(typeof(NavMeshRegionMask), NavMeshRegionMask.SIZE); - Register(typeof(NavMeshSteeringAgent), NavMeshSteeringAgent.SIZE); - Register(typeof(NullableFP), NullableFP.SIZE); - Register(typeof(NullableFPVector2), NullableFPVector2.SIZE); - Register(typeof(NullableFPVector3), NullableFPVector3.SIZE); - Register(typeof(NullableNonNegativeFP), NullableNonNegativeFP.SIZE); - Register(typeof(PhysicsBody2D), PhysicsBody2D.SIZE); - Register(typeof(PhysicsBody3D), PhysicsBody3D.SIZE); - Register(typeof(PhysicsCollider2D), PhysicsCollider2D.SIZE); - Register(typeof(PhysicsCollider3D), PhysicsCollider3D.SIZE); - Register(typeof(PhysicsSceneSettings), PhysicsSceneSettings.SIZE); - Register(typeof(PlayerRef), PlayerRef.SIZE); - Register(typeof(Ptr), Ptr.SIZE); - Register(typeof(QBoolean), QBoolean.SIZE); - Register(typeof(Quantum.Ptr), Quantum.Ptr.SIZE); - Register(typeof(RNGSession), RNGSession.SIZE); - Register(typeof(Shape2D), Shape2D.SIZE); - Register(typeof(Shape3D), Shape3D.SIZE); - Register(typeof(SpringJoint), SpringJoint.SIZE); - Register(typeof(SpringJoint3D), SpringJoint3D.SIZE); - Register(typeof(Transform2D), Transform2D.SIZE); - Register(typeof(Transform2DVertical), Transform2DVertical.SIZE); - Register(typeof(Transform3D), Transform3D.SIZE); - Register(typeof(Quantum.TurnData), Quantum.TurnData.SIZE); - Register(typeof(Quantum.TurnEndReason), 4); - Register(typeof(Quantum.TurnStatus), 4); - Register(typeof(Quantum.TurnType), 4); - Register(typeof(View), View.SIZE); - Register(typeof(Quantum._globals_), Quantum._globals_.SIZE); - } - } - public unsafe partial class FramePrinterGen { - public static void EnsureNotStripped() { - FramePrinter.EnsurePrimitiveNotStripped(); - FramePrinter.EnsurePrimitiveNotStripped(); - FramePrinter.EnsurePrimitiveNotStripped(); - FramePrinter.EnsurePrimitiveNotStripped(); - FramePrinter.EnsurePrimitiveNotStripped(); - FramePrinter.EnsurePrimitiveNotStripped(); - FramePrinter.EnsurePrimitiveNotStripped(); - FramePrinter.EnsurePrimitiveNotStripped(); - FramePrinter.EnsurePrimitiveNotStripped(); - } - } -} -namespace Quantum.Prototypes { - using System; - using System.Collections.Generic; - using System.Runtime.InteropServices; - using Photon.Deterministic; - using Quantum.Core; - using Quantum.Collections; - using Quantum.Inspector; - using Quantum.Physics2D; - using Quantum.Physics3D; - using Optional = Quantum.Inspector.OptionalAttribute; - using MethodImplAttribute = System.Runtime.CompilerServices.MethodImplAttribute; - using MethodImplOptions = System.Runtime.CompilerServices.MethodImplOptions; - - [System.SerializableAttribute()] - [Prototype(typeof(TurnEndReason))] - public unsafe partial struct TurnEndReason_Prototype { - public Int32 Value; - public static implicit operator TurnEndReason(TurnEndReason_Prototype value) { - return (TurnEndReason)value.Value; - } - public static implicit operator TurnEndReason_Prototype(TurnEndReason value) { - return new TurnEndReason_Prototype() { Value = (Int32)value }; - } - } - [System.SerializableAttribute()] - [Prototype(typeof(TurnStatus))] - public unsafe partial struct TurnStatus_Prototype { - public Int32 Value; - public static implicit operator TurnStatus(TurnStatus_Prototype value) { - return (TurnStatus)value.Value; - } - public static implicit operator TurnStatus_Prototype(TurnStatus value) { - return new TurnStatus_Prototype() { Value = (Int32)value }; - } - } - [System.SerializableAttribute()] - [Prototype(typeof(TurnType))] - public unsafe partial struct TurnType_Prototype { - public Int32 Value; - public static implicit operator TurnType(TurnType_Prototype value) { - return (TurnType)value.Value; - } - public static implicit operator TurnType_Prototype(TurnType value) { - return new TurnType_Prototype() { Value = (Int32)value }; - } - } - [System.SerializableAttribute()] - [Prototype(typeof(InputButtons))] - public unsafe partial struct InputButtons_Prototype { - public Int32 Value; - public static implicit operator InputButtons(InputButtons_Prototype value) { - return (InputButtons)value.Value; - } - public static implicit operator InputButtons_Prototype(InputButtons value) { - return new InputButtons_Prototype() { Value = (Int32)value }; - } - } - [System.SerializableAttribute()] - [Prototype(typeof(BallFields))] - public sealed unsafe partial class BallFields_Prototype : ComponentPrototype { - public FPVector2 Spin; - public Int32 Number; - public QBoolean Striped; - public AssetRefBallPoolSpec Spec; - public QBoolean InTable; - partial void MaterializeUser(Frame frame, ref BallFields result, in PrototypeMaterializationContext context); - public override Boolean AddToEntity(FrameBase f, EntityRef entity, in PrototypeMaterializationContext context) { - BallFields component = default; - Materialize((Frame)f, ref component, in context); - return f.Set(entity, component) == SetResult.ComponentAdded; - } - public void Materialize(Frame frame, ref BallFields result, in PrototypeMaterializationContext context) { - result.InTable = this.InTable; - result.Number = this.Number; - result.Spec = this.Spec; - result.Spin = this.Spin; - result.Striped = this.Striped; - MaterializeUser(frame, ref result, in context); - } - public override void Dispatch(ComponentPrototypeVisitorBase visitor) { - ((ComponentPrototypeVisitor)visitor).Visit(this); - } - } - [System.SerializableAttribute()] - [Prototype(typeof(BallPoolPlayer))] - public sealed unsafe partial class BallPoolPlayer_Prototype : StructPrototype { - public PlayerRef Ref; - public TurnData_Prototype TurnStats; - public QBoolean StripedBalls; - partial void MaterializeUser(Frame frame, ref BallPoolPlayer result, in PrototypeMaterializationContext context); - public void Materialize(Frame frame, ref BallPoolPlayer result, in PrototypeMaterializationContext context) { - result.Ref = this.Ref; - result.StripedBalls = this.StripedBalls; - this.TurnStats.Materialize(frame, ref result.TurnStats, in context); - MaterializeUser(frame, ref result, in context); - } - } - [System.SerializableAttribute()] - [Prototype(typeof(Input))] - public sealed unsafe partial class Input_Prototype : StructPrototype { - public FPVector3 Direction; - public FP ForceBarMarkPos; - public FPVector2 BallPosition; - partial void MaterializeUser(Frame frame, ref Input result, in PrototypeMaterializationContext context); - public void Materialize(Frame frame, ref Input result, in PrototypeMaterializationContext context) { - result.BallPosition = this.BallPosition; - result.Direction = this.Direction; - result.ForceBarMarkPos = this.ForceBarMarkPos; - MaterializeUser(frame, ref result, in context); - } - } - [System.SerializableAttribute()] - [Prototype(typeof(TurnData))] - public sealed unsafe partial class TurnData_Prototype : StructPrototype { - public PlayerRef Player; - public MapEntityId Entity; - public AssetRefTurnConfig ConfigRef; - public TurnType_Prototype Type; - public TurnStatus_Prototype Status; - public Int32 Number; - public Int32 Ticks; - partial void MaterializeUser(Frame frame, ref TurnData result, in PrototypeMaterializationContext context); - public void Materialize(Frame frame, ref TurnData result, in PrototypeMaterializationContext context) { - result.ConfigRef = this.ConfigRef; - PrototypeValidator.FindMapEntity(this.Entity, in context, out result.Entity); - result.Number = this.Number; - result.Player = this.Player; - result.Status = this.Status; - result.Ticks = this.Ticks; - result.Type = this.Type; - MaterializeUser(frame, ref result, in context); - } - } - public unsafe partial class FlatEntityPrototypeContainer { - [ArrayLength(0, 1)] - public List BallFields; - partial void CollectGen(List target) { - Collect(BallFields, target); - } - public unsafe partial class StoreVisitor { - public override void Visit(Prototypes.BallFields_Prototype prototype) { - Storage.Store(prototype, ref Storage.BallFields); - } - } - } -} -#pragma warning restore 0649 -#pragma warning restore 1522 -#pragma warning restore 0414 -#pragma warning restore 0219 -#pragma warning restore 0109 diff --git a/data/CommandSetup.Legacy.cs b/data/CommandSetup.Legacy.cs deleted file mode 100644 index f9ecc886d6943496693bfe20f2a0184b92a2fd52..0000000000000000000000000000000000000000 --- a/data/CommandSetup.Legacy.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Photon.Deterministic; - -namespace Quantum -{ - public static class CommandSetup - { - public static DeterministicCommand[] CreateCommands(RuntimeConfig gameConfig, SimulationConfig simulationConfig) - { - return null; - } - } -} diff --git a/data/CommandSetup.User.cs b/data/CommandSetup.User.cs deleted file mode 100644 index ba9a4648ac481500f7ee551960be9400a86564e1..0000000000000000000000000000000000000000 --- a/data/CommandSetup.User.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using Photon.Deterministic; - -namespace Quantum -{ - public static partial class DeterministicCommandSetup - { - static partial void AddCommandFactoriesUser(ICollection factories, RuntimeConfig gameConfig, SimulationConfig simulationConfig) - { - factories.Add(new GameMaster_SetCharacterPosition()); - factories.Add(new GameMaster_SpawnPrototype()); - } - } -} diff --git a/data/CommandSetup.cs b/data/CommandSetup.cs deleted file mode 100644 index faddd53b97083c836d4b50840025d1c9bcf57fdb..0000000000000000000000000000000000000000 --- a/data/CommandSetup.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Photon.Deterministic; -using System; -using System.Collections.Generic; - -namespace Quantum -{ - public static class CommandSetup - { - public static DeterministicCommand[] CreateCommands(RuntimeConfig gameConfig, SimulationConfig simulationConfig) - { - Type baseType = typeof(DeterministicCommand); - Type[] allTypes = typeof(CommandSetup).Assembly.GetTypes(); - - List commands = new List(16); - - foreach (Type type in allTypes) - { - if (type.IsSubclassOf(baseType) == true && type.IsAbstract == false) - { - DeterministicCommand command = Activator.CreateInstance(type) as DeterministicCommand; - if (command != null) - { - commands.Add(command); - } - } - } - - return commands.ToArray(); - } - } -} diff --git a/data/CommandSystem.cs b/data/CommandSystem.cs deleted file mode 100644 index 6153f8101a2d51eddba049a71752165a4104cdbb..0000000000000000000000000000000000000000 --- a/data/CommandSystem.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; - -namespace Quantum -{ - public unsafe class CommandSystem : SystemMainThread - { - public override void Update(Frame f) - { - var currentTurn = f.Global->CurrentTurn; - if (currentTurn.Status != TurnStatus.Active) - { - return; - } - - - var currentPlayer = f.Global->CurrentTurn.Player; - - switch (f.GetPlayerCommand(currentPlayer)) - { - case PlayCommand playCommand: - if (currentTurn.Type != TurnType.Play) - { - return; - } - f.Signals.OnPlayCommandReceived(currentPlayer, playCommand.Data); - f.Events.PlayCommandReceived(currentPlayer, playCommand.Data); - break; - - case SkipCommand skipCommand: - var config = f.FindAsset(currentTurn.ConfigRef.Id); - if (!config.IsSkippable) - { - return; - } - f.Signals.OnSkipCommandReceived(currentPlayer, skipCommand.Data); - f.Events.SkipCommandReceived(currentPlayer, skipCommand.Data); - break; - } - } - } -} \ No newline at end of file diff --git a/data/ConfigAssets.cs b/data/ConfigAssets.cs deleted file mode 100644 index 0242686dc5eb85f7b3c3b65a8df3464e99fe58af..0000000000000000000000000000000000000000 --- a/data/ConfigAssets.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Quantum { - public partial class ConfigAssets { - public AssetRefGameConfig GameConfig; - public AssetRefTurnConfig StartCountdownConfig; - public AssetRefTurnConfig PlayTurnConfig; - public AssetRefTurnConfig CountdownTurnConfig; - } -} diff --git a/data/ConfigAssetsHelper.cs b/data/ConfigAssetsHelper.cs deleted file mode 100644 index 3a1178e27cd22bd8633eeee60ec511878f310956..0000000000000000000000000000000000000000 --- a/data/ConfigAssetsHelper.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Quantum -{ - unsafe public class ConfigAssetsHelper - { - public static TurnConfig GetTurnConfig(Frame f, TurnType type) - { - var configAssets = f.FindAsset(f.RuntimeConfig.ConfigAssets.Id); - TurnConfig config = null; - switch (type) - { - case TurnType.Countdown: - config = f.FindAsset(configAssets.CountdownTurnConfig.Id); - break; - case TurnType.Play: - config = f.FindAsset(configAssets.PlayTurnConfig.Id); - break; - default: - break; - } - return config; - } - - public static GameConfig GetGameConfig(Frame f) { - var configAssets = f.FindAsset(f.RuntimeConfig.ConfigAssets.Id); - return f.FindAsset(configAssets.GameConfig.Id); - } - } -} diff --git a/data/Configuration Files.txt b/data/Configuration Files.txt deleted file mode 100644 index d57c5522ccb072fb8177502a27b9bcf0fe3eee17..0000000000000000000000000000000000000000 --- a/data/Configuration Files.txt +++ /dev/null @@ -1,191 +0,0 @@ -Configuration Files -Introduction -Quantum Start Sequence -Config Files -PhotonServerSettings -DeterministicConfig -SimulationConfig -Delta Time Type -RuntimeConfig -RuntimePlayer -Using DSL Generated Code With RuntimePlayer And RuntimeConfig Serialization - -Introduction -There are a few Quantum config files that have specific roles and purposes. - -These config files are placed in different folders in the Unity project. Finding them quickly is made easy with the shortcuts (unity) editor window found in "Menu/Quantum/Show Shortcuts". - -Most of default config instances reside as Scriptable Objects inside the "Resources" folder at the root level of the Unity project Assets, and will end up in your app build from there (see DeterministicSessionConfigAsset.Instance for example) while others (RuntimeConfig, RuntimePlayer) can be assembled during run-time. - -Back To Top - - -Quantum Start Sequence -Which config is used by whom and send when is shown in the diagram below. - -Config Sequence Diagram -Config Sequence Diagram -Back To Top - - -Config Files - -PhotonServerSettings -Assets/Resources/PhotonServerSettings.asset -Quantum, from version 2.0, uses Photon Realtime to connect and communicate to the Photon Cloud. This config describes where the client connects to (cloud + region, local ip, ..). - -photon realtime introduction - -Also a valid AppId (referring to an active Quantum plugin) is set here. - -Only one instance of this config file is allowed. The loading is tightly integrated into the PhotonNetwork class. See PhotonNetwork.PhotonServerSettings. - -Photon Server Settings -Photon Server Settings -Back To Top - - -DeterministicConfig -Assets/Resouces/DeterministicConfig.asset -Via the DeterministicConfig developers can parametrize internals of the deterministic simulation and plugin (the Quantum server component). Toggle Show Help Info in the inspector of this config for details of each parameter. - -The default way only allows one instance of this asset but as long as it is passed into QuantumRunner.StartParameters it does not matter how the file is retrieved. - -This config file will be synchronized between all clients of one session. Although each player starts their own simulation locally with their own version of the DeterministicConfig, the server will distribute the config file instance of the first player who joined the plugin. - -The data on this config is included in the checksum generation. - -Deterministic Config -Deterministic Config -Back To Top - - -SimulationConfig -Assets/Resources/DB/Configs/SimulationConfig.asset -This config file holds parameters used in the ECS layer and inside core systems like physics and navigation. See the related system sections in the manual for more details of each value. - -The SimulationConfig is part of the Quantum DB and multiple instances of this config are supported. Add the config asset GUID to the RuntimeConfig to select which SimualtionConfig should be used. - -Developers can "extend" (create a partial class) the quantum_code/quantum.state/Core/SimulationConfig.cs class and add more data to it. - -Simulation Config -Simulation Config -Back To Top - - -Delta Time Type -You can customize how the QuantumRunner will accumulate elapsed time to update the Quantum simulation (see the QuantumRunner.DeltaTime property). - -The Default setting will use an internal stopwatch and is the recommended setting for production. -EngineDeltaTime will use, for example Unity.deltaTime, to track when to trigger simulation updates. This is very handy when debugging the project using break points, because upon resuming the simulation with not fast-forward but continue from the exact time the simulation was paused. Alas, this setting can cause issues with time synchronization when initializing online matches: the time tracking can be inaccurate under load (e.g. level loading) and result in a lot of large extra time syncs request and cancelled inputs for a client when starting an online game. -Back To Top - - -RuntimeConfig -In contrast to the SimulationConfig, which has only static configuration data, the RuntimeConfig holds information that can be different from game to game. By default is defines for example what map to load and the random start seed. It is assembled from scratch each time starting a game. - -Developers can add custom data to quantum_code/quantum.state/RuntimeConfig.User.cs (don't forget to fill out the serialization methods). - -Like the DeterministicConfig this "file" is distributed to every other client after the first player connected and joined the Quantum plugin. - -A convenient way of using this config is by creating a MonoBehaviour that stores an instance of RuntimeConfig (and RuntimePlayer) with default values and asset links (GUIDs) for example pointing to other asset files containing specific balancing data. When the player is inside a game lobby parts of the Runtime configs can be overwritten with his custom load-out before connecting and starting the game. See QuantumRunnerLocalDebug.cs or the sample below: - -Runtime Setup -Runtime Setup -using Quantum; -using UnityEngine; - -public sealed class RuntimeSetup : MonoBehaviour -{ - public static RuntimeSetup Instance { get; private set; } - - public RuntimeConfig GameConfig { get { return _gameConfig; } } - public RuntimePlayer PlayerConfig { get { return _playerConfig; } } - - [SerializeField] private RuntimeConfig _gameConfig; - [SerializeField] private RuntimePlayer _playerConfig; - - private void Awake() { - Instance = this; - } -} -Back To Top - - -RuntimePlayer -Similar to the RuntimeConfig the RuntimePlayer describes dynamic properties for one player (quantum_code/quantum.state/RuntimePlayer.User.cs). - -The data for a player behaves differently to the other configs, because it is send by each player individually after the actual game has been started. See the Player document in the manual for more information. - -Back To Top - - -Using DSL Generated Code With RuntimePlayer And RuntimeConfig Serialization -RuntimeConfig and RuntimePlayer require to write manual serialization code. When using DSL generated structs of component prototypes the serialization code can be simplified. - -Caveat: Never use objects that are actually pointers that require a frame to be resolved (e.g. Quantum collections). - -The following struct Foo43 and components prototype Component43 will be used in the RuntimePlayer. - -struct Foo43 { - int Integer; - array[8] Bytes; - asset_ref MapAssetReference; - Bar43 Bar43; -} - -struct Bar43 { - FPVector3 Vector3; -} - -component Component43 { - int Integer; - OtherComponent43 OtherComponent; -} - -component OtherComponent43 { - int Integer; - FP FP; -} -The partial RuntimePlayer.User implementation looks like this. - -partial class RuntimePlayer { - // A) Use a DSL generated struct on RuntimePlayer - public Foo43 Foo; - - // B) Piggyback on a component prototype to set data - public Component43_Prototype Component43 = new Component43_Prototype { OtherComponent = new OtherComponent43_Prototype() }; - - partial void SerializeUserData(BitStream stream) { - // A) Because the struct is memory alined we can pin the memory and serialize it as a byte array which will work platform indenpentently. - unsafe { - fixed (Foo43* p = &Foo) { - stream.SerializeBuffer((byte*)p, sizeof(Foo43)); - } - } - - // B) Initialized the references in the field declaration with new and serialize all fields here. - stream.Serialize(ref Component43.Integer); - stream.Serialize(ref Component43.OtherComponent.Integer); - stream.Serialize(ref Component43.OtherComponent.FP); - } -} -Send the RuntimePlayer from the client: - -var runtimePlayer = new Quantum.RuntimePlayer { - Component43 = new Quantum.Prototypes.Component43_Prototype { - Integer = 1, - OtherComponent = new Quantum.Prototypes.OtherComponent43_Prototype { FP = 2, Integer = 3 } }, - Foo = new Foo43 { - Bar43 = new Bar43 { Vector3 = FPVector3.One }, - Integer = 4, - MapAssetReference = new AssetRefMap() { Id = 66 } - } -}; - -unsafe { - runtimePlayer.Foo.Bytes[0] = 7; - runtimePlayer.Foo.Bytes[1] = 6; -} - -game.SendPlayerData(lp, runtimePlayer); \ No newline at end of file diff --git a/data/Consideration.cs b/data/Consideration.cs deleted file mode 100644 index fe9b7508ccaa1019a9fdb39b8964c2fdb5ffdbef..0000000000000000000000000000000000000000 --- a/data/Consideration.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Photon.Deterministic; -using System; -using Quantum.Prototypes; - -namespace Quantum -{ - [Serializable] - public struct ResponseCurvePack - { - public FP MultiplyFactor; - public AssetRefAIFunctionFP ResponseCurveRef; - [NonSerialized] public ResponseCurve ResponseCurve; - } - - public unsafe partial class Consideration - { - public string Label; - - public AssetRefAIFunctionInt RankRef; - public AssetRefAIFunctionBool CommitmentRef; - public AssetRefConsideration[] NextConsiderationsRefs; - public AssetRefAIAction[] OnEnterActionsRefs; - public AssetRefAIAction[] OnUpdateActionsRefs; - public AssetRefAIAction[] OnExitActionsRefs; - - [NonSerialized] public AIFunctionInt Rank; - [NonSerialized] public AIFunctionBool Commitment; - [NonSerialized] public Consideration[] NextConsiderations; - [NonSerialized] public AIAction[] OnEnterActions; - [NonSerialized] public AIAction[] OnUpdateActions; - [NonSerialized] public AIAction[] OnExitActions; - - public ResponseCurvePack[] ResponseCurvePacks; - - public FP BaseScore; - - public UTMomentumData MomentumData; - public FP Cooldown; - - public byte Depth; - - public int GetRank(Frame frame, EntityRef entity = default) - { - if (Rank == null) - return 0; - - return Rank.Execute(frame, entity); - } - - public FP Score(Frame frame, EntityRef entity = default) - { - if (ResponseCurvePacks.Length == 0) - return 0; - - FP score = 1; - for (int i = 0; i < ResponseCurvePacks.Length; i++) - { - score *= ResponseCurvePacks[i].ResponseCurve.Execute(frame, entity) * ResponseCurvePacks[i].MultiplyFactor; - - // If we find a negative veto, the final score would be zero anyways, so we stop here - if (score == 0) - { - break; - } - } - - score += BaseScore; - - FP modificationFactor = 1 - (1 / ResponseCurvePacks.Length); - FP makeUpValue = (1 - score) * modificationFactor; - FP finalScore = score + (makeUpValue * score); - - return finalScore; - } - - public void OnEnter(Frame frame, UtilityReasoner* reasoner, EntityRef entity = default) - { - for (int i = 0; i < OnEnterActions.Length; i++) - { - OnEnterActions[i].Update(frame, entity); - } - } - - public void OnExit(Frame frame, UtilityReasoner* reasoner, EntityRef entity = default) - { - for (int i = 0; i < OnExitActions.Length; i++) - { - OnExitActions[i].Update(frame, entity); - } - } - - public void OnUpdate(Frame frame, UtilityReasoner* reasoner, EntityRef entity = default) - { - for (int i = 0; i < OnUpdateActions.Length; i++) - { - OnUpdateActions[i].Update(frame, entity); - } - - if (NextConsiderationsRefs != null && NextConsiderationsRefs.Length > 0) - { - Consideration chosenConsideration = reasoner->SelectBestConsideration(frame, NextConsiderations, (byte)(Depth + 1), reasoner, entity); - if (chosenConsideration != default) - { - chosenConsideration.OnUpdate(frame, reasoner, entity); - UTManager.ConsiderationChosen?.Invoke(entity, chosenConsideration.Identifier.Guid.Value); - } - } - } - - public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator) - { - base.Loaded(resourceManager, allocator); - - Rank = (AIFunctionInt)resourceManager.GetAsset(RankRef.Id); - - if (ResponseCurvePacks != null) - { - for (Int32 i = 0; i < ResponseCurvePacks.Length; i++) - { - ResponseCurvePacks[i].ResponseCurve = (ResponseCurve)resourceManager.GetAsset(ResponseCurvePacks[i].ResponseCurveRef.Id); - ResponseCurvePacks[i].MultiplyFactor = 1; - } - } - - OnEnterActions = new AIAction[OnEnterActionsRefs == null ? 0 : OnEnterActionsRefs.Length]; - if (OnEnterActionsRefs != null) - { - for (Int32 i = 0; i < OnEnterActionsRefs.Length; i++) - { - OnEnterActions[i] = (AIAction)resourceManager.GetAsset(OnEnterActionsRefs[i].Id); - } - } - - OnUpdateActions = new AIAction[OnUpdateActionsRefs == null ? 0 : OnUpdateActionsRefs.Length]; - if (OnEnterActionsRefs != null) - { - for (Int32 i = 0; i < OnUpdateActionsRefs.Length; i++) - { - OnUpdateActions[i] = (AIAction)resourceManager.GetAsset(OnUpdateActionsRefs[i].Id); - } - } - - OnExitActions = new AIAction[OnExitActionsRefs == null ? 0 : OnExitActionsRefs.Length]; - if (OnEnterActionsRefs != null) - { - for (Int32 i = 0; i < OnExitActionsRefs.Length; i++) - { - OnExitActions[i] = (AIAction)resourceManager.GetAsset(OnExitActionsRefs[i].Id); - } - } - - Commitment = (AIFunctionBool)resourceManager.GetAsset(CommitmentRef.Id); - - NextConsiderations = new Consideration[NextConsiderationsRefs == null ? 0 : NextConsiderationsRefs.Length]; - if (NextConsiderationsRefs != null) - { - for (Int32 i = 0; i < NextConsiderationsRefs.Length; i++) - { - NextConsiderations[i] = (Consideration)resourceManager.GetAsset(NextConsiderationsRefs[i].Id); - } - } - } - } -} diff --git a/data/Core.cs b/data/Core.cs deleted file mode 100644 index d577cbd0fdb8910ec99626adab8156dc096313da..0000000000000000000000000000000000000000 --- a/data/Core.cs +++ /dev/null @@ -1,6033 +0,0 @@ -using System.Collections.Generic; -using Photon.Deterministic; -using System; -using Quantum.Core; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; -using System.Runtime.InteropServices; -using Quantum.Inspector; -using System.Collections.ObjectModel; -using System.Threading.Tasks; -using Quantum; -using Quantum.Profiling; -using Quantum.Allocator; -using System.Collections.Specialized; -using System.Threading; -using Quantum.Prototypes; -using System.Reflection; -using Quantum.Task; -using System.Runtime.CompilerServices; - -// Core/CommandSetup.cs - -namespace Quantum { - public static partial class DeterministicCommandSetup { - public static IDeterministicCommandFactory[] GetCommandFactories(RuntimeConfig gameConfig, SimulationConfig simulationConfig) { - var factories = new List() { - // pre-defined core commands - Core.DebugCommand.CreateCommand(), - new DeterministicCommandPool(), - }; - - AddCommandFactoriesUser(factories, gameConfig, simulationConfig); - -#pragma warning disable 618 // Use of obsolete members - var obsoleteCommandsCreated = CommandSetup.CreateCommands(gameConfig, simulationConfig); - if (obsoleteCommandsCreated != null && obsoleteCommandsCreated.Length > 0) { - Log.Warn("'CommandSetup.CreateCommands' is now deprecated. " + - "Implement a partial declaration of '" + nameof(DeterministicCommandSetup) + "." + nameof(AddCommandFactoriesUser) + "' instead, as shown on 'CommandSetup.User.cs' available on the SDK package." + - "Command instances can be used as factories of their own type."); - factories.AddRange(obsoleteCommandsCreated); - } -#pragma warning restore 618 - - return factories.ToArray(); - } - - static partial void AddCommandFactoriesUser(ICollection factories, RuntimeConfig gameConfig, SimulationConfig simulationConfig); - } -} - -// Core/Collision.cs - -namespace Quantum { - - /// - /// Interface for receiving callbacks once per frame while two non-trigger 2D colliders are touching. - /// At least one of the entities involved in a collision must have the respective set for the callback to be called. - /// See for setting the callbacks flags to an entity. - /// - /// \ingroup Physics2dApi - public interface ISignalOnCollision2D : ISignal { - /// - /// Called once per frame while two non-trigger 2D colliders are touching. - /// - /// The frame in which the collision happened. - /// The with data about the collision. - /// \ingroup Physics2dApi - void OnCollision2D(Frame f, CollisionInfo2D info); - } - - /// - /// Interface for receiving callbacks once two non-trigger 2D colliders start touching. - /// At least one of the entities involved in a collision must have the respective set for the callback to be called. - /// See for setting the callbacks flags to an entity. - /// - /// \ingroup Physics2dApi - public interface ISignalOnCollisionEnter2D : ISignal { - /// - /// Called once two non-trigger 2D colliders start touching. - /// - /// The frame in which the collision happened. - /// The with data about the collision. - /// \ingroup Physics2dApi - void OnCollisionEnter2D(Frame f, CollisionInfo2D info); - } - - /// - /// Interface for receiving callbacks once two non-trigger 2D colliders stop touching. - /// At least one of the entities involved in a collision must have the respective set for the callback to be called. - /// See for setting the callbacks flags to an entity. - /// - /// \ingroup Physics2dApi - public interface ISignalOnCollisionExit2D : ISignal { - /// - /// Called once two non-trigger 2D colliders stop touching. - /// - /// The frame in which the entities stopped touching. - /// The with the entities that were touching. - /// \ingroup Physics2dApi - void OnCollisionExit2D(Frame f, ExitInfo2D info); - } - - /// - /// Interface for receiving callbacks once per frame while a non-trigger and a trigger 2D colliders are touching. - /// No collision is checked between two kinematic colliders that are both trigger or both non-trigger. - /// At least one of the entities involved in a collision must have the respective set for the callback to be called. - /// See for setting the callbacks flags to an entity. - /// - /// \ingroup Physics2dApi - public interface ISignalOnTrigger2D : ISignal { - /// - /// Called once per frame while a non-trigger and a trigger 2D colliders are touching. - /// - /// The frame in which the collision happened. - /// The with data about the trigger collision. - /// \ingroup Physics2dApi - void OnTrigger2D(Frame f, TriggerInfo2D info); - } - - /// - /// Interface for receiving callbacks once a non-trigger and a trigger 2D colliders start touching. - /// No collision is checked between two kinematic colliders that are both trigger or both non-trigger. - /// At least one of the entities involved in a collision must have the respective set for the callback to be called. - /// See for setting the callbacks flags to an entity. - /// - /// \ingroup Physics2dApi - public interface ISignalOnTriggerEnter2D : ISignal { - /// - /// Called once a non-trigger and a trigger 2D colliders start touching. - /// - /// The frame in which the collision happened. - /// The with data about the trigger collision. - /// \ingroup Physics2dApi - void OnTriggerEnter2D(Frame f, TriggerInfo2D info); - } - - /// - /// Interface for receiving callbacks once a non-trigger and a trigger 2D colliders stop touching. - /// No collision is checked between two kinematic colliders that are both trigger or both non-trigger. - /// At least one of the entities involved in a collision must have the respective set for the callback to be called. - /// See for setting the callbacks flags to an entity. - /// - /// \ingroup Physics2dApi - public interface ISignalOnTriggerExit2D : ISignal { - /// - /// Called once a non-trigger and a trigger 2D colliders stop touching. - /// - /// The frame in which the entities stopped touching. - /// The with the entities that were touching. - /// \ingroup Physics2dApi - void OnTriggerExit2D(Frame f, ExitInfo2D info); - } - - /// - /// Interface for receiving callbacks once per frame while two non-trigger 3D colliders are touching. - /// At least one of the entities involved in a collision must have the respective set for the callback to be called. - /// See for setting the callbacks flags to an entity. - /// - /// \ingroup Physics3dApi - public interface ISignalOnCollision3D : ISignal { - /// - /// Called once per frame while two non-trigger 3D colliders are touching. - /// - /// The frame in which the collision happened. - /// The with data about the collision. - /// \ingroup Physics3dApi - void OnCollision3D(Frame f, CollisionInfo3D info); - } - - /// - /// Interface for receiving callbacks once two non-trigger 3D colliders start touching. - /// At least one of the entities involved in a collision must have the respective set for the callback to be called. - /// See for setting the callbacks flags to an entity. - /// - /// \ingroup Physics3dApi - public interface ISignalOnCollisionEnter3D : ISignal { - /// - /// Called once two non-trigger 3D colliders start touching. - /// - /// The frame in which the collision happened. - /// The with data about the collision. - /// \ingroup Physics3dApi - void OnCollisionEnter3D(Frame f, CollisionInfo3D info); - } - - /// - /// Interface for receiving callbacks once two non-trigger 3D colliders stop touching. - /// At least one of the entities involved in a collision must have the respective set for the callback to be called. - /// See for setting the callbacks flags to an entity. - /// - /// \ingroup Physics3dApi - public interface ISignalOnCollisionExit3D : ISignal { - /// - /// Called once two non-trigger 3D colliders stop touching. - /// - /// The frame in which the entities stopped touching. - /// The with the entities that were touching. - /// \ingroup Physics3dApi - void OnCollisionExit3D(Frame f, ExitInfo3D info); - } - - /// - /// Interface for receiving callbacks once per frame while a non-trigger and a trigger 3D colliders are touching. - /// No collision is checked between two kinematic colliders that are both trigger or both non-trigger. - /// At least one of the entities involved in a collision must have the respective set for the callback to be called. - /// See for setting the callbacks flags to an entity. - /// - /// \ingroup Physics3dApi - public interface ISignalOnTrigger3D : ISignal { - /// - /// Called once per frame while a non-trigger and a trigger 3D colliders are touching. - /// - /// The frame in which the collision happened. - /// The with data about the trigger collision. - /// \ingroup Physics3dApi - void OnTrigger3D(Frame f, TriggerInfo3D info); - } - - /// - /// Interface for receiving callbacks once a non-trigger and a trigger 3D colliders start touching. - /// No collision is checked between two kinematic colliders that are both trigger or both non-trigger. - /// At least one of the entities involved in a collision must have the respective set for the callback to be called. - /// See for setting the callbacks flags to an entity. - /// - /// \ingroup Physics3dApi - public interface ISignalOnTriggerEnter3D : ISignal { - /// - /// Called once a non-trigger and a trigger 3D colliders start touching. - /// - /// The frame in which the collision happened. - /// The with data about the trigger collision. - /// \ingroup Physics3dApi - void OnTriggerEnter3D(Frame f, TriggerInfo3D info); - } - - /// - /// Interface for receiving callbacks once a non-trigger and a trigger 3D colliders stop touching. - /// No collision is checked between two kinematic colliders that are both trigger or both non-trigger. - /// At least one of the entities involved in a collision must have the respective set for the callback to be called. - /// See for setting the callbacks flags to an entity. - /// - /// \ingroup Physics3dApi - public interface ISignalOnTriggerExit3D : ISignal { - /// - /// Called once a non-trigger and a trigger 3D colliders stop touching. - /// - /// The frame in which the entities stopped touching. - /// The with the entities that were touching. - /// \ingroup Physics3dApi - void OnTriggerExit3D(Frame f, ExitInfo3D info); - } -} - -// Core/Frame.cs - -namespace Quantum { - /// - /// The user implementation of that resides in the project quantum_state and has access to all user relevant classes. - /// - /// \ingroup FrameClass - public unsafe partial class Frame : Core.FrameBase { - - public const int DumpFlag_NoSimulationConfig = 1 << 1; - public const int DumpFlag_NoRuntimeConfig = 1 << 3; - public const int DumpFlag_NoDeterministicSessionConfig = 1 << 4; - public const int DumpFlag_NoRuntimePlayers = 1 << 5; - public const int DumpFlag_NoDynamicDB = 1 << 6; - public const int DumpFlag_ReadableDynamicDB = 1 << 7; - public const int DumpFlag_PrintRawValues = 1 << 8; - public const int DumpFlag_ComponentChecksums = 1 << 9; - public const int DumpFlag_AssetDBCheckums = 1 << 10; - public const int DumpFlag_NoIsVerified = 1 << 11; - - [Obsolete("Use DumpFlag_ComponentChecksums")] - public const int DumpFlag_PrintComponentChecksums = DumpFlag_ComponentChecksums; - [Obsolete("Use DumpFlag_ReadableDynamicDB")] - public const int DumpFlag_PrintReadableDynamicDB = DumpFlag_ReadableDynamicDB; - - struct RuntimePlayerData { - public Int32 ActorId; - public Byte[] Data; - public RuntimePlayer Player; - } - - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - _globals_* _globals; - - // configs - RuntimeConfig _runtimeConfig; - SimulationConfig _simulationConfig; - DeterministicSessionConfig _sessionConfig; - - // systems - SystemBase[] _systemsAll; - SystemBase[] _systemsRoots; - Dictionary _systemIndexByType; - - // player data - PersistentMap _playerData; - - ISignalOnPlayerDataSet[] _ISignalOnPlayerDataSet; - - // 2D Physics collision signals - ISignalOnCollision2D[] _ISignalOnCollision2DSystems; - ISignalOnCollisionEnter2D[] _ISignalOnCollisionEnter2DSystems; - ISignalOnCollisionExit2D[] _ISignalOnCollisionExit2DSystems; - - // 2D Physics trigger signals - ISignalOnTrigger2D[] _ISignalOnTrigger2DSystems; - ISignalOnTriggerEnter2D[] _ISignalOnTriggerEnter2DSystems; - ISignalOnTriggerExit2D[] _ISignalOnTriggerExit2DSystems; - - // 3D Physics collision signals - ISignalOnCollision3D[] _ISignalOnCollision3DSystems; - ISignalOnCollisionEnter3D[] _ISignalOnCollisionEnter3DSystems; - ISignalOnCollisionExit3D[] _ISignalOnCollisionExit3DSystems; - - // 3D Physics trigger signals - ISignalOnTrigger3D[] _ISignalOnTrigger3DSystems; - ISignalOnTriggerEnter3D[] _ISignalOnTriggerEnter3DSystems; - ISignalOnTriggerExit3D[] _ISignalOnTriggerExit3DSystems; - - ISignalOnNavMeshWaypointReached[] _ISignalOnNavMeshWaypointReachedSystems; - ISignalOnNavMeshSearchFailed[] _ISignalOnNavMeshSearchFailedSystems; - ISignalOnNavMeshMoveAgent[] _ISignalOnNavMeshMoveAgentSystems; - - ISignalOnMapChanged[] _ISignalOnMapChangedSystems; - ISignalOnEntityPrototypeMaterialized[] _ISignalOnEntityPrototypeMaterializedSystems; - - ISignalOnPlayerConnected[] _ISignalOnPlayerConnectedSystems; - ISignalOnPlayerDisconnected[] _ISignalOnPlayerDisconnectedSystems; - - /// - /// Access the global struct with generated values from the DSL. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - public _globals_* Global { get { return _globals; } } - - /// - /// The randomization session started with the seed from the used to start the simulation with. - /// - /// Supports determinism under roll-backs. - /// If random is used in conjunction with the prediction area feature the session needs to be stored on the entities themselves. - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - public RNGSession* RNG { get { return &_globals->RngSession; } } - - /// - /// Defines the amount of player in this Quantum session. - /// - /// The value is takes from the Deterministic session config. - public Int32 PlayerCount { get { return _sessionConfig.PlayerCount; } } - - public override NavMeshRegionMask* NavMeshRegionMask => &_globals->NavMeshRegions; - - public override FrameMetaData* FrameMetaData => &_globals->FrameMetaData; - - public override CommitCommandsModes CommitCommandsMode => SimulationConfig.Entities.CommitCommandsMode; - - /// - /// Access the signal API.\n - /// Signals are function signatures used as a decoupled inter-system communication API (a bit like a publisher/subscriber API or observer pattern). - /// - /// Custom signals are defined in the DSL. - public FrameSignals Signals; - - /// - /// Access the event API.\n - /// Events are a fine-grained solution to communicate things that happen inside the simulation to the rendering engine (they should never be used to modify/update part of the game state). - /// - /// Custom events are defined in the DSL. - public FrameEvents Events; - - /// - /// Access to the assets API - /// - public FrameAssets Assets; - - /// - /// The frame user context - /// - public new FrameContextUser Context { - get { return (FrameContextUser)base.Context; } - } - - /// - /// The used for this session. - /// - public RuntimeConfig RuntimeConfig { get { return _runtimeConfig; } internal set { _runtimeConfig = value; } } - - /// - /// The used for this session. - /// - public SimulationConfig SimulationConfig { get { return _simulationConfig; } internal set { _simulationConfig = value; } } - - /// - /// The used for this session. - /// - public DeterministicSessionConfig SessionConfig { get { return _sessionConfig; } internal set { _sessionConfig = value; } } - - /// - /// All systems running in the session. - /// - public SystemBase[] SystemsAll { get { return _systemsAll; } } - - /// - /// See . This getter acquires the value from the though. - /// - public override int UpdateRate { get { return _sessionConfig.UpdateFPS; } } - - /// - /// Globally access the physics settings which are taken from the during the Frame constructor. - /// - public sealed override PhysicsSceneSettings* PhysicsSceneSettings { get { return &_globals->PhysicsSettings; } } - - /// - /// Delta time in seconds. Can be set during run-time. - /// - public override FP DeltaTime { - get { return _globals->DeltaTime; } - set { _globals->DeltaTime = value; } - } - - /// - /// Retrieves the Quantum map asset. Can be set during run-time. - /// - /// If assigned value is different than the current one, signal is raised. - public sealed override Map Map { - get { return FindAsset(_globals->Map.Id); } - set { - AssetRefMap newValue = value; - var previousValue = _globals->Map; - if (previousValue.Id != newValue.Id) { - _globals->Map = newValue; - Signals.OnMapChanged(previousValue); - } - } - } - - public Frame(FrameContext context, SystemBase[] systemsAll, SystemBase[] systemsRoots, DeterministicSessionConfig sessionConfig, RuntimeConfig runtimeConfig, SimulationConfig simulationConfig, FP deltaTime) : base(context) { - Assert.Check(context != null); - - _systemsAll = systemsAll; - _systemsRoots = systemsRoots; - - _runtimeConfig = runtimeConfig; - _simulationConfig = simulationConfig; - _sessionConfig = sessionConfig; - - _playerData = new PersistentMap(); - - AllocGen(); - InitStatic(); - InitGen(); - - Assets = new FrameAssets(this); - Events = new FrameEvents(this); - Signals = new FrameSignals(this); - Unsafe = new FrameBaseUnsafe(this); - - Physics2D = new Physics2D.PhysicsEngine2D.Api(this, context.TaskContext.ThreadCount); - Physics3D = new Physics3D.PhysicsEngine3D.Api(this, context.TaskContext.ThreadCount); - - // player data set signal - _ISignalOnPlayerDataSet = BuildSignalsArray(); - - // 2D Physics collision signals - _ISignalOnCollision2DSystems = BuildSignalsArray(); - _ISignalOnCollisionEnter2DSystems = BuildSignalsArray(); - _ISignalOnCollisionExit2DSystems = BuildSignalsArray(); - - // 2D Physics trigger signals - _ISignalOnTrigger2DSystems = BuildSignalsArray(); - _ISignalOnTriggerEnter2DSystems = BuildSignalsArray(); - _ISignalOnTriggerExit2DSystems = BuildSignalsArray(); - - // 3D Physics collision signals - _ISignalOnCollision3DSystems = BuildSignalsArray(); - _ISignalOnCollisionEnter3DSystems = BuildSignalsArray(); - _ISignalOnCollisionExit3DSystems = BuildSignalsArray(); - - // 3D Physics trigger signals - _ISignalOnTrigger3DSystems = BuildSignalsArray(); - _ISignalOnTriggerEnter3DSystems = BuildSignalsArray(); - _ISignalOnTriggerExit3DSystems = BuildSignalsArray(); - - _ISignalOnNavMeshWaypointReachedSystems = BuildSignalsArray(); - _ISignalOnNavMeshSearchFailedSystems = BuildSignalsArray(); - _ISignalOnNavMeshMoveAgentSystems = BuildSignalsArray(); - - // map changed signal - _ISignalOnMapChangedSystems = BuildSignalsArray(); - - // prototype materialized signal - _ISignalOnEntityPrototypeMaterializedSystems = BuildSignalsArray(); - if ( _ISignalOnEntityPrototypeMaterializedSystems.Length > 0 ) { - base._SignalOnEntityPrototypeMaterialized = (entity, prototype) => Signals.OnEntityPrototypeMaterialized(entity, prototype); - } - - _ISignalOnPlayerConnectedSystems = BuildSignalsArray(); - _ISignalOnPlayerDisconnectedSystems = BuildSignalsArray(); - - // assign map, rng session, etc. - _globals->Map = FindAsset(runtimeConfig.Map.Id); - _globals->RngSession = new RNGSession(runtimeConfig.Seed); - _globals->DeltaTime = deltaTime; - - _systemIndexByType = new Dictionary(_systemsAll.Length); - - for (Int32 i = 0; i < _systemsAll.Length; ++i) { - var systemType = _systemsAll[i].GetType(); - - if (_systemIndexByType.ContainsKey(systemType) == false) { - _systemIndexByType.Add(systemType, i); - } - - // set default enabled systems - if (_systemsAll[i].StartEnabled) { - _globals->Systems.Set(_systemsAll[i].RuntimeIndex); - } - } - - // init physics settings - Quantum.PhysicsSceneSettings.Init(&_globals->PhysicsSettings, simulationConfig.Physics); - - // Init navmesh regions to all bit fields to be set - ClearAllNavMeshRegions(); - - // user callbacks - AllocUser(); - InitUser(); - } - - /// - /// Set the prediction area. - /// - /// Center of the prediction area - /// Radius of the prediction area - /// The Prediction Culling feature must be explicitly enabled in . - /// This can be safely called from the main-thread. - /// Prediction Culling allows developers to save CPU time in games where the player has only a partial view of the game scene. - /// Quantum prediction and rollbacks, which are time consuming, will only run for important entities that are visible to the local player(s). Leaving anything outside that area to be simulated only once per tick with no rollbacks as soon as the inputs are confirmed from server. - /// It is safe and simple to activate and, depending on the game, the performance difference can be quite large.Imagine a 30Hz game to constantly rollback ten ticks for every confirmed input (with more players, the predictor eventually misses at least for one of them). This requires the game simulation to be lightweight to be able to run at almost 300Hz(because of the rollbacks). With Prediction Culling enabled the full frames will be simulated at the expected 30Hz all the time while the much smaller prediction area is the only one running within the prediction buffer. - public void SetPredictionArea(FPVector3 position, FP radius) { - Context.SetPredictionArea(position, radius); - } - - /// - /// See . - /// - /// - /// - public void SetPredictionArea(FPVector2 position, FP radius) { - Context.SetPredictionArea(position.XOY, radius); - } - - /// - /// Test is a position is inside the prediction area. - /// - /// Position - /// True if the position is inside the prediction area. - public Boolean InPredictionArea(FPVector3 position) { - return Context.InPredictionArea(this, position); - } - - /// - /// See . - /// - /// - /// - public Boolean InPredictionArea(FPVector2 position) { - return Context.InPredictionArea(this, position); - } - - - /// - /// Serializes the frame using a temporary buffer (20MB). - /// - /// - /// - public override Byte[] Serialize(DeterministicFrameSerializeMode mode) { - return Serialize(mode, new byte[1024 * 1024 * 20], allocOutput: true).Array; - } - - /// - /// Serializes the frame using as a buffer for temporary data. - /// - /// If is set to false, then is also used for the final data - use offset and count from the result to access - /// the part of where serialized frame is stored. - /// - /// If is set to true then a new array is allocated for the result. - /// - /// Despite accepting a buffer, this method still allocates a few small temporary objects. - /// is also going - /// to allocate when serializing DynamicAssetDB, but how much depends on the serializer itself and the number of dynamic assets. - /// - /// - /// - /// - /// - /// Segment of where the serialized frame is stored - /// Do not serialize during GameStart callback because systems have not been initialized, yet. Rather use CallbackSimulateFinished to wait for the first update. - public ArraySegment Serialize(DeterministicFrameSerializeMode mode, byte[] buffer, int offset = 0, bool allocOutput = false) { - offset = ByteUtils.AddValueBlock((int)mode, buffer, offset); - offset = ByteUtils.AddValueBlock(Number, buffer, offset); - offset = ByteUtils.AddValueBlock(CalculateChecksum(false), buffer, offset); - - BitStream stream; - - { - offset = ByteUtils.BeginByteBlockHeader(buffer, offset, out var blockOffset); - - stream = new BitStream(buffer, buffer.Length - offset, offset) { - Writing = true - }; - - SerializeRuntimePlayers(stream); - - offset = ByteUtils.EndByteBlockHeader(buffer, blockOffset, stream.BytesRequired); - } - - { - offset = ByteUtils.BeginByteBlockHeader(buffer, offset, out var blockOffset); - - stream.SetBuffer(buffer, buffer.Length - offset, offset); - var serializer = new FrameSerializer(mode, this, stream) { - Writing = true - }; - - SerializeState(serializer); - - offset = ByteUtils.EndByteBlockHeader(buffer, blockOffset, stream.BytesRequired); - } - - _dynamicAssetDB.Serialize(Context.AssetSerializer, out var assetDBHeader, out var assetDBData); - - { - // write the header for the byte block but don't actually copy into the buffer - // - we'll do that later during the compression stage - offset = ByteUtils.BeginByteBlockHeader(buffer, offset, out var blockOffset); - ByteUtils.EndByteBlockHeader(buffer, blockOffset, assetDBHeader.Length + assetDBData.Length); - } - - using (var outputStream = allocOutput ? new MemoryStream() : new MemoryStream(buffer, offset, buffer.Length - offset)) { - using (var compressedOutput = ByteUtils.CreateGZipCompressStream(outputStream)) { - compressedOutput.Write(buffer, 0, offset); - compressedOutput.Write(assetDBHeader, 0, assetDBHeader.Length); - compressedOutput.Write(assetDBData, 0, assetDBData.Length); - } - - if (allocOutput) { - return new ArraySegment(outputStream.ToArray()); - } else { - return new ArraySegment(buffer, offset, (int)outputStream.Position); - } - - } - } - - public override void Deserialize(Byte[] data) { - var blocks = ByteUtils.ReadByteBlocks(ByteUtils.GZipDecompressBytes(data)).ToArray(); - - var mode = (DeterministicFrameSerializeMode)BitConverter.ToInt32(blocks[0], 0); - - Number = BitConverter.ToInt32(blocks[1], 0); - - var checksum = BitConverter.ToUInt64(blocks[2], 0); - - DeserializeRuntimePlayers(blocks[3]); - DeserializeDynamicAssetDB(blocks[5]); - - FrameSerializer serializer; - serializer = new FrameSerializer(mode, this, blocks[4]); - serializer.Reading = true; - - SerializeState(serializer); - - serializer.VerifyNoUnresolvedPointers(); - - if (CalculateChecksum(false) != checksum) { - throw new Exception($"Checksum of deserialized frame does not match checksum in the source data"); - } - } - - void SerializeState(FrameSerializer serializer) { - FrameBase.Serialize(this, serializer); - _globals_.Serialize(_globals, serializer); - SerializeEntitiesGen(serializer); - SerializeUser(serializer); - } - - Byte[] SerializeRuntimePlayers() { - BitStream stream; - stream = new BitStream(1024 * 10); - stream.Writing = true; - SerializeRuntimePlayers(stream); - return stream.ToArray(); - } - - void SerializeRuntimePlayers(BitStream stream) { - stream.WriteInt(_playerData.Count); - - foreach (var player in _playerData.Iterator()) { - stream.WriteInt(player.Key); - stream.WriteInt(player.Value.ActorId); - stream.WriteByteArrayLengthPrefixed(player.Value.Data); - player.Value.Player.Serialize(stream); - } - } - - void DeserializeRuntimePlayers(Byte[] bytes) { - BitStream stream; - stream = new BitStream(bytes); - stream.Reading = true; - - var count = stream.ReadInt(); - _playerData = new PersistentMap(); - for (Int32 i = 0; i < count; ++i) { - var player = stream.ReadInt(); - - RuntimePlayerData data; - data.ActorId = stream.ReadInt(); - data.Data = stream.ReadByteArrayLengthPrefixed(); - data.Player = new RuntimePlayer(); - data.Player.Serialize(stream); - - _playerData = _playerData.Add(player, data); - } - } - - void DeserializeDynamicAssetDB(Byte[] bytes) { - var assets = _dynamicAssetDB.Deserialize(bytes, Context.AssetSerializer); - - foreach (var asset in assets) { - asset.Loaded(_dynamicAssetDB, Context.Allocator); - } - } - - /// - /// Dump the frame in human readable form into a string. - /// - /// Frame representation - public sealed override String DumpFrame(int dumpFlags = 0) { - var printer = new FramePrinter(); - printer.Reset(this); - - printer.IsRawPrintEnabled = ((dumpFlags & DumpFlag_PrintRawValues) == DumpFlag_PrintRawValues); - - // frame info - if ((dumpFlags & DumpFlag_NoIsVerified) == DumpFlag_NoIsVerified) { - printer.AddLine($"#### FRAME DUMP FOR {Number} ####"); - } else { - printer.AddLine($"#### FRAME DUMP FOR {Number} IsVerified={IsVerified} ####"); - } - - if ((dumpFlags & DumpFlag_NoSimulationConfig) != DumpFlag_NoSimulationConfig) { - printer.AddLine(); - printer.AddObject("# " + nameof(SimulationConfig), SimulationConfig); - } - - if ((dumpFlags & DumpFlag_NoRuntimeConfig) != DumpFlag_NoRuntimeConfig) { - printer.AddLine(); - printer.AddObject("# " + nameof(RuntimeConfig), RuntimeConfig); - } - - if ((dumpFlags & DumpFlag_NoDeterministicSessionConfig) != DumpFlag_NoDeterministicSessionConfig) { - printer.AddLine(); - printer.AddObject("# " + nameof(SessionConfig), SessionConfig); - } - - if ((dumpFlags & DumpFlag_NoRuntimePlayers) != DumpFlag_NoRuntimePlayers) { - printer.AddLine(); - printer.AddLine("# PLAYERS"); - { - printer.ScopeBegin(); - foreach (var kv in _playerData.Iterator()) { - printer.AddObject($"[{kv.Key}]", kv.Value); - } - printer.ScopeEnd(); - } - } - - // globals state - printer.AddLine(); - printer.AddPointer("# GLOBALS", _globals); - - // print entities - printer.AddLine(); - printer.AddLine("# ENTITIES"); - Print(this, printer, (dumpFlags & DumpFlag_ComponentChecksums) == DumpFlag_ComponentChecksums); - - if ((dumpFlags & DumpFlag_AssetDBCheckums) == DumpFlag_AssetDBCheckums) { - printer.AddLine(); - printer.AddLine("# ASSETDB CHECKSUMS"); - { - printer.ScopeBegin(); - AssetObject[] assets = new AssetObject[1]; - - var orderedAssets = this.Context.AssetDB.FindAllAssets(true).OrderBy(x => x.Guid); - - foreach (var asset in orderedAssets) { - assets[0] = asset; - var bytes = this.Context.AssetSerializer.SerializeAssets(assets); - fixed (byte* p = bytes) { - var hash = CRC64.Calculate(0, p, bytes.Length); - printer.AddLine($"{asset.Identifier}: {hash}"); - } - } - printer.ScopeEnd(); - } - } - - if ((dumpFlags & DumpFlag_NoDynamicDB) != DumpFlag_NoDynamicDB) { - printer.AddLine(); - printer.AddLine("# DYNAMICDB"); - { - printer.ScopeBegin(); - - var assetSerializer = Context.AssetSerializer; - if ((dumpFlags & DumpFlag_ReadableDynamicDB) == DumpFlag_ReadableDynamicDB) { - printer.AddLine($"NextGuid: {_dynamicAssetDB.NextGuid}"); - foreach (var asset in _dynamicAssetDB.Assets) { - printer.AddLine($"{asset.GetType().FullName}:"); - printer.ScopeBegin(); - printer.AddLine($"{assetSerializer.PrintAsset(asset)}"); - printer.ScopeEnd(); - } - } else { - printer.AddLine("Dump: "); - printer.ScopeBegin(); - var data = _dynamicAssetDB.Serialize(assetSerializer); - fixed (byte* p = data) { - UnmanagedUtils.PrintBytesHex(p, data.Length, 32, printer); - } - printer.ScopeEnd(); - } - - printer.ScopeEnd(); - } - } - - // physics states - if (Physics2D != null) { - printer.AddLine(); - Physics2D.Print(printer); - } - - if (Physics3D != null) { - printer.AddLine(); - Physics3D.Print(printer); - } - - // heap state - if ((dumpFlags & DumpFlag_NoHeap) != DumpFlag_NoHeap) { - printer.AddLine(); - printer.AddLine("# HEAP"); - Allocator.Heap.Print(_heap, printer); - } - - // dump user data - var dump = printer.ToString(); - DumpFrameUser(ref dump); - - return dump; - } - - - /// - /// Calculates a checksum for the current game state. If the game is not started with - /// flag, this method is not thread-safe, i.e. calling it from multiple threads for frames from the same simulation is going to break. - /// - public sealed override UInt64 CalculateChecksum() { - return CalculateChecksum(Context.UseSharedChecksumSerializer); - } - - /// - /// Calculates a checksum for the current game state. - /// - /// True - use shared checksum serializer to avoid allocs (not thread-safe). - /// - public UInt64 CalculateChecksum(bool useSharedSerializer) { - FrameSerializer frameSerializer; - if (useSharedSerializer) { - frameSerializer = Context.SharedChecksumSerializer; - Assert.Check(frameSerializer != null); - } else { - frameSerializer = new FrameSerializer(DeterministicFrameSerializeMode.Serialize, this, new FrameChecksumerBitStream()); - } - return CalculateChecksumInternal(frameSerializer); - } - - internal UInt64 CalculateChecksumInternal(FrameSerializer serializer) { - - if (serializer == null) { - throw new ArgumentNullException(nameof(serializer)); - } - - if (serializer.Mode != DeterministicFrameSerializeMode.Serialize) { - throw new ArgumentException($"Serializer needs to be in {nameof(DeterministicFrameSerializeMode.Serialize)} mode", nameof(serializer)); - } - - if (serializer.Stream is FrameChecksumerBitStream checksumStream) { - - Profiling.HostProfiler.Start("CalculateChecksumInternal"); - - try { - serializer.Reset(); - serializer.Writing = true; - serializer.Frame = this; - - checksumStream.Checksum = (ulong)Number; - - // checksum globals - _globals_.Serialize(_globals, serializer); - - // checksum entity registry - FrameBase.Serialize(this, serializer); - - // checksum heap - return checksumStream.Checksum; - } finally { - serializer.Frame = null; - Profiling.HostProfiler.End(); - } - } else { - throw new InvalidOperationException($"Serializer's stream needs to be of {nameof(FrameChecksumerBitStream)} type (is: {serializer.Stream?.GetType().FullName}"); - } - } - - /// - /// Copies the complete frame memory. - /// - /// Input frame object - protected sealed override void Copy(DeterministicFrame frame) { - var f = (Frame)frame; - - // copy player data - _playerData = f._playerData; - - // copy heap from frame - Allocator.Heap.Copy(Context.Allocator, _heap, f._heap); - - // copy entity registry - FrameBase.Copy(this, f); - - // dynamic DB - _dynamicAssetDB.CopyFrom(f._dynamicAssetDB); - - // perform native copy - CopyFromGen(f); - CopyFromUser(f); - } - - public sealed override void Free() { - FreeUser(); - FreeGen(); - base.Free(); - } - - [Obsolete("Use SystemIsEnabledSelf instead.")] - public Boolean SystemIsEnabled() where T : SystemBase { - return SystemIsEnabledSelf(); - } - - [Obsolete("Use SystemIsEnabledSelf instead.")] - public Boolean SystemIsEnabled(Type t) { - return SystemIsEnabledSelf(t); - } - - /// - /// Test if a system is enabled. - /// - /// System type - /// True if the system is enabled - /// Logs an error if the system type is not found. - public Boolean SystemIsEnabledSelf() where T : SystemBase { - var system = FindSystem(); - if (system.Item0 == null) { - return false; - } - - return _globals->Systems.IsSet(system.Item1); - } - - public Boolean SystemIsEnabledSelf(Type t) { - var system = FindSystem(t); - if (system.Item0 == null) { - return false; - } - - return _globals->Systems.IsSet(system.Item1); - } - - public Boolean SystemIsEnabledSelf(SystemBase s) { - if (s == null) { - return false; - } - - return _globals->Systems.IsSet(s.RuntimeIndex); - } - - public Boolean SystemIsEnabledInHierarchy() where T : SystemBase { - var system = FindSystem(); - return SystemIsEnabledInHierarchy(system.Item0); - } - - public Boolean SystemIsEnabledInHierarchy(Type t) { - var system = FindSystem(t); - return SystemIsEnabledInHierarchy(system.Item0); - } - - public Boolean SystemIsEnabledInHierarchy(SystemBase system) { - if (system == null) - return false; - - if (_globals->Systems.IsSet(system.RuntimeIndex) == false) - return false; - if (system.ParentSystem == null) - return true; - - return SystemIsEnabledInHierarchy(system.ParentSystem); - } - - /// - /// Enable a system. - /// - /// System type - /// Logs an error if the system type is not found. - public void SystemEnable() where T : SystemBase { - SystemEnable(typeof(T)); - } - - public void SystemEnable(Type t) { - var system = FindSystem(t); - if (system.Item0 == null) { - return; - } - - if (_globals->Systems.IsSet(system.Item1) == false) { - // set flag - _globals->Systems.Set(system.Item1); - - // Fire callback only if it becomes enabled in hierarchy - if (system.Item0.ParentSystem == null || SystemIsEnabledInHierarchy(system.Item0.ParentSystem)) { - try { - system.Item0.OnEnabled(this); - } catch (Exception exn) { - Log.Exception(exn); - } - } - } - } - - /// - /// Disables a system. - /// - /// System type - /// Logs an error if the system type is not found. - /// - /// // test for a certain asset and disable the system during its OnInit method - /// public override void OnInit(Frame f) { - /// var testSettings = f.FindAsset(f.Map.UserAsset.Id); - /// if (testSettings == null) { - /// f.SystemDisable(); - /// return; - /// } - /// //.. - /// } - /// - public void SystemDisable() where T : SystemBase { - SystemDisable(typeof(T)); - } - - public void SystemDisable(T system) where T : SystemBase { - SystemDisable(system.GetType()); - } - - public void SystemDisable(Type t) { - var system = FindSystem(t); - if (system.Item0 == null) { - return; - } - - if (_globals->Systems.IsSet(system.Item1)) { - // clear flag - _globals->Systems.Clear(system.Item1); - - // Fire callback only if it was previously enabled in hierarchy - if (system.Item0.ParentSystem == null || SystemIsEnabledInHierarchy(system.Item0.ParentSystem)) { - try { - system.Item0.OnDisabled(this); - } catch (Exception exn) { - Log.Exception(exn); - } - } - } - } - - QTuple FindSystem() { - return FindSystem(typeof(T)); - } - - QTuple FindSystem(Type t) { - if (_systemIndexByType.TryGetValue(t, out var i)) { - return QTuple.Create(_systemsAll[i], i); - } - - Log.Error("System '{0}' not found, did you forget to add it to SystemSetup.CreateSystems ?", t.Name); - return new QTuple(null, -1); - } - - - T[] BuildSignalsArray() { - return _systemsAll.Where(x => x is T).Cast().ToArray(); - } - - void BuildSignalsArrayOnComponentAdded() where T : unmanaged, IComponent { - Assert.Check(ComponentTypeId.Id > 0); - - var array = _systemsAll.Where(x => x is ISignalOnComponentAdded).Cast>().ToArray(); - if (array.Length > 0) { - _ComponentSignalsOnAdded[ComponentTypeId.Id] = (entity, componentData) => { - var component = (T*)componentData; - var systems = &(_globals->Systems); - for (Int32 i = 0; i < array.Length; ++i) { - if (SystemIsEnabledInHierarchy((SystemBase)array[i])) { - array[i].OnAdded(this, entity, component); - } - } - }; - } else { - _ComponentSignalsOnAdded[ComponentTypeId.Id] = null; - } - } - - void BuildSignalsArrayOnComponentRemoved() where T : unmanaged, IComponent { - Assert.Check(ComponentTypeId.Id > 0); - - var array = _systemsAll.Where(x => x is ISignalOnComponentRemoved).Cast>().ToArray(); - if (array.Length > 0) { - _ComponentSignalsOnRemoved[ComponentTypeId.Id] = (entity, componentData) => { - var component = (T*)componentData; - var systems = &(_globals->Systems); - for (Int32 i = 0; i < array.Length; ++i) { - if (SystemIsEnabledInHierarchy((SystemBase)array[i])) { - array[i].OnRemoved(this, entity, component); - } - } - }; - } else { - _ComponentSignalsOnRemoved[ComponentTypeId.Id] = null; - } - } - - void AddEvent(EventBase evnt) { - // set evnt.Tick - evnt.Tick = Number; - - // add ast last - Context.Events.AddLast(evnt); - } - - public static void InitStatic() { - StaticDelegates.Init(); - InitStaticGen(); - } - - // partial declarations populated from code generator - static partial void InitStaticGen(); - partial void InitGen(); - partial void FreeGen(); - partial void AllocGen(); - partial void CopyFromGen(Frame frame); - partial void SerializeEntitiesGen(FrameSerializer serializer); - - partial void InitUser(); - partial void FreeUser(); - partial void AllocUser(); - partial void CopyFromUser(Frame frame); - - partial void SerializeUser(FrameSerializer serializer); - partial void DumpFrameUser(ref String dump); - - - /// - /// Gets the runtime player configuration data for a certain player. - /// - /// Player ref - /// Player config or null if player was not found - public RuntimePlayer GetPlayerData(PlayerRef player) { - RuntimePlayerData data; - - if (_playerData.TryFind(player, out data)) { - return data.Player; - } - - return null; - } - - /// - /// Converts a Quantum PlayerRef to an ActorId (Photon client id). - /// - /// Player reference - /// ActorId or null if payer was not found - public Int32? PlayerToActorId(PlayerRef player) { - RuntimePlayerData data; - - if (_playerData.TryFind(player, out data)) { - return data.ActorId; - } - - return null; - } - - /// - /// Returns the first player that is using a certain ActorId (Photon client id). - /// - /// Actor id - /// Player reference or null if actor id was not found - /// The first player because multiple players from the same Photon client can join. - public PlayerRef? ActorIdToFirstPlayer(Int32 actorId) { - foreach (var kvp in _playerData.Iterator()) { - if (kvp.Value.ActorId == actorId) { - return kvp.Key; - } - } - - return null; - } - - /// - /// Returns all players with a certain ActorId (Photon client id). - /// - /// Actor id - /// Array of player references - public PlayerRef[] ActorIdToAllPlayers(Int32 actorId) { - return _playerData.Iterator().Where(x => x.Value.ActorId == actorId).Select(x => (PlayerRef)x.Key).ToArray(); - } - - public void UpdatePlayerData() { - UInt64 set = 0; - - for (Int32 i = 0; i < PlayerCount; ++i) { - var rpc = GetRawRpc(i); - if (rpc != null && rpc.Length > 0) { - var flags = GetPlayerInputFlags(i); - if ((flags & DeterministicInputFlags.Command) != DeterministicInputFlags.Command) { - var playerDataOriginal = _playerData; - - try { - // create player data - RuntimePlayerData data; - data.Data = rpc; - data.ActorId = BitConverter.ToInt32(rpc, rpc.Length - 4); - data.Player = Quantum.RuntimePlayer.FromByteArray(rpc); - - // set data - _playerData = _playerData.AddOrSet(i, data); - - // set mask - set |= 1UL << FPMath.Clamp(i, 0, 63); -#if DEBUG - } catch (Exception e) { - Log.Error("## RuntimePlayer Deserialization Threw Exception ##"); - Log.Exception(e); -#else - } catch { -#endif - _playerData = playerDataOriginal; - } - } - } - } - - if (set != 0UL) { - for (Int32 i = 0; i < PlayerCount; ++i) { - var b = 1UL << i; - if ((set & b) == b) { - try { - Signals.OnPlayerDataSet(i); - } catch (Exception exn) { - Log.Exception(exn); - } - } - } - } - } - } -} - - -// Core/FrameAssets.cs -namespace Quantum { - partial class Frame { - public partial struct FrameAssets { - Frame _f; - - public FrameAssets(Frame f) { - _f = f; - } - - public EntityView View(string view, DatabaseType dbType = DatabaseType.Default) { - return _f.FindAsset(view, dbType); - } - - public EntityPrototype Prototype(string prototype, DatabaseType dbType = DatabaseType.Default) { - return _f.FindAsset(prototype, dbType); - } - - public EntityView View(AssetRefEntityView view) { - return _f.FindAsset(view.Id); - } - - public EntityPrototype Prototype(AssetRefEntityPrototype prototype) { - return _f.FindAsset(prototype.Id); - } - - public Map Map(AssetRefMap assetRef) { - return _f.FindAsset(assetRef.Id); - } - - public PhysicsMaterial PhysicsMaterial(AssetRefPhysicsMaterial assetRef) { - return _f.FindAsset(assetRef.Id); - } - - public PolygonCollider PolygonCollider(AssetRefPolygonCollider assetRef) { - return _f.FindAsset(assetRef.Id); - } - - public CharacterController3DConfig CharacterController3DConfig(AssetRefCharacterController3DConfig assetRef) { - return _f.FindAsset(assetRef.Id); - } - - public CharacterController2DConfig CharacterController2DConfig(AssetRefCharacterController2DConfig assetRef) { - return _f.FindAsset(assetRef.Id); - } - - public NavMesh NavMesh(AssetRefNavMesh assetRef) { - return _f.FindAsset(assetRef.Id); - } - - public NavMeshAgentConfig NavMeshAgentConfig(AssetRefNavMeshAgentConfig assetRef) { - return _f.FindAsset(assetRef.Id); - } - - public SimulationConfig SimulationConfig(AssetRefSimulationConfig assetRef) { - return _f.FindAsset(assetRef.Id); - } - - public TerrainCollider TerrainCollider(AssetRefTerrainCollider assetRef) { - return _f.FindAsset(assetRef.Id); - } - } - } -} - -// Core/FrameContextUser.cs -namespace Quantum { - public partial class FrameContextUser : Core.FrameContext { - public FrameContextUser(Args args) - : base(args) { - ConstructUser(args); - } - - public override sealed void Dispose() { - DisposeUser(); - base.Dispose(); - } - - partial void ConstructUser(Args args); - partial void DisposeUser(); - } -} - -// Core/FrameEvents.cs - -namespace Quantum { - partial class Frame { - public partial struct FrameEvents { - Frame _f; - - public FrameEvents(Frame f) { - _f = f; - } - } - } -} - - -// Core/FrameSignals.cs - -namespace Quantum { - public unsafe interface ISignalOnComponentAdded : ISignal where T : unmanaged, IComponent { - void OnAdded(Frame f, EntityRef entity, T* component); - } - - public unsafe interface ISignalOnComponentRemoved : ISignal where T : unmanaged, IComponent { - void OnRemoved(Frame f, EntityRef entity, T* component); - } - - public unsafe interface ISignalOnMapChanged : ISignal { - void OnMapChanged(Frame f, AssetRefMap previousMap); - } - - public unsafe interface ISignalOnEntityPrototypeMaterialized : ISignal { - void OnEntityPrototypeMaterialized(Frame f, EntityRef entity, EntityPrototypeRef prototypeRef); - } - - public unsafe interface ISignalOnPlayerConnected : ISignal { - void OnPlayerConnected(Frame f, PlayerRef player); - } - - public unsafe interface ISignalOnPlayerDisconnected : ISignal { - void OnPlayerDisconnected(Frame f, PlayerRef player); - } - - partial class Frame { - public unsafe partial struct FrameSignals { - Frame _f; - - public FrameSignals(Frame f) { - _f = f; - } - - public void OnPlayerDataSet(PlayerRef player) { - var array = _f._ISignalOnPlayerDataSet; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnPlayerDataSet(_f, player); - } - } - } - - public void OnMapChanged(AssetRefMap previousMap) { - var array = _f._ISignalOnMapChangedSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnMapChanged(_f, previousMap); - } - } - } - - public void OnEntityPrototypeMaterialized(EntityRef entity, EntityPrototypeRef prototypeRef) { - var array = _f._ISignalOnEntityPrototypeMaterializedSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnEntityPrototypeMaterialized(_f, entity, prototypeRef); - } - } - } - - public void OnPlayerConnected(PlayerRef player) { - var array = _f._ISignalOnPlayerConnectedSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnPlayerConnected(_f, player); - } - } - } - - public void OnPlayerDisconnected(PlayerRef player) { - var array = _f._ISignalOnPlayerDisconnectedSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnPlayerDisconnected(_f, player); - } - } - } - - public void OnNavMeshWaypointReached(EntityRef entity, FPVector3 waypoint, Navigation.WaypointFlag waypointFlags, ref bool resetAgent) { - var array = _f._ISignalOnNavMeshWaypointReachedSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnNavMeshWaypointReached(_f, entity, waypoint, waypointFlags, ref resetAgent); - } - } - } - - public void OnNavMeshSearchFailed(EntityRef entity, ref bool resetAgent) { - var array = _f._ISignalOnNavMeshSearchFailedSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnNavMeshSearchFailed(_f, entity, ref resetAgent); - } - } - } - - public void OnNavMeshMoveAgent(EntityRef entity, FPVector2 desiredDirection) { - var array = _f._ISignalOnNavMeshMoveAgentSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnNavMeshMoveAgent(_f, entity, desiredDirection); - } - } - } - - public void OnCollision2D(CollisionInfo2D info) { - var array = _f._ISignalOnCollision2DSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnCollision2D(_f, info); - } - } - } - - public void OnCollisionEnter2D(CollisionInfo2D info) { - var array = _f._ISignalOnCollisionEnter2DSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnCollisionEnter2D(_f, info); - } - } - } - - public void OnCollisionExit2D(ExitInfo2D info) { - var array = _f._ISignalOnCollisionExit2DSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnCollisionExit2D(_f, info); - } - } - } - - public void OnTrigger2D(TriggerInfo2D info) { - var array = _f._ISignalOnTrigger2DSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnTrigger2D(_f, info); - } - } - } - - public void OnTriggerEnter2D(TriggerInfo2D info) { - var array = _f._ISignalOnTriggerEnter2DSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnTriggerEnter2D(_f, info); - } - } - } - - public void OnTriggerExit2D(ExitInfo2D info) { - var array = _f._ISignalOnTriggerExit2DSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnTriggerExit2D(_f, info); - } - } - } - - public void OnCollision3D(CollisionInfo3D info) { - var array = _f._ISignalOnCollision3DSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnCollision3D(_f, info); - } - } - } - - public void OnCollisionEnter3D(CollisionInfo3D info) { - var array = _f._ISignalOnCollisionEnter3DSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnCollisionEnter3D(_f, info); - } - } - } - - public void OnCollisionExit3D(ExitInfo3D info) { - var array = _f._ISignalOnCollisionExit3DSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnCollisionExit3D(_f, info); - } - } - } - - public void OnTrigger3D(TriggerInfo3D info) { - var array = _f._ISignalOnTrigger3DSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnTrigger3D(_f, info); - } - } - } - - public void OnTriggerEnter3D(TriggerInfo3D info) { - var array = _f._ISignalOnTriggerEnter3DSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnTriggerEnter3D(_f, info); - } - } - } - - public void OnTriggerExit3D(ExitInfo3D info) { - var array = _f._ISignalOnTriggerExit3DSystems; - for (Int32 i = 0; i < array.Length; ++i) { - var s = array[i]; - if (_f.SystemIsEnabledInHierarchy((SystemBase)s)) { - s.OnTriggerExit3D(_f, info); - } - } - } - } - } -} - - -// Core/NavMeshSignals.cs - -namespace Quantum -{ - /// - /// Signal is fired when an agent reaches a waypoint. - /// - /// Requires enabled in . - /// \ingroup NavigationApi - public unsafe interface ISignalOnNavMeshWaypointReached : ISignal { - /// Current frame object - /// The entity the navmesh agent component belongs to - /// The current waypoint position - /// The current waypoint flags - /// If set to true the NavMeshPathfinder component will be cleared and stopped. Set to false if NavMeshPathfinder.SetTarget() was called inside the callback. - void OnNavMeshWaypointReached(Frame f, EntityRef entity, FPVector3 waypoint, Navigation.WaypointFlag waypointFlags, ref bool resetAgent); - } - - /// - /// Signal is fired when the agent could not find a path in the agent update after using - /// - /// Requires enabled in . - /// \ingroup NavigationApi - public unsafe interface ISignalOnNavMeshSearchFailed: ISignal { - /// Current frame object - /// The entity the navmesh agent component belongs to - /// Set this to true if the agent should reset its internal state (default is true). - void OnNavMeshSearchFailed(Frame f, EntityRef entity, ref bool resetAgent); - } - - /// - /// Signal is called when the agent should move. The desired direction is influence by avoidance. - /// - /// The agent velocity should be set in the callback. - /// \ingroup NavigationApi - public unsafe interface ISignalOnNavMeshMoveAgent: ISignal { - void OnNavMeshMoveAgent(Frame f, EntityRef entity, FPVector2 desiredDirection); - } -} - - -// Core/RecordingFlags.cs - -namespace Quantum { - [Flags] - public enum RecordingFlags { - None = 0, - Input = 1 << 0, - Checksums = 1 << 1, - Default = Input | Checksums, - All = 0xFF - } -} - -// Core/RuntimeConfig.cs - -namespace Quantum { - /// - /// In contrast to the , which has only static configuration data, the RuntimeConfig holds information that can be different from game to game. - /// - /// By default is defines for example what map to load and the random start seed. It is assembled from scratch each time starting a game. - /// Developers can add custom data to quantum_code/quantum.state/RuntimeConfig.User.cs (don't forget to fill out the serialization methods). - /// Like the this config is distributed to every other client after the first player connected and joined the Quantum plugin. - [Serializable] - public partial class RuntimeConfig { - /// Seed to initialize the randomization session under . - public Int32 Seed; - /// Asset reference of the Quantum map used with the upcoming game session. - public AssetRefMap Map; - /// Asset reference to the SimulationConfig used with the upcoming game session. - public AssetRefSimulationConfig SimulationConfig; - - /// - /// Serializing the members to be send to the server plugin and other players. - /// - /// Input output stream - public void Serialize(BitStream stream) { - stream.Serialize(ref Seed); - stream.Serialize(ref Map.Id.Value); - stream.Serialize(ref SimulationConfig.Id.Value); - SerializeUserData(stream); - } - - /// - /// Dump the content into a human readable form. - /// - /// String representation - public String Dump() { - String dump = ""; - DumpUserData(ref dump); - - StringBuilder sb = new StringBuilder(); - sb.Append(dump); - sb.Append("\n"); - sb.AppendLine("Seed: " + Seed); - sb.AppendLine($"Map.Guid: {Map.ToString()}"); - sb.AppendLine($"SimulationConfig.Guid: {SimulationConfig.ToString()} "); - - return sb.ToString(); - } - - partial void DumpUserData(ref String dump); - partial void SerializeUserData(BitStream stream); - - /// - /// Serialize the class into a byte array. - /// - /// Config to serialized - /// Byte array - public static Byte[] ToByteArray(RuntimeConfig config) { - BitStream stream; - - stream = new BitStream(new Byte[8192]); - stream.Writing = true; - - config.Serialize(stream); - - return stream.ToArray(); - } - - /// - /// Deserialize the class from a byte array. - /// - /// Config class in byte array form - /// New instance of the deserialized class - public static RuntimeConfig FromByteArray(Byte[] data) { - BitStream stream; - stream = new BitStream(data); - stream.Reading = true; - - RuntimeConfig config; - config = new RuntimeConfig(); - config.Serialize(stream); - - return config; - } - } -} - - -// Core/RuntimePlayer.cs - -namespace Quantum { - - public interface ISignalOnPlayerDataSet : ISignal { - void OnPlayerDataSet(Frame f, PlayerRef player); - } - - [Serializable] - public partial class RuntimePlayer { - public void Serialize(BitStream stream) { - SerializeUserData(stream); - } - - public String Dump() { - String dump = ""; - DumpUserData(ref dump); - return dump ?? ""; - } - - partial void DumpUserData(ref String dump); - partial void SerializeUserData(BitStream stream); - - public static Byte[] ToByteArray(RuntimePlayer player) { - BitStream stream; - - stream = new BitStream(new Byte[8192]); - stream.Writing = true; - - player.Serialize(stream); - - return stream.ToArray(); - } - - public static RuntimePlayer FromByteArray(Byte[] data) { - BitStream stream; - stream = new BitStream(data); - stream.Reading = true; - - RuntimePlayer player; - player = new RuntimePlayer(); - player.Serialize(stream); - - return player; - } - } -} - - -// Core/SimulationConfig.cs - -namespace Quantum { - /// - /// The SimulationConfig holds parameters used in the ECS layer and inside core systems like physics and navigation. - /// - [Serializable, AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)] - public partial class SimulationConfig : AssetObject { - public const long DEFAULT_ID = (long)DefaultAssetGuids.SimulationConfig; - - public enum AutoLoadSceneFromMapMode { - Disabled, - Legacy, - UnloadPreviousSceneThenLoad, - LoadThenUnloadPreviousScene - } - - /// - /// Global navmesh configurations. - /// - [Space] - public Navigation.Config Navigation; - /// - /// Global physics configurations. - /// - [Space] - public PhysicsCommon.Config Physics; - /// - /// Global entities configuration - /// - [Space] - public FrameBase.EntitiesConfig Entities; - /// - /// This option will trigger a Unity scene load during the Quantum start sequence.\n - /// This might be convenient to start with but once the starting sequence is customized disable it and implement the scene loading by yourself. - /// "Previous Scene" refers to a scene name in Quantum Map. - /// - [Tooltip("This option will trigger a Unity scene load during the Quantum start sequence.\nThis might be convenient to start with but once the starting sequence is customized disable it and implement the scene loading by yourself.\n\"Previous Scene\" refers to a scene name in Quantum Map.")] - public AutoLoadSceneFromMapMode AutoLoadSceneFromMap = AutoLoadSceneFromMapMode.UnloadPreviousSceneThenLoad; - /// - /// Configure how the client tracks the time to progress the Quantum simulation from the QuantumRunner class. - /// - [Tooltip("Configure how the client tracks the time to progress the Quantum simulation from the QuantumRunner class.")] - public SimulationUpdateTime DeltaTimeType = SimulationUpdateTime.Default; - /// - /// Define the max heap size for one page of memory the frame class uses for custom allocations like QList<> for example. - /// - /// 2^15 = 32.768 bytes - /// TotalHeapSizeInBytes = (1 << HeapPageShift) * HeapPageCount - [Tooltip("Define the max heap size for one page of memory the frame class uses for custom allocations like QList<> for example.\n\n2^15 = 32.768 bytes\nTotalHeapSizeInBytes = (1 << HeapPageShift) * HeapPageCount\n\nDefault is 15.")] - public int HeapPageShift = 15; - /// - /// Define the max heap page count for memory the frame class uses for custom allocations like QList<> for example. - /// - /// TotalHeapSizeInBytes = (1 << HeapPageShift) * HeapPageCount - [Tooltip("Define the max heap page count for memory the frame class uses for custom allocations like QList<> for example.\n\nTotalHeapSizeInBytes = (1 << HeapPageShift) * HeapPageCount\n\nDefault is 256.")] - public int HeapPageCount = 256; - /// - /// Sets extra heaps to allocate for a session in case you need to - /// create 'auxiliary' frames than actually required for the simulation itself - /// - [Tooltip("Sets extra heaps to allocate for a session in case you need to create 'auxiliary' frames than actually required for the simulation itself.\nDefault is 0.")] - public int HeapExtraCount = 0; - /// - /// Override the number of threads used internally. - /// - [Tooltip("Override the number of threads used internally.\nDefault is 2.")] - public int ThreadCount = 2; - /// - /// How long to store checksumed verified frames. The are used to generate a frame dump in case of a checksum error happening. Not used in Replay and Local mode. - /// - [Tooltip("How long to store checksumed verified frames.\nThe are used to generate a frame dump in case of a checksum error happening. Not used in Replay and Local mode.\nDefault is 3.")] - public FP ChecksumSnapshotHistoryLengthSeconds = 3; - /// - /// Additional options for checksum dumps, if the default settings don't provide a clear picture. - /// - [EnumFlags] - [Tooltip("Additional options for checksum dumps, if the default settings don't provide a clear picture. ")] - public SimulationConfigChecksumErrorDumpOptions ChecksumErrorDumpOptions; - } - - [Serializable, StructLayout(LayoutKind.Explicit)] - public unsafe struct AssetRefSimulationConfig : IEquatable { - public const int SIZE = sizeof(ulong); - - [FieldOffset(0)] - public AssetGuid Id; - - public static implicit operator AssetRefSimulationConfig(Map value) { - var r = default(AssetRefSimulationConfig); - if (value != null) { - r.Id = value.Guid; - } - return r; - } - - public static void Serialize(void* ptr, FrameSerializer serializer) { - var p = (AssetRefSimulationConfig*)ptr; - AssetGuid.Serialize(&p->Id, serializer); - } - - public override string ToString() { - return AssetRef.ToString(Id); - } - - public bool Equals(AssetRefSimulationConfig other) { - return Id.Equals(other.Id); - } - - public override bool Equals(object obj) { - return obj is AssetRefSimulationConfig other && Equals(other); - } - - public override int GetHashCode() { - return Id.GetHashCode(); - } - - public static bool operator ==(AssetRefSimulationConfig a, AssetRefSimulationConfig b) { - return a.Id == b.Id; - } - - public static bool operator !=(AssetRefSimulationConfig a, AssetRefSimulationConfig b) { - return a.Id != b.Id; - } - } - - public static class AssetRefSimulationConfigExt { - public static SimulationConfig FindAsset(this Core.FrameBase f, AssetRefSimulationConfig assetRef) { - return f.FindAsset(assetRef.Id); - } - } - - [Flags] - public enum SimulationConfigChecksumErrorDumpOptions { - SendAssetDBChecksums = 1 << 0, - ReadableDynamicDB = 1 << 1, - RawFPValues = 1 << 2, - ComponentChecksums = 1 << 3, - } - -} - - -// Core/SimulationUpdateTime.cs -namespace Quantum { - /// - /// The type of measuring time progressions to update the local simulation. - /// - /// Caveat: Changing it will make every client use the setting which might be undesirable when only used for debugging. - public enum SimulationUpdateTime { - /// - /// Internal stopwatch. Recommended for releasing games. - /// - Default, - /// - /// Engine (Unity) delta time. Extremely useful when pausing the Unity simulation during debugging for example. - /// - /// Caveat: the setting can cause issues with time synchronization when initializing online matches: the time tracking can be inaccurate under load (e.g.level loading) and result in a lot of large extra time syncs request and canceled inputs for a client when starting an online game. - EngineDeltaTime, - /// - /// Engine unscaled delta time. - /// - EngineUnscaledDeltaTime - } -} - -// Core/StaticDelegates.cs - -namespace Quantum { - public static unsafe partial class StaticDelegates { - internal struct Tag {} - - public static void Init() { - CallOnce.Invoke(() => InitGen()); - } - - static partial void InitGen(); - } -} - -// Core/TypeRegistry.cs - -namespace Quantum { - public partial class TypeRegistry { - readonly Dictionary _types = new Dictionary(); - - public ReadOnlyDictionary Types { - get { - return new ReadOnlyDictionary(_types); - } - } - - public TypeRegistry() { - AddBuiltIns(); - AddGenerated(); - } - - void Register(Type type, int size) { - if (_types.ContainsKey(type)) { - return; - } - - _types.Add(type, size); - } - - void AddBuiltIns() { - Register(typeof(EntityRef), EntityRef.SIZE); - Register(typeof(ComponentReference), ComponentReference.SIZE); - Register(typeof(AssetRef), AssetRef.SIZE); - Register(typeof(Shape2DType), 1); - Register(typeof(Shape3DType), 1); - Register(typeof(Shape2D), Shape2D.SIZE); - Register(typeof(Shape2D.BoxShape), Shape2D.BoxShape.SIZE); - Register(typeof(Shape2D.CircleShape), Shape2D.CircleShape.SIZE); - Register(typeof(Shape2D.PolygonShape), Shape2D.PolygonShape.SIZE); - Register(typeof(Shape2D.EdgeShape), Shape2D.EdgeShape.SIZE); - Register(typeof(Shape2D.CompoundShape2D), Shape2D.CompoundShape2D.SIZE); - Register(typeof(Shape3D), Shape3D.SIZE); - Register(typeof(Shape3D.BoxShape), Shape3D.BoxShape.SIZE); - Register(typeof(Shape3D.SphereShape), Shape3D.SphereShape.SIZE); - Register(typeof(Shape3D.MeshShape), Shape3D.MeshShape.SIZE); - Register(typeof(Shape3D.TerrainShape), Shape3D.TerrainShape.SIZE); - Register(typeof(Shape3D.CompoundShape3D), Shape3D.CompoundShape3D.SIZE); - Register(typeof(Physics2D.Joint), Physics2D.Joint.SIZE); - Register(typeof(Physics2D.JointType), sizeof(short)); - Register(typeof(Physics2D.SpringJoint), Physics2D.SpringJoint.SIZE); - Register(typeof(Physics2D.DistanceJoint), Physics2D.DistanceJoint.SIZE); - Register(typeof(Physics2D.HingeJoint), Physics2D.HingeJoint.SIZE); - Register(typeof(Physics3D.Joint3D), Physics3D.Joint3D.SIZE); - Register(typeof(Physics3D.JointType3D), sizeof(short)); - Register(typeof(Physics3D.SpringJoint3D), Physics3D.SpringJoint3D.SIZE); - Register(typeof(Physics3D.DistanceJoint3D), Physics3D.DistanceJoint3D.SIZE); - Register(typeof(Physics3D.HingeJoint3D), Physics3D.HingeJoint3D.SIZE); - Register(typeof(QBoolean), QBoolean.SIZE); - - // register internal heap types - heap struct itself does not need to be registered - Allocator.Heap.RegisterInternalTypes(Register); - - // register internal physics types - PhysicsCommon.RegisterInternalTypes(Register); - Physics2D.PhysicsEngine2D.RegisterInternalTypes(Register); - Physics3D.PhysicsEngine3D.RegisterInternalTypes(Register); - - // register collection memory integrity checks - Collections.QCollectionsUtils.RegisterTypes(Register); - } - - partial void AddGenerated(); - } -} - -// Game/CallbackDispatcher.cs - -namespace Quantum { - public class CallbackDispatcher : DispatcherBase, Quantum.ICallbackDispatcher { - - protected static Dictionary GetBuiltInTypes() { - return new Dictionary() { - { typeof(CallbackChecksumComputed), CallbackChecksumComputed.ID }, - { typeof(CallbackChecksumError), CallbackChecksumError.ID }, - { typeof(CallbackChecksumErrorFrameDump), CallbackChecksumErrorFrameDump.ID }, - { typeof(CallbackEventCanceled), CallbackEventCanceled.ID }, - { typeof(CallbackEventConfirmed), CallbackEventConfirmed.ID }, - { typeof(CallbackGameDestroyed), CallbackGameDestroyed.ID }, - { typeof(CallbackGameStarted), CallbackGameStarted.ID }, - { typeof(CallbackGameResynced), CallbackGameResynced.ID }, - { typeof(CallbackInputConfirmed), CallbackInputConfirmed.ID }, - { typeof(CallbackPollInput), CallbackPollInput.ID }, - { typeof(CallbackSimulateFinished), CallbackSimulateFinished.ID }, - { typeof(CallbackUpdateView), CallbackUpdateView.ID }, - { typeof(CallbackPluginDisconnect), CallbackPluginDisconnect.ID }, - }; - } - - public CallbackDispatcher() : base(GetBuiltInTypes()) { } - protected CallbackDispatcher(Dictionary callbackTypes) : base(callbackTypes) { } - - public bool Publish(CallbackBase e) { - return base.InvokeMeta(e.ID, e); - } - } -} - - -// Game/ChecksumErrorFrameDumpContext.cs - -namespace Quantum { - public unsafe partial class ChecksumErrorFrameDumpContext { - public SimulationConfig SimulationConfig; - public QTuple[] AssetDBChecksums; - - private ChecksumErrorFrameDumpContext() {} - - public ChecksumErrorFrameDumpContext(QuantumGame game, Frame frame) { - var options = game.Configurations.Simulation.ChecksumErrorDumpOptions; - - SimulationConfig = game.Configurations.Simulation; - - // write checksums - if ((options & SimulationConfigChecksumErrorDumpOptions.SendAssetDBChecksums) == SimulationConfigChecksumErrorDumpOptions.SendAssetDBChecksums) { - - var assets = frame.Context.AssetDB.FindAllAssets(true).ToList(); - AssetDBChecksums = new QTuple[assets.Count]; - assets.Sort((a, b) => a.Guid.CompareTo(b.Guid)); - - AssetObject[] temp = new AssetObject[1]; - for (int i = 0; i < assets.Count; ++i) { - temp[0] = assets[i]; - var bytes = frame.Context.AssetSerializer.SerializeAssets(temp); - fixed (byte* p = bytes) { - var crc = CRC64.Calculate(0, p, bytes.Length); - AssetDBChecksums[i] = QTuple.Create(assets[i].Guid, crc); - } - } - } - - ConstructUser(game, frame); - } - - partial void ConstructUser(QuantumGame game, Frame frame); - partial void SerializeUser(QuantumGame game, BinaryWriter writer); - partial void DeserializeUser(QuantumGame game, BinaryReader reader); - - public void Serialize(QuantumGame game, BinaryWriter writer) { - writer.Write(AssetDBChecksums?.Length ?? 0); - if (AssetDBChecksums != null) { - foreach (var asset in AssetDBChecksums) { - writer.Write(asset.Item0.Value); - writer.Write(asset.Item1); - } - } - - // write simulation config - if (SimulationConfig != null) { - var simConfigBytes = game.AssetSerializer.SerializeAssets(new[] { SimulationConfig }); - writer.Write(simConfigBytes.Length); - writer.Write(simConfigBytes); - } else { - writer.Write(0); - } - - SerializeUser(game, writer); - } - - public static ChecksumErrorFrameDumpContext Deserialize(QuantumGame game, BinaryReader reader) { - var result = new ChecksumErrorFrameDumpContext(); - - // read checksums - { - int count = reader.ReadInt32(); - if (count > 0) { - result.AssetDBChecksums = new QTuple[count]; - - for (int i = 0; i < count; ++i) { - var guidRaw = reader.ReadInt64(); - var crc64 = reader.ReadUInt64(); - result.AssetDBChecksums[i] = QTuple.Create(new AssetGuid(guidRaw), crc64); - } - } - } - - // read sim config - { - int count = reader.ReadInt32(); - if (count > 0) { - var configBytes = reader.ReadBytes(count); - result.SimulationConfig = (SimulationConfig)game.AssetSerializer.DeserializeAssets(configBytes).Single(); - } - } - - result.DeserializeUser(game, reader); - - return result; - } - } -} - - -// Game/EventDispatcher.cs - -namespace Quantum { - public class EventDispatcher : DispatcherBase, IEventDispatcher { - - private static Dictionary GetEventTypes() { - var result = new Dictionary { - { typeof(EventBase), 0 } - }; - - for (int eventID = 0; eventID < Frame.FrameEvents.EVENT_TYPE_COUNT; ++eventID) { - result.Add(Frame.FrameEvents.GetEventType(eventID), eventID + 1); - } - - return result; - } - - public EventDispatcher() : base(GetEventTypes()) { } - - public unsafe bool Publish(EventBase e) { - - int eventDepth = 0; - for (int id = e.Id; id >= 0; id = Frame.FrameEvents.GetParentEventID(id)) { - ++eventDepth; - } - - int* eventIdStack = stackalloc int[eventDepth]; - for (int id = e.Id, i = 0; id >= 0; id = Frame.FrameEvents.GetParentEventID(id), i++) { - eventIdStack[i] = id; - } - - bool hadActiveHandlers = false; - - // start with the EventBase - int metaIndex = 0; - - for (; ; ) { - hadActiveHandlers |= base.InvokeMeta(metaIndex, e); - - if (--eventDepth >= 0) { - // choose next event - metaIndex = eventIdStack[eventDepth] + 1; - } else { - break; - } - } - - return hadActiveHandlers; - } - } -} - - -// Game/InstantReplaySettings.cs - -namespace Quantum { - [Serializable] - public struct InstantReplaySettings { - public int SnapshotsPerSecond; - public FP LenghtSeconds; - - public static InstantReplaySettings Default => new InstantReplaySettings() { - LenghtSeconds = 3, - SnapshotsPerSecond = 1, - }; - - public static InstantReplaySettings FromLength(FP length, int snapshotsPerSecond) { - return new InstantReplaySettings() { - LenghtSeconds = length, - SnapshotsPerSecond = snapshotsPerSecond - }; - } - - public override string ToString() { - return $"({nameof(SnapshotsPerSecond)}: {SnapshotsPerSecond}, {nameof(LenghtSeconds)}: {LenghtSeconds})"; - } - } -} - - -// Game/QuantumGame.Snapshots.cs - -namespace Quantum { - public partial class QuantumGame { - - DeterministicFrameRingBuffer _checksumSnapshotBuffer; - DeterministicFrameRingBuffer _instantReplaySnapshotBuffer; - - bool _instantReplaySnapshotsRecording; - Int32 _commonSnapshotInterval; - Int32 _instantReplaySnapshotInterval; - - void SnapshotsOnDestroy() { - _checksumSnapshotBuffer?.Clear(); - _checksumSnapshotBuffer = null; - _instantReplaySnapshotBuffer?.Clear(); - _instantReplaySnapshotBuffer = null; - } - - void SnapshotsOnSimulateFinished(DeterministicFrame state) { - - if (!state.IsVerified) { - return; - } - - HostProfiler.Start("QuantumGame.RecordingSnapshots"); - - if (_checksumSnapshotBuffer != null) { - // in case replay interval is less than checksum interval and replay is not being recorded, - // there's no need to sample at a common rate - Int32 interval; - if (_commonSnapshotInterval > 0 && _instantReplaySnapshotsRecording) { - Assert.Check(_instantReplaySnapshotBuffer == null); - interval = _commonSnapshotInterval; - } else { - interval = Session.SessionConfig.ChecksumInterval; - } - - if ((state.Number % interval) == 0) { - _checksumSnapshotBuffer.PushBack(state, this, _context); - } - } - - if (_instantReplaySnapshotsRecording && _instantReplaySnapshotBuffer != null) { - Assert.Check(_commonSnapshotInterval <= 0); - if (_instantReplaySnapshotBuffer.Count == 0 || (state.Number % _instantReplaySnapshotInterval) == 0) { - _instantReplaySnapshotBuffer.PushBack(state, this, _context); - } - } - - HostProfiler.End(); - } - - Int32 SnapshotsCreateBuffers(Int32 simulationRate, Int32 checksumInterval, FP checksumTimeWindow, Int32 replayInterval, FP replayTimeWindow) { - - var checksumFrameWindow = FPMath.CeilToInt(simulationRate * checksumTimeWindow); - var replayFrameWindow = FPMath.CeilToInt(simulationRate * replayTimeWindow); - - var checksumBufferSize = DeterministicFrameRingBuffer.GetSize(FPMath.CeilToInt(simulationRate * checksumTimeWindow), checksumInterval); - var replayBufferSize = DeterministicFrameRingBuffer.GetSize(FPMath.CeilToInt(simulationRate * replayTimeWindow), replayInterval); - - if (checksumInterval > 0 && checksumBufferSize > 0 && replayBufferSize > 0 && replayInterval > 0) { - if (DeterministicFrameRingBuffer.TryGetCommonSamplingPattern(checksumFrameWindow, checksumInterval, replayFrameWindow, replayInterval, out var commonWindow, out var commonInterval)) { - _commonSnapshotInterval = commonInterval; - _checksumSnapshotBuffer = new DeterministicFrameRingBuffer(DeterministicFrameRingBuffer.GetSize(commonWindow, commonInterval)); - Log.Trace($"Snapshots: common buffer created with interval: {_commonSnapshotInterval}, window: {commonWindow}, capacity: {_checksumSnapshotBuffer.Capacity}"); - return _checksumSnapshotBuffer.Capacity; - } else { - // shared buffer not possible - Log.Warn($"Unable to create a shared buffer for checksumed frames and replay snapshots. This is not optimal. Check the documentation for details."); - } - } - - if (checksumBufferSize > 0) { - _checksumSnapshotBuffer = new DeterministicFrameRingBuffer(checksumBufferSize); - Log.Trace($"Snapshots: checksum buffer created with interval {checksumInterval}, capacity: {checksumBufferSize}"); - } - - if (replayBufferSize > 0) { - _instantReplaySnapshotInterval = replayInterval; - _instantReplaySnapshotBuffer = new DeterministicFrameRingBuffer(replayBufferSize); - Log.Trace($"Snapshots: replay buffer created with interval {replayInterval}, capacity: {replayBufferSize}"); - } - - return checksumBufferSize + replayBufferSize; - } - - static Int32 SnapshotsGetMinBufferSize(Int32 window, Int32 samplingRate) { - return samplingRate <= 0 ? 0 : (1 + window / samplingRate); - } - } -} - -// Game/QuantumGame.cs - -namespace Quantum { - /// - /// This class contains values for flags that will be accessible with . - /// Built-in flags control some aspects of QuantumGame inner workings, without affecting the simulation - /// outcome. - /// - public partial class QuantumGameFlags { - /// - /// Starts the game in the server mode. - /// When this flag is not set, all the events marked with "server" get culled immediatelly. - /// If this flag is set, all the events marked with "client" get culled immediatelly. - /// - public const int Server = 1 << 0; - /// - /// By default, QuantumGame uses a single shared checksum serializer to reduce allocations. - /// The serializer is *not* static - it is only shared between frames comming from the same QuantumGame. - /// Set this flag if you want to disable this behaviour, for example if you calculate - /// checksums for multiple frames using multiple threads. - /// - public const int DisableSharedChecksumSerializer = 1 << 1; - /// - /// Custom user flags start from this value. Flags are accessible with . - /// - public const int CustomFlagsStart = 1 << 16; - } - - /// - /// QuantumGame acts as an interface to the simulation from the client code's perspective. - /// - /// Access and method to this class is always safe from the clients point of view. - public unsafe partial class QuantumGame : IDeterministicGame { - public event Action ProfilerSampleGenerated; - - public struct StartParameters { - public IResourceManager ResourceManager; - public IAssetSerializer AssetSerializer; - public ICallbackDispatcher CallbackDispatcher; - public IEventDispatcher EventDispatcher; - public InstantReplaySettings InstantReplaySettings; - public int HeapExtraCount; - public DynamicAssetDB InitialDynamicAssets; - public int GameFlags; - } - - - /// - /// Stores the different frames the simulation uses during one tick. - /// - public class FramesContainer { - public Frame Verified; - public Frame Predicted; - public Frame PredictedPrevious; - public Frame PreviousUpdatePredicted; - } - - // Caveat: Only set after the first CreateFrame() call - public class ConfigurationsContainer { - public RuntimeConfig Runtime; - public SimulationConfig Simulation; - } - - /// Access the frames of various times available during one tick. - public FramesContainer Frames { get; } - - /// Access the configurations that the simulation is running with. - public ConfigurationsContainer Configurations { get; } - - /// Access the Deterministic session object to query more internals. - public DeterministicSession Session { get; private set; } - - /// Used for position interpolation on the client for smoother interpolation results. - public Single InterpolationFactor { get; private set; } - - /// - public InstantReplaySettings InstantReplayConfig { get; private set; } - - /// - public IAssetSerializer AssetSerializer { get; } - - /// Extra heaps to allocate for a session in case you need to create 'auxiliary' frames than actually required for the simulation itself. - public int HeapExtraCount { get; } - - - Byte[] _inputStreamReadZeroArray; - IResourceManager _resourceManager; - ICallbackDispatcher _callbackDispatcher; - IEventDispatcher _eventDispatcher; - - FrameSerializer _inputSerializerRead; - FrameSerializer _inputSerializerWrite; - - SystemBase[] _systemsRoot; - SystemBase[] _systemsAll; - - FrameContext _context; - TypeRegistry _typeRegistry; - bool _polledInputInThisSimulation; - DynamicAssetDB _initialDynamicAssets; - int _flags; - - public QuantumGame(in StartParameters startParams) { - _typeRegistry = new TypeRegistry(); - Frames = new FramesContainer(); - Configurations = new ConfigurationsContainer(); - - _resourceManager = startParams.ResourceManager; - AssetSerializer = startParams.AssetSerializer; - _callbackDispatcher = startParams.CallbackDispatcher; - _eventDispatcher = startParams.EventDispatcher; - InstantReplayConfig = startParams.InstantReplaySettings; - HeapExtraCount = startParams.HeapExtraCount; - _flags = startParams.GameFlags; - - if (startParams.InitialDynamicAssets != null) { - _initialDynamicAssets = new DynamicAssetDB(); - _initialDynamicAssets.CopyFrom(startParams.InitialDynamicAssets); - } - - InitCallbacks(); - } - - [Obsolete] - public QuantumGame(IResourceManager manager, IAssetSerializer assetSerializer, ICallbackDispatcher callbackDispatcher, IEventDispatcher eventDispatcher) - : this(new StartParameters() { - ResourceManager = manager, - AssetSerializer = assetSerializer, - CallbackDispatcher = callbackDispatcher, - EventDispatcher = eventDispatcher, - }) { } - - /// - /// Returns an array that is unique on every client and represents the indexes for players that your local machine controls in the Quantum simulation. - /// - /// Array of player indices - public Int32[] GetLocalPlayers() { - return Session.LocalPlayerIndices; - } - - /// - /// Helps to decide if a PlayerRef is associated with the local player. - /// - /// Player reference - /// True if the player is the local player - public Boolean PlayerIsLocal(PlayerRef playerRef) { - if (playerRef == PlayerRef.None) { - return false; - } - - for (Int32 i = 0; i < Session.LocalPlayerIndices.Length; i++) { - if (Session.LocalPlayerIndices[i] == playerRef) { - return true; - } - } - - return false; - } - - /// - /// Sends a command to the server. - /// - /// Command to send - /// Commands are similar to input, they drive the simulation, but do not have to be sent regularly. - /// - /// RemoveUnitCommand command = new RemoveUnitCommand(); - /// command.CellIndex = 42; - /// QuantumRunner.Default.Game.SendCommand(command); - /// - public void SendCommand(DeterministicCommand command) { - var players = GetLocalPlayers(); - if (players.Length > 0) { - Session.SendCommand(players[0], command); - } else { - Log.Error("No local player found to send command for"); - } - } - - /// - /// Sends a command to the server. - /// - /// Specify the player index (PlayerRef) when you have multiple players controlled from the same machine. - /// Command to send - /// See - /// Games that only have one local player can ignore the player index field. - public void SendCommand(Int32 player, DeterministicCommand command) { - Session.SendCommand(player, command); - } - - /// - /// Send data for the local player to join the online match. - /// If the client has multiple local players, the data will be sent for the first of them (smallest player index). - /// - /// Player data - /// After starting, joining the Quantum Game and after the OnGameStart signal has been fired each player needs to call the SendPlayerData method to be added as a player in every ones simulation.\n - /// The reason this needs to be called explicitly is that it greatly simplifies late-joining players. - public void SendPlayerData(RuntimePlayer data) { - var players = GetLocalPlayers(); - if (players.Length > 0) { - Session.SetPlayerData(players[0], RuntimePlayer.ToByteArray(data)); - } else { - Log.Error("No local player found to send player data for."); - } - } - - /// - /// Send data for one local player to join the online match. - /// - /// Local player index - /// Player data - /// After starting, joining the Quantum Game and after the OnGameStart signal has been fired each player needs to call the SendPlayerData method to be added as a player in every ones simulation.\n - /// The reason this needs to be called explicitly is that it greatly simplifies late-joining players. - public void SendPlayerData(Int32 player, RuntimePlayer data) { - Session.SetPlayerData(player, RuntimePlayer.ToByteArray(data)); - } - - /// - /// - /// - public int GameFlags => _flags; - - public void OnDestroy() { - SnapshotsOnDestroy(); - InvokeOnDestroy(); - } - - public Frame CreateFrame() { - return (Frame)((IDeterministicGame)this).CreateFrame(_context); - } - - DeterministicFrame IDeterministicGame.CreateFrame(IDisposable context) { - return new Frame((FrameContextUser)context, _systemsAll, _systemsRoot, Session.SessionConfig, Configurations.Runtime, Configurations.Simulation, Session.DeltaTime); - } - - DeterministicFrame IDeterministicGame.CreateFrame(IDisposable context, Byte[] data) { - Frame f = CreateFrame(); - f.Deserialize(data); - return f; - } - - public DeterministicFrame GetVerifiedFrame(int tick) { - if (_checksumSnapshotBuffer != null) { - var result = _checksumSnapshotBuffer.Find(tick, DeterministicFrameSnapshotBufferFindMode.Equal); - if (result == null) { - Log.Warn($"Unable to find verified frame for tick {tick}, increase {nameof(DeterministicSessionConfig.ChecksumInterval)} or increase {nameof(SimulationConfig.ChecksumSnapshotHistoryLengthSeconds)}."); - } - return result; - } - return null; - } - - public IDisposable CreateFrameContext() { - if (_context == null) { - Assert.Check(_systemsAll == null); - Assert.Check(_systemsRoot == null); - - // create asset database - var assetDB = _resourceManager.CreateAssetDatabase(); - - // de-serialize runtime config, session is the one from the server - Configurations.Runtime = RuntimeConfig.FromByteArray(Session.RuntimeConfig); - Configurations.Simulation = assetDB.FindAsset(Configurations.Runtime.SimulationConfig.Id, true); - - // register commands - Session.CommandSerializer.RegisterFactories(DeterministicCommandSetup.GetCommandFactories(Configurations.Runtime, Configurations.Simulation)); - - // initialize systems - _systemsRoot = SystemSetup.CreateSystems(Configurations.Runtime, Configurations.Simulation).Where(x => x != null).ToArray(); - _systemsAll = _systemsRoot.SelectMany(x => x.Hierarchy).ToArray(); - - Int32 heapCount = 4; - heapCount += Math.Max(0, Configurations.Simulation.HeapExtraCount); - heapCount += Math.Max(0, HeapExtraCount); - heapCount += SnapshotsCreateBuffers(Session.SessionConfig.UpdateFPS, - Session.IsOnline ? Session.SessionConfig.ChecksumInterval : 0, Configurations.Simulation.ChecksumSnapshotHistoryLengthSeconds, - InstantReplayConfig.SnapshotsPerSecond == 0 ? 0 : Session.SessionConfig.UpdateFPS / InstantReplayConfig.SnapshotsPerSecond, InstantReplayConfig.LenghtSeconds); - - // set system runtime indices - for (Int32 i = 0; i < _systemsAll.Length; ++i) { - _systemsAll[i].RuntimeIndex = i; - } - - // set core count override - Session.PlatformInfo.CoreCount = Configurations.Simulation.ThreadCount; - - FrameContext.Args args; - args.AssetDatabase = assetDB; - args.PlatformInfo = Session.PlatformInfo; - args.IsServer = (_flags & QuantumGameFlags.Server) == QuantumGameFlags.Server; - args.IsLocalPlayer = Session.IsLocalPlayer; - args.HeapConfig = new Heap.Config(Configurations.Simulation.HeapPageShift, Configurations.Simulation.HeapPageCount, heapCount); - args.PhysicsConfig = Configurations.Simulation.Physics; - args.NavigationConfig = Configurations.Simulation.Navigation; - args.CommandSerializer = Session.CommandSerializer; - args.AssetSerializer = AssetSerializer; - args.InitialDynamicAssets = _initialDynamicAssets; - args.UseSharedChecksumSerialized = (_flags & QuantumGameFlags.DisableSharedChecksumSerializer) != QuantumGameFlags.DisableSharedChecksumSerializer; - - // toggle various parts of the context code - args.UsePhysics2D = _systemsAll.FirstOrDefault(x => x is PhysicsSystem2D) != null; - args.UsePhysics3D = _systemsAll.FirstOrDefault(x => x is PhysicsSystem3D) != null; - args.UseNavigation = _systemsAll.FirstOrDefault(x => x is NavigationSystem) != null; - args.UseCullingArea = _systemsAll.FirstOrDefault(x => x is CullingSystem2D) != null || _systemsAll.FirstOrDefault(x => x is CullingSystem3D) != null; - - // create frame context - _context = new FrameContextUser(args); - } - - return _context; - } - - /// - /// Set the prediction area. - /// - /// Center of the prediction area - /// Radius of the prediction area - /// The Prediction Culling feature must be explicitly enabled in . - /// This can be safely called from the main-thread. - /// Prediction Culling allows developers to save CPU time in games where the player has only a partial view of the game scene. - /// Quantum prediction and rollbacks, which are time consuming, will only run for important entities that are visible to the local player(s). Leaving anything outside that area to be simulated only once per tick with no rollbacks as soon as the inputs are confirmed from server. - /// It is safe and simple to activate and, depending on the game, the performance difference can be quite large.Imagine a 30Hz game to constantly rollback ten ticks for every confirmed input (with more players, the predictor eventually misses at least for one of them). This requires the game simulation to be lightweight to be able to run at almost 300Hz(because of the rollbacks). With Prediction Culling enabled the full frames will be simulated at the expected 30Hz all the time while the much smaller prediction area is the only one running within the prediction buffer. - public void SetPredictionArea(FPVector3 position, FP radius) { - _context.SetPredictionArea(position, radius); - } - - /// - /// See . - /// - /// - /// - public void SetPredictionArea(FPVector2 position, FP radius) { - _context.SetPredictionArea(position.XOY, radius); - } - - public void OnGameEnded() { - InvokeOnGameEnded(); - } - - public void OnGameStart(DeterministicFrame f) { - // init event invoker - InitEventInvoker(Session.RollbackWindow); - - Frames.Predicted = (Frame)f; - Frames.PredictedPrevious = (Frame)f; - Frames.Verified = (Frame)f; - Frames.PreviousUpdatePredicted = (Frame)f; - - InvokeOnGameStart(); - - // init systems on latest frame - InitSystems(f); - - Log.Debug("Local Players: " + string.Join(" ", Session.LocalPlayerIndices)); - } - - public void OnGameResync() { - _checksumSnapshotBuffer?.Clear(); - ReplayToolsOnGameResync(); - - // reset physics engines statics - Frames.Verified.Physics2D.Init(); - Frames.Verified.Physics3D.Init(); - - // events won't get confirmed - CancelPendingEvents(); - - InvokeOnGameResync(); - } - - public DeterministicFrameInputTemp OnLocalInput(Int32 frame, Int32 player) { - var input = default(QTuple); - - // poll input - try { - bool isFirst = _polledInputInThisSimulation == false; - _polledInputInThisSimulation = true; - input = InvokeOnPollInput(frame, player, isFirst); - } catch (Exception exn) { - Log.Error("## Input Code Threw Exception ##"); - Log.Exception(exn); - } - - if (_inputSerializerWrite == null) { - _inputSerializerWrite = new FrameSerializer(DeterministicFrameSerializeMode.Serialize, null, new Byte[1024]); - } - - // clear old data - _inputSerializerWrite.Reset(); - _inputSerializerWrite.Writing = true; - _inputSerializerWrite.InputMode = true; - - // pack into stream - Input.Write(_inputSerializerWrite, input.Item0); - - // return temp input - return DeterministicFrameInputTemp.Predicted(frame, player, _inputSerializerWrite.Stream.Data, _inputSerializerWrite.Stream.BytesRequired, input.Item1); - } - - public void OnSimulate(DeterministicFrame state) { - HostProfiler.Start("QuantumGame.OnSimulate"); - - var f = (Frame)state; - - try { - // reset profiling - HostProfiler.Start("Init Profiler"); - f.Context.ProfilerContext.Reset(); - var profiler = f.Context.ProfilerContext.GetProfilerForTaskThread(0); - HostProfiler.End(); - - HostProfiler.Start("ApplyInputs"); - ApplyInputs(f); - HostProfiler.End(); - - HostProfiler.Start("OnSimulateBegin"); - f.Context.OnFrameSimulationBegin(f); - f.OnFrameSimulateBegin(); - f.Context.TaskContext.BeginFrame(f); - HostProfiler.End(); - - var handle = f.Context.TaskContext.AddRootTask(); - - HostProfiler.Start("UpdatePlayerData"); - f.UpdatePlayerData(); - HostProfiler.End(); - - profiler.Start("Scheduling Tasks #ff9900"); - HostProfiler.Start("Scheduling Tasks"); - - var systems = &f.Global->Systems; - - for (Int32 i = 0; i < _systemsRoot.Length; ++i) { - if (f.SystemIsEnabledSelf(_systemsRoot[i])) { - try { - handle = _systemsRoot[i].OnSchedule(f, handle); - } catch (Exception exn) { - LogSimulationException(exn); - } - } - } - - HostProfiler.End(); - profiler.End(); - - try { - f.Context.TaskContext.EndFrame(); - f.OnFrameSimulateEnd(); - f.Context.OnFrameSimulationEnd(); - } catch (Exception exn) { - Log.Exception(exn); - } - - if (ProfilerSampleGenerated != null) { - var data = f.Context.ProfilerContext.CreateReport(f.Number, f.IsVerified); - ProfilerSampleGenerated(data); - } - -#if PROFILER_FRAME_AVERAGE - f.Context.ProfilerContext.StoreFrameTime(); - Log.Info("Frame Average: " + f.Context.ProfilerContext.GetFrameTimeAverage()); -#endif - } catch (Exception exn) { - LogSimulationException(exn); - } - - HostProfiler.End(); - } - - public void OnSimulateFinished(DeterministicFrame state) { - SnapshotsOnSimulateFinished(state); - InvokeOnSimulateFinished(state); - } - - public void OnUpdateDone() { - Frames.Predicted = (Frame)Session.FramePredicted; - Frames.PredictedPrevious = (Frame)Session.FramePredictedPrevious; - Frames.Verified = (Frame)Session.FrameVerified; - Frames.PreviousUpdatePredicted = (Frame)Session.PreviousUpdateFramePredicted; - - if (Session.IsStalling == false) { - var f = (float)(Session.AccumulatedTime / Frames.Predicted.DeltaTime.AsFloat); - InterpolationFactor = f < 0.0f ? 0.0f : f > 1.0f ? 1.0f : f; // Clamp01 - } - - InvokeOnUpdateView(); - InvokeEvents(); - } - - public void AssignSession(DeterministicSession session) { - Session = session; - - DeterministicSessionConfig sessionConfig; - Session.GetLocalConfigs(out sessionConfig, out _); - - // verify player count is in correct range - if (sessionConfig.PlayerCount < 1 || sessionConfig.PlayerCount > Quantum.Input.MAX_COUNT) { - throw new Exception(String.Format("Invalid player count {0} (needs to be in 1-{1} range)", sessionConfig.PlayerCount, Quantum.Input.MAX_COUNT)); - } - - // verify all types - var verifier = new MemoryLayoutVerifier(MemoryLayoutVerifier.Platform ?? new MemoryLayoutVerifier.DefaultPlatform()); - var result = verifier.Verify(_typeRegistry.Types); - if (result.Count > 0) { - throw new Exception("MemoryIntegrity Check Failed: " + System.Environment.NewLine + String.Join(System.Environment.NewLine, result.ToArray())); - } else { - Log.Info("Memory Integrity Verified"); - } - } - - public void OnChecksumError(DeterministicTickChecksumError error, DeterministicFrame[] frames) { - InvokeOnChecksumError(error, frames); - } - - public void OnChecksumComputed(Int32 frame, ulong checksum) { - InvokeOnChecksumComputed(frame, checksum); - ReplayToolsOnChecksumComputed(frame, checksum); - } - - public void OnSimulationEnd() { - _context.OnSimulationEnd(); - } - - public void OnSimulationBegin() { - _polledInputInThisSimulation = false; - _context.OnSimulationBegin(); - } - - public void OnInputConfirmed(DeterministicFrameInputTemp input) { - InvokeOnInputConfirmed(input); - ReplayToolsOnInputConfirmed(input); - } - - public void OnChecksumErrorFrameDump(int actorId, int frameNumber, DeterministicSessionConfig sessionConfig, byte[] runtimeConfig, byte[] frameData, byte[] extraData) { - InvokeOnChecksumErrorFrameDump(actorId, frameNumber, sessionConfig, runtimeConfig, frameData, extraData); - } - - public void OnPluginDisconnect(string reason) { - Log.Error("DISCONNECTED: " + reason); - InvokeOnPluginDisconnect(reason); - } - - public int GetInputInMemorySize() { - return sizeof(Input); - } - - public Int32 GetInputSerializedFixedSize() { - var stream = new FrameSerializer(DeterministicFrameSerializeMode.Serialize, null, 1024); - stream.Writing = true; - stream.InputMode = true; - Input.Write(stream, new Input()); - return stream.ToArray().Length; - } - - void InitSystems(DeterministicFrame df) { - var f = (Frame)df; - - try { - f.Context.OnFrameSimulationBegin(f); - - // call init on ALL systems - for (Int32 i = 0; i < _systemsAll.Length; ++i) { - try { - _systemsAll[i].OnInit(f); - - if (f.CommitCommandsMode == CommitCommandsModes.InBetweenSystems) { - f.Unsafe.CommitAllCommands(); - } - } catch (Exception exn) { - LogSimulationException(exn); - } - } - - // TODO: this seems like a good place to fire OnMapChanged, - // if we want to do that for the initial map - - // call OnEnabled on all systems which start enabled - for (Int32 i = 0; i < _systemsAll.Length; ++i) { - if (_systemsAll[i].StartEnabled) { - try { - _systemsAll[i].OnEnabled(f); - - if (f.CommitCommandsMode == CommitCommandsModes.InBetweenSystems) { - f.Unsafe.CommitAllCommands(); - } - } catch (Exception exn) { - LogSimulationException(exn); - } - } - } - - f.Context.OnFrameSimulationEnd(); - } catch (Exception e) { - LogSimulationException(e); - } - - // invoke events from OnInit/OnEnabled - InvokeEvents(); - } - - public void DeserializeInputInto(int player, byte[] data, byte* buffer) { - if (_inputSerializerRead == null) { - _inputStreamReadZeroArray = new Byte[1024]; - _inputSerializerRead = new FrameSerializer(DeterministicFrameSerializeMode.Serialize, null, new Byte[1024]); - } - - _inputSerializerRead.Reset(); - _inputSerializerRead.Frame = null; - _inputSerializerRead.Reading = true; - _inputSerializerRead.InputMode = true; - - if (data == null || data.Length == 0) { - _inputSerializerRead.CopyFromArray(_inputStreamReadZeroArray); - } else { - _inputSerializerRead.CopyFromArray(data); - } - - try { - *(Input*)buffer = Input.Read(_inputSerializerRead); - } catch (Exception exn) { - *(Input*)buffer = default; - - // log exception - Log.Error("Received invalid input data from player {0}, could not deserialize.", player); - Log.Exception(exn); - } - } - - void ApplyInputs(Frame f) { - for (Int32 i = 0; i < Session.PlayerCount; i++) { - var raw = f.GetRawInput(i); - if (raw == null) { - Log.Error($"Got null input for player {i}"); - } else { - f.SetPlayerInput(i, *(Input*)raw); - } - } - } - - Boolean ReadInputFromStream(out Input input) { - try { - input = Input.Read(_inputSerializerRead); - return true; - } catch { - input = default(Input); - return false; - } - } - - void LogSimulationException(Exception exn) { - Log.Error("## Simulation Code Threw Exception ##"); - Log.Exception(exn); - } - - public byte[] GetExtraErrorFrameDumpData(DeterministicFrame frame) { - using (var stream = new MemoryStream()) { - using (var writer = new BinaryWriter(stream)) { - var data = new ChecksumErrorFrameDumpContext(this, (Frame)frame); - data.Serialize(this, writer); - } - return stream.ToArray(); - } - } - } -} - -// Game/QuantumGame.ReplayTools.cs - -namespace Quantum { - public partial class QuantumGame { - - public InputProvider RecordedInputs { get; private set; } - public ChecksumFile RecordedChecksums { get; private set; } - - ChecksumFile _checksumsToVerify; - - [Obsolete("No longer needed. Just use File.WriteAllBytes(path, serializer.SerializeAssets(assets))")] - public static void ExportDatabase(IEnumerable assets, IAssetSerializer serializer, string folderPath, int serializationBufferSize, string dbExtension = ".json") { - var filePath = Path.Combine(folderPath, "db" + dbExtension); - File.WriteAllBytes(filePath, serializer.SerializeAssets(assets)); - } - - [Obsolete("Use GetInstantReplaySnapshot(int)")] - public Frame GetRecordedSnapshot(int frame) { - return GetInstantReplaySnapshot(frame); - } - - public Frame GetInstantReplaySnapshot(int frame) { - if (!_instantReplaySnapshotsRecording) { - Log.Error("Can't find any recorded snapshots. Use StartRecordingSnapshots to start recording."); - return null; - } - - var buffer = (_commonSnapshotInterval > 0 ? _checksumSnapshotBuffer : _instantReplaySnapshotBuffer); - Assert.Check(buffer != null); - - var result = buffer.Find(frame, DeterministicFrameSnapshotBufferFindMode.ClosestLessThanOrEqual); - if (result == null) { - result = buffer.Find(frame, DeterministicFrameSnapshotBufferFindMode.Closest); - if (result == null) { - Log.Warn("Unable to find a replay snapshot for frame {0}. No snapshots were saved.", frame); - } else { - Log.Warn("Unable to find a replay snapshot for frame {0} or earlier. The closest match is {1}." + - "Increase the max replay length.", frame, result.Number); - } - } - - return (Frame)result; - } - - public void GetInstantReplaySnapshots(int startFrame, int endFrame, List frames) { - if (!_instantReplaySnapshotsRecording) { - Log.Error("Can't find any recorded snapshots. Use StartRecordingSnapshots to start recording."); - return; - } - - var buffer = (_commonSnapshotInterval > 0 ? _checksumSnapshotBuffer : _instantReplaySnapshotBuffer); - Assert.Check(buffer != null); - - var firstFrame = buffer.Find(startFrame, DeterministicFrameSnapshotBufferFindMode.ClosestLessThanOrEqual); - var minFrameNumber = firstFrame?.Number ?? startFrame; - - foreach (Frame frame in buffer.Data) { - if (frame == null) { - continue; - } - - if (frame.Number >= minFrameNumber && frame.Number <= endFrame) - frames.Add(frame); - } - } - - public ReplayFile CreateSavegame() { - if (Frames.Verified == null) { - Log.Error("Cannot create a savegame. Frames verified not found."); - return null; - } - - return new ReplayFile { - DeterministicConfig = Frames.Verified.SessionConfig, - RuntimeConfig = Frames.Verified.RuntimeConfig, - InputHistory = null, - Length = Frames.Verified.Number, - Frame = Frames.Verified.Serialize(DeterministicFrameSerializeMode.Serialize) - }; - } - - public ReplayFile GetRecordedReplay() { - if (Frames.Verified == null) { - Log.Error("Cannot create a replay. Frames current or verified are not valid, yet."); - return null; - } - - if (RecordedInputs == null) { - Log.Error("Cannot create a replay, because no recorded input was found. Use StartRecordingInput to start recording or setup RecordingFlags."); - return null; - } - - var verifiedFrame = Frames.Verified.Number; - - return new ReplayFile { - DeterministicConfig = Frames.Verified.SessionConfig, - RuntimeConfig = Frames.Verified.RuntimeConfig, - InputHistory = RecordedInputs.ExportToList(verifiedFrame), - Length = verifiedFrame, - InitialFrame = Session.InitialTick, - InitialFrameData = Session.IntitialFrameData, - }; - } - - - private void ReplayToolsOnInputConfirmed(DeterministicFrameInputTemp input) { - if (RecordedInputs == null) - return; - RecordedInputs.OnInputConfirmed(this, input); - } - - private void ReplayToolsOnGameResync() { - _instantReplaySnapshotBuffer?.Clear(); - RecordedInputs?.Clear(Frames.Verified.Number); - RecordedChecksums?.Clear(); - } - - private void ReplayToolsOnChecksumComputed(Int32 frame, ulong checksum) { - if (RecordedChecksums != null) { - RecordedChecksums.RecordChecksum(this, frame, checksum); - } - if (_checksumsToVerify != null) { - _checksumsToVerify.VerifyChecksum(this, frame, checksum); - } - } - - public void StartRecordingInput(Int32? startFrame = null) { - if (Session == null) { - Log.Error("Can't start input recording, because the session is invalid. Wait for the OnGameStart callback."); - return; - } - if (RecordedInputs == null) { - if (startFrame.HasValue) { - RecordedInputs = new InputProvider(Session.SessionConfig.PlayerCount, startFrame.Value, 60 * 60, 0); - } else { - // start frame is the session RollbackWindow - RecordedInputs = new InputProvider(Session.SessionConfig); - } - Log.Info("QuantumGame.ReplayTools: Input recording started"); - } - } - - public void StartRecordingChecksums() { - if (RecordedChecksums == null) { - RecordedChecksums = new ChecksumFile(); - Log.Info("QuantumGame.ReplayTools: Checksum recording started"); - } - } - - public void StartVerifyingChecksums(ChecksumFile checksums) { - if (_checksumsToVerify == null) { - _checksumsToVerify = checksums; - Log.Info("QuantumGame.ReplayTools: Checksum verification started"); - } - } - - public void StartRecordingInstantReplaySnapshots() { - if (_instantReplaySnapshotsRecording) { - return; - } - - if (InstantReplayConfig.LenghtSeconds <= 0 || InstantReplayConfig.SnapshotsPerSecond <= 0) { - Assert.Check(_instantReplaySnapshotBuffer == null); - Assert.Check(_commonSnapshotInterval <= 0); - Log.Error($"Can't start recording replay snapshots with these settings: {InstantReplayConfig}"); - return; - } - - _instantReplaySnapshotsRecording = true; - } - - [Obsolete("Use StartRecordingInstantReplaySnapshots() instead and StartParameters properties instead.")] - public void StartRecordingSnapshots(float bufferSizeSec, int snapshotFrequencyPerSec) { - } - } -} - -// Game/QuantumGame.EventDispatcher.cs - -namespace Quantum { - public partial class QuantumGame { - - Dictionary _eventsTriggered; - Queue _eventsConfirmationQueue; - - - public int EventWaitingForConfirmationCount => _eventsConfirmationQueue.Count; - - void InitEventInvoker(Int32 size) { - // how many events per frame without a resize - const int EventsPerTickHeuristic = 50; - _eventsTriggered = new Dictionary(size * EventsPerTickHeuristic); - _eventsConfirmationQueue = new Queue(size * EventsPerTickHeuristic); - } - - void RaiseEvent(EventBase evnt) { - try { - evnt.Game = this; - _eventDispatcher?.Publish(evnt); - } catch (Exception exn) { - Log.Error("## Event Callback Threw Exception ##"); - Log.Exception(exn); - } - } - - void CancelPendingEvents() { - while (_eventsConfirmationQueue.Count > 0) { - var key = _eventsConfirmationQueue.Dequeue(); - _eventsTriggered.Remove(key); - InvokeOnEvent(key, false); - } - _eventsTriggered.Clear(); - } - - - void InvokeEvents() { - while (_context.Events.Count > 0) { - var head = _context.Events.PopHead(); - try { - if (head.Synced) { - if (Session.IsFrameVerified(head.Tick)) { - RaiseEvent(head); - } - } else { - // calculate hash code - var key = new EventKey(head.Tick, head.Id, head.GetHashCode()); - - // if frame is verified, CONFIRM the event in the temp collection of hashes - bool confirmed = Session.IsFrameVerified(head.Tick); - - // if this was already raised, do nothing - if (!_eventsTriggered.TryGetValue(key, out var alreadyConfirmed)) { - // dont trigger this again - _eventsTriggered.Add(key, confirmed); - // trigger event - RaiseEvent(head); - // enqueue confirmation - _eventsConfirmationQueue.Enqueue(key); - } else if (confirmed && !alreadyConfirmed) { - // confirm this event is definitive... - _eventsTriggered[key] = confirmed; - } - } - } finally { - _context.ReleaseEvent(head); - } - } - - // invoke confirmed/canceled event callbacks - while (_eventsConfirmationQueue.Count > 0) { - - var key = _eventsConfirmationQueue.Peek(); - - // need to wait; this will block confirmations from resimulations to maintain order - if (!Session.IsFrameVerified(key.Tick)) { - Assert.Check(key.Tick <= Session.FrameVerified.Number + Session.RollbackWindow); - break; - } - - var confirmed = _eventsTriggered[key]; - _eventsTriggered.Remove(key); - _eventsConfirmationQueue.Dequeue(); - - InvokeOnEvent(key, confirmed); - } - } - } -} - -// Game/QuantumGameCallbacks.cs - -namespace Quantum { - - public enum CallbackId { - PollInput, - GameStarted, - GameResynced, - GameDestroyed, - UpdateView, - SimulateFinished, - EventCanceled, - EventConfirmed, - ChecksumError, - ChecksumErrorFrameDump, - InputConfirmed, - ChecksumComputed, - PluginDisconnect, - UserCallbackIdStart, - } - - /// - /// Callback called when the simulation queries local input. - /// - public sealed class CallbackPollInput : QuantumGame.CallbackBase { - public new const Int32 ID = (int)CallbackId.PollInput; - internal CallbackPollInput(QuantumGame game) : base(ID, game) { } - - public Int32 Frame; - public Int32 Player; - - public void SetInput(Input input, DeterministicInputFlags flags) { - IsInputSet = true; - Input = input; - Flags = flags; - } - - public void SetInput(QTuple input) { - SetInput(input.Item0, input.Item1); - } - - public bool IsFirstInThisUpdate { get; internal set; } - public bool IsInputSet { get; internal set; } - public Input Input { get; private set; } - public DeterministicInputFlags Flags { get; private set; } - } - - /// - /// Callback called when the game has been started. - /// - public sealed class CallbackGameStarted : QuantumGame.CallbackBase { - public new const Int32 ID = (int)CallbackId.GameStarted; - internal CallbackGameStarted(QuantumGame game) : base(ID, game) { } - } - - /// - /// Callback called when the game has been re-synchronized from a snapshot. - /// - public sealed class CallbackGameResynced : QuantumGame.CallbackBase { - public new const Int32 ID = (int)CallbackId.GameResynced; - internal CallbackGameResynced(QuantumGame game) : base(ID, game) { } - } - - /// - /// Callback called when the game was destroyed. - /// - public sealed class CallbackGameDestroyed : QuantumGame.CallbackBase { - public new const Int32 ID = (int)CallbackId.GameDestroyed; - internal CallbackGameDestroyed(QuantumGame game) : base(ID, game) { } - } - - /// - /// Callback guaranteed to be called every rendered frame. - /// - public sealed class CallbackUpdateView : QuantumGame.CallbackBase { - public new const Int32 ID = (int)CallbackId.UpdateView; - internal CallbackUpdateView(QuantumGame game) : base(ID, game) { } - } - - /// - /// Callback called when frame simulation has completed. - /// - public sealed class CallbackSimulateFinished : QuantumGame.CallbackBase { - public new const Int32 ID = (int)CallbackId.SimulateFinished; - internal CallbackSimulateFinished(QuantumGame game) : base(ID, game) { } - - public Frame Frame; - } - - /// - /// Callback called when an event raised in a predicted frame was canceled in a verified frame due to a roll-back / missed prediction. - /// Synchronised events are only raised on verified frames and thus will never be canceled; this is useful to graciously discard non-sync'ed events in the view. - /// - public sealed class CallbackEventCanceled : QuantumGame.CallbackBase { - public new const Int32 ID = (int)CallbackId.EventCanceled; - internal CallbackEventCanceled(QuantumGame game) : base(ID, game) { } - - public EventKey EventKey; - } - - /// - /// Callback called when an event was confirmed by a verified frame. - /// - public sealed class CallbackEventConfirmed : QuantumGame.CallbackBase { - public new const Int32 ID = (int)CallbackId.EventConfirmed; - internal CallbackEventConfirmed(QuantumGame game) : base(ID, game) { } - - public EventKey EventKey; - } - - /// - /// Callback called on a checksum error. - /// - public sealed class CallbackChecksumError : QuantumGame.CallbackBase { - public new const Int32 ID = (int)CallbackId.ChecksumError; - internal CallbackChecksumError(QuantumGame game) : base(ID, game) { } - - public DeterministicTickChecksumError Error; - internal DeterministicFrame[] _rawFrames; - internal Frame[] _convertedFrame; - - public int FrameCount => Frames.Length; - public Frame GetFrame(int index) => (Frame)Frames[index]; - - public Frame[] Frames { - get { - if (_convertedFrame == null) { - _convertedFrame = new Frame[_rawFrames.Length]; - for (int i = 0; i < _rawFrames.Length; ++i) { - _convertedFrame[i] = (Frame)_rawFrames[i]; - } - } - return _convertedFrame; - } - } - } - - /// - /// Callback called when due to a checksum error a frame is dumped. - /// - public sealed class CallbackChecksumErrorFrameDump : QuantumGame.CallbackBase { - public new const Int32 ID = (int)CallbackId.ChecksumErrorFrameDump; - internal CallbackChecksumErrorFrameDump(QuantumGame game) : base(ID, game) { } - - public Int32 ActorId; - public Int32 FrameNumber; - public Byte[] FrameData; - public Byte[] RuntimeConfigBytes; - public Byte[] ExtraBytes; - public DeterministicSessionConfig SessionConfig; - - private Frame _frameToOverride; - - private Byte[] _overridenFrameData; - private SimulationConfig _overridenSimulationConfig; - private DeterministicSessionConfig _overridenSessionConfig; - private RuntimeConfig _overridenRuntimeConfig; - - private QTuple _frameDump; - private QTuple _frame; - private QTuple _runtimeConfig; - private QTuple _context; - - internal void Clear() { - - try { - if (_overridenRuntimeConfig != null) { - _frameToOverride.RuntimeConfig = _overridenRuntimeConfig; - } - if (_overridenSessionConfig != null) { - _frameToOverride.SessionConfig = _overridenSessionConfig; - } - - if (_overridenSimulationConfig != null) { - _frameToOverride.SimulationConfig = _overridenSimulationConfig; - } - - if (_overridenFrameData != null) { - _frameToOverride.Deserialize(_overridenFrameData); - } - } finally { - _frameToOverride = null; - _overridenFrameData = null; - _overridenSimulationConfig = null; - _overridenSessionConfig = null; - _overridenRuntimeConfig = null; - - _runtimeConfig = default; - _context = default; - _frame = default; - _frameDump = default; - SessionConfig = null; - } - } - - public Frame Frame { - get { - if (!_frame.Item0) { - _frame = QTuple.Create(true, (Frame)null); - if (_frameToOverride != null) { - var originalFrameData = _frameToOverride.Serialize(DeterministicFrameSerializeMode.Serialize); - try { - _frameToOverride.Deserialize(FrameData); - _frame = QTuple.Create(true, _frameToOverride); - _overridenFrameData = originalFrameData; - } catch (System.Exception ex) { - // revert to the old data - Log.Warn($"Failed to deserilize dump frame. The snapshot will appear as raw data.\n{ex}"); - _frameToOverride.Deserialize(originalFrameData); - } - - _overridenRuntimeConfig = _frameToOverride.RuntimeConfig; - _overridenSessionConfig = _frameToOverride.SessionConfig; - _overridenSimulationConfig = _frameToOverride.SimulationConfig; - _frameToOverride.SessionConfig = SessionConfig; - - if (RuntimeConfig != null) { - _frameToOverride.RuntimeConfig = RuntimeConfig; - } - - if (SimulationConfig != null) { - _frameToOverride.SimulationConfig = SimulationConfig; - } - } - } - return _frame.Item1; - } - } - - public string FrameDump { - get { - if (!_frameDump.Item0) { - if (Frame != null) { - int dumpFlags = Frame.DumpFlag_NoHeap | Frame.DumpFlag_NoIsVerified; - if (RuntimeConfig == null) { - dumpFlags |= Frame.DumpFlag_NoRuntimeConfig; - } - if (SimulationConfig == null) { - dumpFlags |= Frame.DumpFlag_NoSimulationConfig; - } - - var options = Game.Configurations.Simulation.ChecksumErrorDumpOptions; - if (options.HasFlag(SimulationConfigChecksumErrorDumpOptions.ReadableDynamicDB)) { - dumpFlags |= Frame.DumpFlag_ReadableDynamicDB; - } - if (options.HasFlag(SimulationConfigChecksumErrorDumpOptions.RawFPValues)) { - dumpFlags |= Frame.DumpFlag_PrintRawValues; - } - if (options.HasFlag(SimulationConfigChecksumErrorDumpOptions.ComponentChecksums)) { - dumpFlags |= Frame.DumpFlag_ComponentChecksums; - } - - _frameDump = QTuple.Create(true, Frame.DumpFrame(dumpFlags)); - - if (Context?.AssetDBChecksums != null) { - var sb = new StringBuilder(); - sb.Append(_frameDump.Item1); - sb.AppendLine(); - sb.AppendLine("# RECEIVED ASSETDB CHECKSUMS"); - foreach (var entry in Context.AssetDBChecksums) { - sb.Append(entry.Item0).Append(": ").Append(entry.Item1).AppendLine(); - } - - _frameDump = QTuple.Create(true, sb.ToString()); - } - } else { - unsafe { - byte[] actualData = FrameData; - bool wasCompressed = false; - try { - actualData = ByteUtils.GZipDecompressBytes(FrameData); - wasCompressed = true; - } catch { } - - fixed (byte* p = actualData) { - var printer = new FramePrinter(); - printer.AddLine($"#### RAW FRAME DUMP (was compressed: {wasCompressed}) ####"); - printer.ScopeBegin(); - UnmanagedUtils.PrintBytesHex(p, FrameData.Length, 32, printer); - printer.ScopeEnd(); - _frameDump = QTuple.Create(true, printer.ToString()); - } - } - } - } - - return _frameDump.Item1; - } - } - - - public RuntimeConfig RuntimeConfig { - get { - if (!_runtimeConfig.Item0) { - try { - _runtimeConfig = QTuple.Create(true, RuntimeConfig.FromByteArray(RuntimeConfigBytes)); - } catch (Exception ex) { - Log.Exception(ex); - _runtimeConfig = QTuple.Create(true, (RuntimeConfig)null); - } - } - return _runtimeConfig.Item1; - } - } - - public SimulationConfig SimulationConfig => Context?.SimulationConfig; - - internal void Init(Frame frame) { - _frameToOverride = frame; - } - - public ChecksumErrorFrameDumpContext Context { - get { - if (!_context.Item0) { - try { - using (var reader = new BinaryReader(new MemoryStream(ExtraBytes))) { - _context = QTuple.Create(true, ChecksumErrorFrameDumpContext.Deserialize(Game, reader)); - } - } catch (Exception ex) { - Log.Exception(ex); - _context = QTuple.Create(true, (ChecksumErrorFrameDumpContext)null); - } - } - return _context.Item1; - } - } - } - - /// - /// Callback when local input was confirmed. - /// - public sealed class CallbackInputConfirmed : QuantumGame.CallbackBase { - public new const Int32 ID = (int)CallbackId.InputConfirmed; - internal CallbackInputConfirmed(QuantumGame game) : base(ID, game) { } - public DeterministicFrameInputTemp Input; - } - - /// - /// Callback called when a checksum has been computed. - /// - public sealed class CallbackChecksumComputed : QuantumGame.CallbackBase { - public new const Int32 ID = (int)CallbackId.ChecksumComputed; - internal CallbackChecksumComputed(QuantumGame game) : base(ID, game) { } - - public Int32 Frame; - public UInt64 Checksum; - } - - /// - /// Callback called when the local client is disconnected by the plugin. - /// - public sealed class CallbackPluginDisconnect : QuantumGame.CallbackBase { - public new const Int32 ID = (int)CallbackId.PluginDisconnect; - internal CallbackPluginDisconnect(QuantumGame game) : base(ID, game) { } - - public string Reason; - } - - partial class QuantumGame { - - public class CallbackBase : Quantum.CallbackBase { - public new QuantumGame Game { - get => (QuantumGame)base.Game; - set => base.Game = value; - } - - public CallbackBase(int id, QuantumGame game) : base(id, game) { - } - - - public static Type GetCallbackType(CallbackId id) { - switch (id) { - case CallbackId.ChecksumComputed: return typeof(CallbackChecksumComputed); - case CallbackId.ChecksumError: return typeof(CallbackChecksumError); - case CallbackId.ChecksumErrorFrameDump: return typeof(CallbackChecksumErrorFrameDump); - case CallbackId.EventCanceled: return typeof(CallbackEventCanceled); - case CallbackId.EventConfirmed: return typeof(CallbackEventConfirmed); - case CallbackId.GameDestroyed: return typeof(CallbackGameDestroyed); - case CallbackId.GameStarted: return typeof(CallbackGameStarted); - case CallbackId.InputConfirmed: return typeof(CallbackInputConfirmed); - case CallbackId.PollInput: return typeof(CallbackPollInput); - case CallbackId.SimulateFinished: return typeof(CallbackSimulateFinished); - case CallbackId.UpdateView: return typeof(CallbackUpdateView); - case CallbackId.PluginDisconnect: return typeof(CallbackPluginDisconnect); - default: throw new ArgumentOutOfRangeException(nameof(id)); - } - } - } - - // callback objects - private CallbackChecksumComputed _callbackChecksumComputed; - private CallbackChecksumError _callbackChecksumError; - private CallbackChecksumErrorFrameDump _callbackChecksumErrorFrameDump; - private CallbackEventCanceled _callbackEventCanceled; - private CallbackEventConfirmed _callbackEventConfirmed; - private CallbackGameDestroyed _callbackGameDestroyed; - private CallbackGameStarted _callbackGameStarted; - private CallbackGameResynced _callbackGameResynced; - private CallbackInputConfirmed _callbackInputConfirmed; - private CallbackPollInput _callbackPollInput; - private CallbackSimulateFinished _callbackSimulateFinished; - private CallbackUpdateView _callbackUpdateView; - private CallbackPluginDisconnect _callbackPluginDisconnect; - - public void InitCallbacks() { - _callbackChecksumComputed = new CallbackChecksumComputed(this); - _callbackChecksumError = new CallbackChecksumError(this); - _callbackChecksumErrorFrameDump = new CallbackChecksumErrorFrameDump(this); - _callbackEventCanceled = new CallbackEventCanceled(this); - _callbackEventConfirmed = new CallbackEventConfirmed(this); - _callbackGameDestroyed = new CallbackGameDestroyed(this); - _callbackGameStarted = new CallbackGameStarted(this); - _callbackGameResynced = new CallbackGameResynced(this); - _callbackInputConfirmed = new CallbackInputConfirmed(this); - _callbackPollInput = new CallbackPollInput(this); - _callbackSimulateFinished = new CallbackSimulateFinished(this); - _callbackUpdateView = new CallbackUpdateView(this); - _callbackPluginDisconnect = new CallbackPluginDisconnect(this); - } - - public void InvokeOnGameEnded() { - // not implemented - } - - public void InvokeOnDestroy() { - try { - _callbackDispatcher?.Publish(_callbackGameDestroyed); - } catch (Exception ex) { - Log.Exception(ex); - - } - } - - void InvokeOnGameStart() { - try { - _callbackDispatcher?.Publish(_callbackGameStarted); - } catch (Exception ex) { - Log.Exception(ex); - } - } - - void InvokeOnGameResync() { - try { - _callbackDispatcher?.Publish(_callbackGameResynced); - } catch (Exception ex) { - Log.Exception(ex); - } - } - - - QTuple InvokeOnPollInput(int frame, int player, bool isFirstInThisUpdate) { - - try { - _callbackPollInput.IsInputSet = false; - _callbackPollInput.Frame = frame; - _callbackPollInput.Player = player; - _callbackPollInput.IsFirstInThisUpdate = isFirstInThisUpdate; - _callbackDispatcher?.Publish(_callbackPollInput); - if (_callbackPollInput.IsInputSet) { - return QTuple.Create(_callbackPollInput.Input, _callbackPollInput.Flags); - } - return default; - } catch (Exception ex) { - Log.Exception(ex); - return default; - } - } - - void InvokeOnUpdateView() { - HostProfiler.Start("QuantumGame.InvokeOnUpdateView"); - try { - _callbackDispatcher?.Publish(_callbackUpdateView); - } catch (Exception ex) { - Log.Exception(ex); - } - HostProfiler.End(); - } - - public void InvokeOnSimulateFinished(DeterministicFrame state) { - HostProfiler.Start("QuantumGame.InvokeOnSimulateFinished"); - try { - _callbackSimulateFinished.Frame = (Frame)state; - _callbackDispatcher?.Publish(_callbackSimulateFinished); - } catch (Exception ex) { - Log.Exception(ex); - } - - _callbackSimulateFinished.Frame = null; - HostProfiler.End(); - } - - public void InvokeOnChecksumError(DeterministicTickChecksumError error, DeterministicFrame[] frames) { - try { - _callbackChecksumError.Error = error; - _callbackChecksumError._rawFrames = frames; - _callbackChecksumError._convertedFrame = null; - try { - _callbackDispatcher?.Publish(_callbackChecksumError); - } finally { - _callbackChecksumError._rawFrames = null; - _callbackChecksumError._convertedFrame = null; - } - } catch (Exception ex) { - Log.Exception(ex); - } - } - - public void InvokeOnChecksumComputed(Int32 frame, ulong checksum) { - try { - _callbackChecksumComputed.Frame = frame; - _callbackChecksumComputed.Checksum = checksum; - _callbackDispatcher?.Publish(_callbackChecksumComputed); - } catch (Exception ex) { - Log.Exception(ex); - } - } - - public void InvokeOnInputConfirmed(DeterministicFrameInputTemp input) { - try { - _callbackInputConfirmed.Input = input; - try { - _callbackDispatcher?.Publish(_callbackInputConfirmed); - } finally { - _callbackInputConfirmed.Input = default; - } - } catch (Exception ex) { - Log.Exception(ex); - } - } - - public void InvokeOnChecksumErrorFrameDump(Int32 actorId, Int32 frameNumber, DeterministicSessionConfig sessionConfig, byte[] runtimeConfig, byte[] frameData, byte[] extraData) { - HostProfiler.Start("QuantumGame.InvokeOnChecksumErrorFrameDump"); - try { - - // find the frame that's going to be overwritten: - Frame frameToOverwrite = null; - - if (_checksumSnapshotBuffer?.Capacity > 0) { - if (_checksumSnapshotBuffer.Count == 0) { - _checksumSnapshotBuffer.PushBack(Frames.Verified, this, _context); - } - frameToOverwrite = (Frame)_checksumSnapshotBuffer.PeekBack(); - } else { - // TODO: use replay buffer maybe? or one of predicted? - Log.Warn("Unable to acquire a frame to decode the snapshot. The snapshot will appear as raw binary data. Increase ChecksumFrameBufferSize."); - } - - try { - _callbackChecksumErrorFrameDump.Init(frameToOverwrite); - _callbackChecksumErrorFrameDump.ActorId = actorId; - _callbackChecksumErrorFrameDump.FrameNumber = frameNumber; - _callbackChecksumErrorFrameDump.FrameData = frameData; - _callbackChecksumErrorFrameDump.SessionConfig = sessionConfig; - _callbackChecksumErrorFrameDump.RuntimeConfigBytes = runtimeConfig; - _callbackChecksumErrorFrameDump.ExtraBytes = extraData; - - _callbackDispatcher?.Publish(_callbackChecksumErrorFrameDump); - - } finally { - _callbackChecksumErrorFrameDump.Clear(); - } - } catch (Exception ex) { - HostProfiler.End(); - Log.Exception(ex); - } - } - - private void InvokeOnEvent(EventKey key, bool confirmed) { - try { - if (confirmed) { - _callbackEventConfirmed.EventKey = key; - _callbackDispatcher?.Publish(_callbackEventConfirmed); - } else { - // call event cancelation, passing: game (this), frame (f), event hash... - // also pass the index from eventCollection (trhis is the event type ID); - _callbackEventCanceled.EventKey = key; - _callbackDispatcher?.Publish(_callbackEventCanceled); - } - } catch (Exception ex) { - Log.Exception(ex); - } - } - - public void InvokeOnPluginDisconnect(string reason) { - try { - _callbackPluginDisconnect.Reason = reason; - _callbackDispatcher?.Publish(_callbackPluginDisconnect); - } catch (Exception ex) { - Log.Exception(ex); - } - } - } -} - -// Replay/DotNetTaskRunner.cs - -namespace Quantum { - public class DotNetTaskRunner : IDeterministicPlatformTaskRunner { - int _length; - bool[] _done = new bool[128]; - public void Schedule(Action[] delegates) { - // store how many we're executing - _length = delegates.Length; - // clear current state - Array.Clear(_done, 0, _done.Length); - // barrier this - Thread.MemoryBarrier(); - // queue work - for (int i = 0; i < delegates.Length; ++i) { - ThreadPool.QueueUserWorkItem(Wrap(i, delegates[i])); - } - } - public void WaitForComplete() { - throw new NotImplementedException(); - } - public bool PollForComplete() { - for (int i = 0; i < _length; ++i) { - if (Volatile.Read(ref _done[i]) == false) { - return false; - } - } - return true; - } - WaitCallback Wrap(int index, Action callback) { - return _ => { - try { - Photon.Deterministic.Assert.Check(Volatile.Read(ref _done[index]) == false); - callback(); - } catch (Exception exn) { - Log.Exception(exn); - } finally { - Volatile.Write(ref _done[index], true); - } - }; - } - } -} - -// Replay/FlatEntityPrototypeContainer.cs - -namespace Quantum.Prototypes { - [Serializable] - public partial class FlatEntityPrototypeContainer { - [ArrayLength(0, 1)] public List CharacterController2D; - [ArrayLength(0, 1)] public List CharacterController3D; - [ArrayLength(0, 1)] public List NavMeshAvoidanceAgent; - [ArrayLength(0, 1)] public List NavMeshAvoidanceObstacle; - [ArrayLength(0, 1)] public List NavMeshPathfinder; - [ArrayLength(0, 1)] public List NavMeshSteeringAgent; - [ArrayLength(0, 1)] public List PhysicsBody2D; - [ArrayLength(0, 1)] public List PhysicsBody3D; - [ArrayLength(0, 1)] public List PhysicsCollider2D; - [ArrayLength(0, 1)] public List PhysicsCollider3D; - [ArrayLength(0, 1)] public List PhysicsCallbacks2D; - [ArrayLength(0, 1)] public List PhysicsCallbacks3D; - [ArrayLength(0, 1)] public List Transform2D; - [ArrayLength(0, 1)] public List Transform2DVertical; - [ArrayLength(0, 1)] public List Transform3D; - [ArrayLength(0, 1)] public List View; - [ArrayLength(0, 1)] public List PhysicsJoints2D; - [ArrayLength(0, 1)] public List PhysicsJoints3D; - - public void Collect(List target) { - Collect(CharacterController2D, target); - Collect(CharacterController3D, target); - Collect(NavMeshAvoidanceAgent, target); - Collect(NavMeshAvoidanceObstacle, target); - Collect(NavMeshPathfinder, target); - Collect(NavMeshSteeringAgent, target); - Collect(PhysicsBody2D, target); - Collect(PhysicsBody3D, target); - Collect(PhysicsCollider2D, target); - Collect(PhysicsCollider3D, target); - Collect(PhysicsCallbacks2D, target); - Collect(PhysicsCallbacks3D, target); - Collect(Transform2D, target); - Collect(Transform2DVertical, target); - Collect(Transform3D, target); - Collect(View, target); - Collect(PhysicsJoints2D, target); - Collect(PhysicsJoints3D, target); - CollectGen(target); - } - - public void Store(IList prototypes) { - var visitor = new FlatEntityPrototypeContainer.StoreVisitor() { - Storage = this - }; - - foreach (var prototype in prototypes) { - prototype.Dispatch(visitor); - } - } - - public unsafe partial class StoreVisitor : ComponentPrototypeVisitor { - public FlatEntityPrototypeContainer Storage; - public override void Visit(CharacterController2D_Prototype prototype) { - Storage.Store(prototype, ref Storage.CharacterController2D); - } - public override void Visit(CharacterController3D_Prototype prototype) { - Storage.Store(prototype, ref Storage.CharacterController3D); - } - public override void Visit(NavMeshAvoidanceAgent_Prototype prototype) { - Storage.Store(prototype, ref Storage.NavMeshAvoidanceAgent); - } - public override void Visit(NavMeshAvoidanceObstacle_Prototype prototype) { - Storage.Store(prototype, ref Storage.NavMeshAvoidanceObstacle); - } - public override void Visit(NavMeshPathfinder_Prototype prototype) { - Storage.Store(prototype, ref Storage.NavMeshPathfinder); - } - public override void Visit(NavMeshSteeringAgent_Prototype prototype) { - Storage.Store(prototype, ref Storage.NavMeshSteeringAgent); - } - public override void Visit(PhysicsBody2D_Prototype prototype) { - Storage.Store(prototype, ref Storage.PhysicsBody2D); - } - public override void Visit(PhysicsBody3D_Prototype prototype) { - Storage.Store(prototype, ref Storage.PhysicsBody3D); - } - public override void Visit(PhysicsCollider2D_Prototype prototype) { - Storage.Store(prototype, ref Storage.PhysicsCollider2D); - } - public override void Visit(PhysicsCollider3D_Prototype prototype) { - Storage.Store(prototype, ref Storage.PhysicsCollider3D); - } - public override void Visit(PhysicsCallbacks2D_Prototype prototype) { - Storage.Store(prototype, ref Storage.PhysicsCallbacks2D); - } - public override void Visit(PhysicsCallbacks3D_Prototype prototype) { - Storage.Store(prototype, ref Storage.PhysicsCallbacks3D); - } - public override void Visit(Transform2D_Prototype prototype) { - Storage.Store(prototype, ref Storage.Transform2D); - } - public override void Visit(Transform2DVertical_Prototype prototype) { - Storage.Store(prototype, ref Storage.Transform2DVertical); - } - public override void Visit(Transform3D_Prototype prototype) { - Storage.Store(prototype, ref Storage.Transform3D); - } - public override void Visit(View_Prototype prototype) { - Storage.Store(prototype, ref Storage.View); - } - public override void Visit(PhysicsJoints2D_Prototype prototype) { - Storage.Store(prototype, ref Storage.PhysicsJoints2D); - } - - public override void Visit(PhysicsJoints3D_Prototype prototype) { - Storage.Store(prototype, ref Storage.PhysicsJoints3D); - } - } - - partial void CollectGen(List target); - - private void Collect(List source, List destination) where TPrototype : ComponentPrototype { - if (source == null) - return; - for (int i = 0; i < source.Count; ++i) - destination.Add(source[i]); - } - - private void Store(T value, ref List destination) { - Assert.Check(value.GetType() == typeof(T)); - if (destination == null) - destination = new List(1); - destination.Add(value); - } - } -} - - -// Replay/InactiveTaskRunner.cs - -namespace Quantum { - public class InactiveTaskRunner : IDeterministicPlatformTaskRunner { - public void Schedule(Action[] delegates) { } - - public void WaitForComplete() { } - - public bool PollForComplete() { - return true; - } - } -} - -// Replay/JsonAssetSerializerBase.cs - -namespace Quantum { - public abstract class JsonAssetSerializerBase : IAssetSerializer { - private List _prototypeBuffer = new List(); - - public bool IsPrettyPrintEnabled { get; set; } = false; - - /// - /// If set to a positive value, all uncompressed BinaryData assets with size over the value will be compressed - /// during serialization. - /// - public int CompressBinaryDataOnSerializationThreshold { get; set; } = 1024; - - /// - /// If true, all compressed BinaryData assets will be decompressed during deserialization. - /// - public bool DecompressBinaryDataOnDeserialization { get; set; } - - public Encoding Encoding => Encoding.UTF8; - - public byte[] SerializeReplay(ReplayFile replay) { - var json = ToJson(replay); - return Encoding.GetBytes(json); - } - - public ReplayFile DeserializeReplay(byte[] data) { - var json = Encoding.GetString(data); - return (ReplayFile)FromJson(json, typeof(ReplayFile)); - } - - public byte[] SerializeChecksum(ChecksumFile checksums) { - var json = ToJson(checksums); - return Encoding.GetBytes(json); - } - - public ChecksumFile DeserializeChecksum(byte[] data) { - var json = Encoding.GetString(data); - return (ChecksumFile)FromJson(json, typeof(ChecksumFile)); - } - - public byte[] SerializeAssets(IEnumerable assets) { - FlatDatabaseFile db = new FlatDatabaseFile(); - List userAssets = new List(); - - var visitor = new AssetVisitor() { - Storage = db, - Serializer = this - }; - - Assembly executingAssembly = typeof(JsonAssetSerializerBase).Assembly; - - foreach (var asset in assets) { - if (asset is IBuiltInAssetObject builtInAsset) { - builtInAsset.Dispatch(visitor); - } else { - var assetType = asset.GetType(); - var surrogate = new UserAssetSurrogate() { - Type = (assetType.Assembly == executingAssembly) ? assetType.FullName : assetType.AssemblyQualifiedName, - Json = ToJson(asset), - }; - userAssets.Add(surrogate); - } - } - - db.UserAssets = userAssets; - - var json = ToJson(db); - return Encoding.GetBytes(json); - } - - - public string PrintAsset(AssetObject asset) { - - object objectToSerialize = asset; - if ( asset is EntityPrototype ep ) { - objectToSerialize = CreateSurrogate(ep); - } else if ( asset is Map map) { - objectToSerialize = CreateSurrogate(map); - } - - return ToJson(objectToSerialize); - } - - public IEnumerable DeserializeAssets(byte[] data) => IAssetSerializerExtensions.DeserializeAssets(this, data); - - public IEnumerable DeserializeAssets(byte[] data, int index, int count) { - string json = Encoding.UTF8.GetString(data, index, count); - var db = (FlatDatabaseFile)FromJson(json, typeof(FlatDatabaseFile)); - - List result = new List(); - Collect(db.CharacterController2DConfig, result); - Collect(db.CharacterController3DConfig, result); - Collect(db.EntityView, result); - Collect(db.NavMesh, result); - Collect(db.NavMeshAgentConfig, result); - Collect(db.PhysicsMaterial, result); - Collect(db.PolygonCollider, result); - Collect(db.TerrainCollider, result); - - if (db.EntityPrototype != null) { - foreach (var surrogate in db.EntityPrototype) { - result.Add(CreateFromSurrogate(surrogate)); - } - } - - if (db.Map != null) { - foreach (var surrogate in db.Map) { - result.Add(CreateFromSurrogate(surrogate)); - } - } - - if (db.BinaryData != null) { - foreach (var surrogate in db.BinaryData) { - result.Add(CreateFromSurrogate(surrogate)); - } - } - - if (db.UserAssets != null) { - foreach (var surrogate in db.UserAssets) { - var type = Type.GetType(surrogate.Type, true); - var asset = (AssetObject)FromJson(surrogate.Json, type); - result.Add(asset); - } - } - - return result; - } - - protected void Collect(List source, List destination) where AssetType : AssetObject { - if (source == null) - return; - for (int i = 0; i < source.Count; ++i) - destination.Add(source[i]); - } - - protected abstract object FromJson(string json, Type type); - - protected abstract string ToJson(object obj); - - private static EntityPrototypeSurrogate CreateSurrogate(EntityPrototype asset) { - var visitor = new FlatEntityPrototypeContainer.StoreVisitor() { - Storage = new FlatEntityPrototypeContainer() - }; - - foreach (var prototype in asset.Container.Components) { - prototype.Dispatch(visitor); - } - - return new EntityPrototypeSurrogate() { - Identifier = asset.Identifier, - Container = visitor.Storage - }; - } - - private static MapSurrogate CreateSurrogate(Map asset) { - var mapEntities = new FlatEntityPrototypeContainer[asset.MapEntities.Length]; - - var visitor = new FlatEntityPrototypeContainer.StoreVisitor(); - - for (int i = 0; i < mapEntities.Length; ++i) { - visitor.Storage = mapEntities[i] = new FlatEntityPrototypeContainer(); - foreach (var prototype in asset.MapEntities[i].Components) { - prototype.Dispatch(visitor); - } - } - - return new MapSurrogate() { - Map = asset, - MapEntities = mapEntities, - }; - } - - - - private EntityPrototype CreateFromSurrogate(EntityPrototypeSurrogate surrogate) { - try { - Assert.Check(_prototypeBuffer.Count == 0); - surrogate.Container.Collect(_prototypeBuffer); - return new EntityPrototype() { - Identifier = surrogate.Identifier, - Container = new EntityPrototypeContainer() { - Components = _prototypeBuffer.ToArray() - } - }; - } finally { - _prototypeBuffer.Clear(); - } - } - - private Map CreateFromSurrogate(MapSurrogate surrogate) { - Assert.Check(_prototypeBuffer.Count == 0); - - var entityCount = surrogate.MapEntities.Length; - var mapEntities = new EntityPrototypeContainer[entityCount]; - - for (int i = 0; i < entityCount; ++i) { - try { - surrogate.MapEntities[i].Collect(_prototypeBuffer); - mapEntities[i] = new EntityPrototypeContainer() { - Components = _prototypeBuffer.ToArray() - }; - } finally { - _prototypeBuffer.Clear(); - } - } - - var map = surrogate.Map; - map.MapEntities = mapEntities; - return map; - } - - private BinaryDataSurrogate CreateSurrogate(BinaryData asset) { - - byte[] data = asset.Data ?? Array.Empty(); - bool isCompressed = asset.IsCompressed; - - if (!asset.IsCompressed && CompressBinaryDataOnSerializationThreshold > 0 && data.Length >= CompressBinaryDataOnSerializationThreshold) { - data = ByteUtils.GZipCompressBytes(data); - isCompressed = true; - } - - return new BinaryDataSurrogate() { - Identifier = asset.Identifier, - Base64Data = ByteUtils.Base64Encode(data), - IsCompressed = isCompressed, - }; - } - - private BinaryData CreateFromSurrogate(BinaryDataSurrogate surrogate) { - - var result = new BinaryData() { - Identifier = surrogate.Identifier, - Data = ByteUtils.Base64Decode(surrogate.Base64Data), - IsCompressed = surrogate.IsCompressed, - }; - - if (surrogate.IsCompressed && DecompressBinaryDataOnDeserialization) { - result.IsCompressed = false; - result.Data = ByteUtils.GZipDecompressBytes(result.Data); - } - - return result; - } - - [Serializable] - public class EntityPrototypeSurrogate { - public FlatEntityPrototypeContainer Container; - public AssetObjectIdentifier Identifier; - } - - [Serializable] - public class MapSurrogate { - public Map Map; - public FlatEntityPrototypeContainer[] MapEntities; - } - - [Serializable] - public class UserAssetSurrogate { - public string Json; - public string Type; - } - - [Serializable] - public class BinaryDataSurrogate { - public AssetObjectIdentifier Identifier; - public bool IsCompressed; - public string Base64Data; - } - - private class AssetVisitor : IAssetObjectVisitor { - public FlatDatabaseFile Storage; - public JsonAssetSerializerBase Serializer; - - void IAssetObjectVisitor.Visit(BinaryData asset) { - Storage.BinaryData.Add(Serializer.CreateSurrogate(asset)); - } - - void IAssetObjectVisitor.Visit(CharacterController2DConfig asset) { - Storage.CharacterController2DConfig.Add(asset); - } - - void IAssetObjectVisitor.Visit(CharacterController3DConfig asset) { - Storage.CharacterController3DConfig.Add(asset); - } - - void IAssetObjectVisitor.Visit(EntityPrototype asset) { - Storage.EntityPrototype.Add(CreateSurrogate(asset)); - } - - void IAssetObjectVisitor.Visit(EntityView asset) { - Storage.EntityView.Add(asset); - } - - void IAssetObjectVisitor.Visit(Map asset) { - Storage.Map.Add(CreateSurrogate(asset)); - } - - void IAssetObjectVisitor.Visit(NavMesh asset) { - Storage.NavMesh.Add(asset); - } - - void IAssetObjectVisitor.Visit(NavMeshAgentConfig asset) { - Storage.NavMeshAgentConfig.Add(asset); - } - - void IAssetObjectVisitor.Visit(PhysicsMaterial asset) { - Storage.PhysicsMaterial.Add(asset); - } - - void IAssetObjectVisitor.Visit(PolygonCollider asset) { - Storage.PolygonCollider.Add(asset); - } - - void IAssetObjectVisitor.Visit(TerrainCollider asset) { - Storage.TerrainCollider.Add(asset); - } - } - - [Serializable] - private sealed class FlatDatabaseFile { - public List CharacterController2DConfig = new List(); - public List CharacterController3DConfig = new List(); - public List EntityPrototype = new List(); - public List EntityView = new List(); - public List Map = new List(); - public List NavMesh = new List(); - public List NavMeshAgentConfig = new List(); - public List PhysicsMaterial = new List(); - public List PolygonCollider = new List(); - public List TerrainCollider = new List(); - public List UserAssets = new List(); - public List BinaryData = new List(); - } - } -} - -// Replay/ChecksumFile.cs - -namespace Quantum { - - [Serializable] - public class ChecksumFile { - public const int GrowSize = 60 * 60; // one minute of recording at 60 FPS - - [Serializable] - public struct ChecksumEntry { - public int Frame; - // This is super annoying: Unity JSON cannot read the unsigned long data type. - // We can convert on this level, keeping the ULong CalculateChecksum() signature and encode the - // checksum as a long for serialization. Any other ideas? - public long ChecksumAsLong; - } - - public ChecksumEntry[] Checksums; - - private Int32 writeIndex; - - public Dictionary ToDictionary() { - return Checksums.Where(item => item.Frame != 0).ToDictionary(item => item.Frame, item => item); - } - - internal void RecordChecksum(QuantumGame game, Int32 frame, ulong checksum) { - if (Checksums == null) { - Checksums = new ChecksumEntry[GrowSize]; - } - - if (writeIndex + 1 > Checksums.Length) { - Array.Resize(ref Checksums, Checksums.Length + GrowSize); - } - - Checksums[writeIndex].Frame = frame; - Checksums[writeIndex].ChecksumAsLong = ChecksumFileHelper.UlongToLong(checksum); - writeIndex++; - } - - internal void VerifyChecksum(QuantumGame game, Int32 frame, ulong checksum) { - if (Checksums.Length > 0) { - var readIndex = (frame - Checksums[0].Frame) / game.Session.SessionConfig.ChecksumInterval; - Assert.Check(Checksums[readIndex].Frame == frame); - if (Checksums[readIndex].ChecksumAsLong != ChecksumFileHelper.UlongToLong(checksum)) { - Log.Error($"Checksum mismatch in frame {frame}: {Checksums[readIndex].ChecksumAsLong} != {ChecksumFileHelper.UlongToLong(checksum)}"); - } - } - } - - internal void Clear() { - writeIndex = 0; - if ( Checksums != null ) { - for (int i = 0; i < Checksums.Length; ++i) { - Checksums[i] = default; - } - } - } - } - - public static class ChecksumFileHelper { - public static unsafe long UlongToLong(ulong value) { - return *((long*)&value); - } - - public static unsafe ulong LongToULong(long value) { - return *((ulong*)&value); - } - } -} - - -// Replay/InputProvider.cs - -namespace Quantum { - public class InputProvider : IDeterministicReplayProvider { - private int _playerCount; - private int _growSize; - private int _startFrame; - private DeterministicTickInputSet[] _inputs; - - private int MaxFrame => _inputs.Length + _startFrame; - - public InputProvider(DeterministicSessionConfig config, int capacity = 60 * 60, int growSize = 0) : this(config.PlayerCount, config.RollbackWindow, capacity, growSize) { - } - - [Obsolete("Use 'InputProvider(DeterministicTickInputSet[])' instead.")] - public InputProvider(DeterministicSessionConfig config, DeterministicTickInputSet[] inputList) : this(inputList) {} - - public InputProvider(DeterministicTickInputSet[] inputList) { - ImportFromList(inputList); - } - - public InputProvider(int playerCount, int startFrame, int capacity, int growSize) { - _playerCount = playerCount; - _startFrame = startFrame; - _growSize = growSize; - - if (capacity > 0) { - Allocate(capacity); - } - } - - public void Clear(int startFrame) { - _startFrame = startFrame; - for (int i = 0; i < _inputs.Length; i++) { - _inputs[i].Tick = i + _startFrame; - for (int j = 0; j < _playerCount; j++) { - _inputs[i].Inputs[j].Clear(); - } - } - } - - [Obsolete("Use 'ImportFromList(DeterministicTickInputSet[])' instead.")] - public void ImportFromList(DeterministicTickInputSet[] inputList, int startFrame) { - // all InputProvider features expect the first input on the list to be the starting frame, - // setting it to a different number will result in misbehavior - ImportFromList(inputList); - } - - public void ImportFromList(DeterministicTickInputSet[] inputList) { - _startFrame = inputList.Length == 0 ? 0 : inputList[0].Tick; - - // Use external list as our own - _inputs = inputList; - for (int i = 0; i < _inputs.Length; i++) { - for (int j = 0; j < inputList[i].Inputs.Length; j++) { - inputList[i].Inputs[j].Sent = true; - } - } - } - - public DeterministicTickInputSet[] ExportToList(int verifiedFrame) { - var size = _inputs.Length; - while (size > 0 && _inputs[size - 1].Inputs.Any(x => x.Tick == 0 || x.Tick > verifiedFrame)) { - // Truncate non-verified and incomplete input from the end - size--; - } - - if (size <= 0) { - return new DeterministicTickInputSet[0]; - } - - var result = new DeterministicTickInputSet[size]; - Array.Copy(_inputs, result, size); - - return result; - } - - public void OnInputConfirmed(QuantumGame game, DeterministicFrameInputTemp input) { - if (input.Frame < _startFrame) { - // if starting to record from a frame following a snapshot, - // confirmed inputs from previous frames can still arrive - return; - } - - if (input.Frame >= MaxFrame) { - var minSize = Math.Max(input.Frame - _startFrame, _inputs.Length); - var growSize = _growSize > 0 ? minSize + _growSize : minSize * 2; - Allocate(growSize); - } - - _inputs[ToIndex(input.Frame)].Inputs[input.Player].Set(input); - } - - public void InjectInput(DeterministicTickInput input, bool localReplay) { - if (input.Tick >= MaxFrame) { - var minSize = Math.Max(input.Tick - _startFrame, _inputs.Length); - var growSize = _growSize > 0 ? minSize + _growSize : minSize * 2; - Allocate(growSize); - } - - _inputs[ToIndex(input.Tick)].Inputs[input.PlayerIndex].CopyFrom(input); - - if (localReplay) { - _inputs[ToIndex(input.Tick)].Inputs[input.PlayerIndex].Sent = true; - } - } - - public void AddRpc(int player, byte[] data, bool command) { - } - - public bool CanSimulate(int frame) { - var index = ToIndex(frame); - - if (index >= 0 && index < _inputs.Length) { - return _inputs[index].IsComplete(); - } - - return false; - } - - public QTuple GetRpc(int frame, int player) { - if (frame < MaxFrame) { - return QTuple.Create( - _inputs[ToIndex(frame)].Inputs[player].Rpc, - (_inputs[ToIndex(frame)].Inputs[player].Flags & DeterministicInputFlags.Command) == DeterministicInputFlags.Command); - } - - return default; - } - - public DeterministicFrameInputTemp GetInput(int frame, int player) { - if (frame < MaxFrame) { - var input = _inputs[ToIndex(frame)].Inputs[player]; - return DeterministicFrameInputTemp.Verified(frame, player, null, input.DataArray, input.DataLength, input.Flags); - } - - return default; - } - - private int ToIndex(int frame) { - return frame - _startFrame; - } - - private void Allocate(int size) { - var oldSize = 0; - if (_inputs == null) { - _inputs = new DeterministicTickInputSet[size]; - } else { - oldSize = _inputs.Length; - Array.Resize(ref _inputs, size); - } - - for (int i = oldSize; i < _inputs.Length; i++) { - _inputs[i].Tick = i + _startFrame; - _inputs[i].Inputs = new DeterministicTickInput[_playerCount]; - for (int j = 0; j < _playerCount; j++) { - _inputs[i].Inputs[j] = new DeterministicTickInput(); - } - } - } - } - - public static class InputProviderExtensions { - public static void CopyFrom(this DeterministicTickInput input, DeterministicTickInput otherInput) { - input.Sent = otherInput.Sent; - input.Tick = otherInput.Tick; - input.PlayerIndex = otherInput.PlayerIndex; - input.DataLength = otherInput.DataLength; - input.Flags = otherInput.Flags; - - if (otherInput.DataArray != null) { - input.DataArray = new byte[otherInput.DataArray.Length]; - Array.Copy(otherInput.DataArray, input.DataArray, otherInput.DataArray.Length); - } - - if (otherInput.Rpc != null) { - input.Rpc = new byte[otherInput.Rpc.Length]; - Array.Copy(otherInput.Rpc, input.Rpc, otherInput.Rpc.Length); - } - } - - public static void Clear(this DeterministicTickInput input) { - input.Tick = default; - input.PlayerIndex = default; - input.DataArray = default; - input.DataLength = default; - input.Flags = default; - input.Rpc = default; - } - - - - public static void Set(this DeterministicTickInput input, DeterministicFrameInputTemp temp) { - input.Tick = temp.Frame; - input.PlayerIndex = temp.Player; - input.DataArray = temp.CloneData(); - input.DataLength = temp.DataLength; - input.Flags = temp.Flags; - input.Rpc = temp.Rpc; - } - - public static bool IsComplete(this DeterministicTickInputSet set) { - for (int i = 0; i < set.Inputs.Length; i++) { - if (set.Inputs[i].Tick == 0) { - return false; - } - } - - return true; - } - - public static bool IsFinished(this DeterministicTickInputSet set) { - for (int i = 0; i < set.Inputs.Length; i++) { - if (set.Inputs[i].Tick == 0 || - set.Inputs[i].Sent == false) { - return false; - } - } - - return true; - } - } -} - -// Replay/ReplayFile.cs - -namespace Quantum { - - [Serializable] - public class ReplayFile { - public RuntimeConfig RuntimeConfig; - public DeterministicSessionConfig DeterministicConfig; - public DeterministicTickInputSet[] InputHistory; - public Int32 Length; - public byte[] Frame; - public Int32 InitialFrame; - public byte[] InitialFrameData; - } -} - - -// Replay/SessionContainer.cs - -namespace Quantum { - public class SessionContainer { - public static Boolean _loadedAllStatics = false; - public static readonly Object _lock = new Object(); - - DeterministicSessionConfig _sessionConfig; - RuntimeConfig _runtimeConfig; - QuantumGame _game; - DeterministicSession _session; - long _startGameTimeoutInMiliseconds = -1; - DateTime _startGameTimestamp; - - public QuantumGame QuantumGame => _game; - public IDeterministicGame Game => _game; - public DeterministicSession Session => _session; - public RuntimeConfig RuntimeConfig => _runtimeConfig; - public DeterministicSessionConfig DeterministicConfig => _sessionConfig; - - /// - /// Check this when the container reconnects into a running game and handle accordingly. - /// - public bool HasGameStartTimedOut => _startGameTimeoutInMiliseconds > 0 && Session != null && Session.IsPaused && DateTime.Now > _startGameTimestamp + TimeSpan.FromMilliseconds(_startGameTimeoutInMiliseconds); - /// - /// Default is infinity (-1). Set this when the you expect to connect to a running game and wait for a snapshot. - /// - public long GameStartTimeoutInMiliseconds { - get { - return _startGameTimeoutInMiliseconds; - } - set { - _startGameTimeoutInMiliseconds = value; - } - } - - #region Obsolete Members - - [Obsolete("Renamed to Session")] - public DeterministicSession session; - [Obsolete("Renamed to SessionConfig")] - public DeterministicSessionConfig config => _sessionConfig; - [Obsolete("Renamed to RuntimeConfig")] - public RuntimeConfig runtimeConfig => _runtimeConfig; - [Obsolete("Removed allocator access because it being disposed internally and unsafe to use outside")] - public Native.Allocator allocator => null; - [Obsolete("Removed property, use StartReplay(.., IDeterministicReplayProvider provider, ..)")] - public IDeterministicReplayProvider provider; - [Obsolete("Renamed to Game, changed to getter only, set by Start(QuantumGame.StartParameters startParams)")] - public IDeterministicGame game => _game; - [Obsolete("Removed property, set by Start(QuantumGame.StartParameters startParams)")] - public IResourceManager resourceManager; - [Obsolete("Removed property, set by using constructor SessionContainer(ReplayFile)")] - public ReplayFile replayFile; - [Obsolete("Removed property, set by Start(QuantumGame.StartParameters startParams)")] - public IAssetSerializer assetSerializer; - [Obsolete("Removed property, set by Start(QuantumGame.StartParameters startParams)")] - public IEventDispatcher eventDispatcher; - [Obsolete("Removed property, set by Start(QuantumGame.StartParameters startParams)")] - public ICallbackDispatcher callbackDispatcher; - [Obsolete("Removed property, set by Start(QuantumGame.StartParameters startParams)")] - public int gameFlags; - - #endregion - - public static Native.Allocator CreateNativeAllocator() { - switch (Environment.OSVersion.Platform) { - case PlatformID.Unix: - case PlatformID.MacOSX: - return new Native.LIBCAllocator(); - default: - return new Native.MSVCRTAllocator(); - } - } - - public static Native.Utility CreateNativeUtils() { - switch (Environment.OSVersion.Platform) { - case PlatformID.Unix: - case PlatformID.MacOSX: - return new Native.LIBCUtility(); - - default: - return new Native.MSVCRTUtility(); - } - } - - [Obsolete("Use Start(QuantumGame.StartParameters startParams, IDeterministicReplayProvider provider) instead of setting properties")] - public void Start(bool logInitForConsole = true) { - if (provider == null) { - provider = new InputProvider(_sessionConfig); - } - - StartReplay(new QuantumGame.StartParameters() { - ResourceManager = resourceManager, - AssetSerializer = assetSerializer, - CallbackDispatcher = callbackDispatcher, - EventDispatcher = eventDispatcher, - GameFlags = gameFlags, - }, provider, "server", logInitForConsole); - } - - /// - /// Start the simulation as a replay by providing an input provider. - /// - /// Game start parameters - /// Input provider - /// Optional client id - /// Optionally disable setting up the console as log output (required on the Quantum plugin) - public void StartReplay(QuantumGame.StartParameters startParams, IDeterministicReplayProvider provider, string clientId = "server", bool logInitForConsole = true, IDeterministicPlatformTaskRunner taskRunner = null) { - DeterministicSessionArgs sessionArgs; - sessionArgs.Mode = DeterministicGameMode.Replay; - sessionArgs.Game = null; - sessionArgs.Replay = provider; - sessionArgs.Communicator = null; - sessionArgs.PlatformInfo = null; - sessionArgs.InitialTick = 0; - sessionArgs.FrameData = null; - sessionArgs.SessionConfig = null; - sessionArgs.RuntimeConfig = null; - Start(startParams, sessionArgs, _sessionConfig.PlayerCount, clientId, logInitForConsole, taskRunner); - } - - /// - /// Start the simulation as a spectator. - /// - /// Game start parameters - /// Quantum network comunicator (has to have a peer that is connected to a room - /// Optionally the frame to start from - /// The tick that the frame data is based on - /// Optional client id - /// Optionally disable setting up the console as log output (required on the Quantum plugin) - public void StartSpectator(QuantumGame.StartParameters startParams, ICommunicator networkCommunicator, byte[] frameData = null, int initialTick = 0, string clientId = "observer", bool logInitForConsole = true, IDeterministicPlatformTaskRunner taskRunner = null) { - DeterministicSessionArgs sessionArgs; - sessionArgs.Mode = DeterministicGameMode.Spectating; - sessionArgs.Game = null; - sessionArgs.Replay = null; - sessionArgs.Communicator = networkCommunicator; - sessionArgs.PlatformInfo = null; - sessionArgs.InitialTick = initialTick; - sessionArgs.FrameData = frameData; - sessionArgs.SessionConfig = null; - sessionArgs.RuntimeConfig = null; - Start(startParams, sessionArgs, 0, clientId, logInitForConsole, taskRunner); - } - - /// - /// Start the simulation in a custom way. - /// - /// Game start parameters - /// Game session args - /// Number of player slots - /// Optional client id - /// Optionally disable setting up the console as log output (required on the Quantum plugin) - public void Start(QuantumGame.StartParameters startParams, DeterministicSessionArgs sessionArgs, int playerSlots, string clientId = "server", bool logInitForConsole = true, IDeterministicPlatformTaskRunner taskRunner = null) { - if (!_loadedAllStatics) { - lock (_lock) { - if (!_loadedAllStatics) { - // console first - if (logInitForConsole) { - Log.InitForConsole(); - } - - // try to figure out platform if not set - if (Native.Utils == null) { - Native.Utils = CreateNativeUtils(); - } - - if (MemoryLayoutVerifier.Platform == null) { - MemoryLayoutVerifier.Platform = new MemoryLayoutVerifier.DefaultPlatform(); - } - } - - _loadedAllStatics = true; - } - } - - _game = new QuantumGame(startParams); - - DeterministicPlatformInfo info; - info = new DeterministicPlatformInfo(); - info.Allocator = CreateNativeAllocator(); - info.Architecture = DeterministicPlatformInfo.Architectures.x86; - info.RuntimeHost = DeterministicPlatformInfo.RuntimeHosts.PhotonServer; - info.Runtime = DeterministicPlatformInfo.Runtimes.NetFramework; - info.TaskRunner = taskRunner ?? new DotNetTaskRunner(); - - switch (Environment.OSVersion.Platform) { - case PlatformID.Unix: - info.Platform = DeterministicPlatformInfo.Platforms.Linux; - break; - - case PlatformID.MacOSX: - info.Platform = DeterministicPlatformInfo.Platforms.OSX; - break; - - default: - info.Platform = DeterministicPlatformInfo.Platforms.Windows; - break; - } - - sessionArgs.Game = _game; - sessionArgs.PlatformInfo = info; - sessionArgs.SessionConfig = _sessionConfig; - sessionArgs.RuntimeConfig = RuntimeConfig.ToByteArray(_runtimeConfig); - - _session = new DeterministicSession(sessionArgs); - _session.Join(clientId, playerSlots); - - _startGameTimestamp = DateTime.Now; - } - - /// - /// Update the session. - /// - /// Optionally provide a custom delta time - public void Service(double? dt = null) { - _session.Update(dt); - } - - /// - /// Destroy the session. - /// - public void Destroy() { - _session?.Destroy(); - _session = null; - } - - /// - /// Use other constructors that provide the session and runtime config. - /// - public SessionContainer() { - _sessionConfig = null; - _runtimeConfig = null; - } - - public SessionContainer(ReplayFile replayFile) { - _sessionConfig = replayFile.DeterministicConfig; - _runtimeConfig = replayFile.RuntimeConfig; - } - - public SessionContainer(DeterministicSessionConfig sessionConfig, RuntimeConfig runtimeConfig) { - _sessionConfig = sessionConfig; - _runtimeConfig = runtimeConfig; - } - } -} - - -// Replay/SessionContainer.Legacy.cs - -namespace Quantum.Legacy { - [Obsolete("Use Quantum.SessionContainer")] - public class SessionContainer { - DeterministicSessionConfig _sessionConfig; - RuntimeConfig _runtimeConfig; - - public DeterministicSession session; - public DeterministicSessionConfig config => _sessionConfig ?? replayFile.DeterministicConfig; - public RuntimeConfig runtimeConfig => _runtimeConfig ?? replayFile.RuntimeConfig; - public Native.Allocator allocator => _allocator.Value; - - - public IDeterministicReplayProvider provider; - public IDeterministicGame game; - public IResourceManager resourceManager; - public ReplayFile replayFile; - public IAssetSerializer assetSerializer; - public IEventDispatcher eventDispatcher; - public ICallbackDispatcher callbackDispatcher; - public int gameFlags; - - public static Boolean _loadedAllStatics = false; - public static Object _lock = new Object(); - - private static Lazy _allocator = new Lazy(() => CreateNativeAllocator()); - - public static Native.Allocator CreateNativeAllocator() { - switch (System.Environment.OSVersion.Platform) { - case PlatformID.Unix: - case PlatformID.MacOSX: - return new Native.LIBCAllocator(); - default: - return new Native.MSVCRTAllocator(); - } - } - - public static Native.Utility CreateNativeUtils() { - switch (System.Environment.OSVersion.Platform) { - case PlatformID.Unix: - case PlatformID.MacOSX: - return new Native.LIBCUtility(); - - default: - return new Native.MSVCRTUtility(); - } - } - - public void Start(bool logInitForConsole = true) { - - if (!_loadedAllStatics) { - lock (_lock) { - if (!_loadedAllStatics) { - // console first - if (logInitForConsole) { - Log.InitForConsole(); - } - - // try to figure out platform if not set - if (Native.Utils == null) { - Native.Utils = CreateNativeUtils(); - } - - if (MemoryLayoutVerifier.Platform == null) { - MemoryLayoutVerifier.Platform = new MemoryLayoutVerifier.DefaultPlatform(); - } - } - _loadedAllStatics = true; - } - } - - game = new QuantumGame(new QuantumGame.StartParameters() { - ResourceManager = resourceManager, - AssetSerializer = assetSerializer, - CallbackDispatcher = callbackDispatcher, - EventDispatcher = eventDispatcher, - GameFlags = gameFlags, - }); - - if (provider == null) { - provider = new InputProvider(config); - } - - DeterministicPlatformInfo info; - info = new DeterministicPlatformInfo(); - info.Allocator = allocator; - info.Architecture = DeterministicPlatformInfo.Architectures.x86; - info.RuntimeHost = DeterministicPlatformInfo.RuntimeHosts.PhotonServer; - info.Runtime = DeterministicPlatformInfo.Runtimes.NetFramework; - info.TaskRunner = new DotNetTaskRunner(); - - switch (System.Environment.OSVersion.Platform) { - case PlatformID.Unix: - info.Platform = DeterministicPlatformInfo.Platforms.Linux; - break; - - case PlatformID.MacOSX: - info.Platform = DeterministicPlatformInfo.Platforms.OSX; - break; - - default: - info.Platform = DeterministicPlatformInfo.Platforms.Windows; - break; - } - - DeterministicSessionArgs args; - args.Game = game; - args.Mode = DeterministicGameMode.Replay; - args.Replay = provider; - args.FrameData = null; - args.Communicator = null; - args.InitialTick = 0; - args.SessionConfig = config; - args.PlatformInfo = info; - args.RuntimeConfig = RuntimeConfig.ToByteArray(runtimeConfig); - - session = new DeterministicSession(args); - session.Join("server", config.PlayerCount); - } - - public void Service(double? dt = null) { - session.Update(dt); - } - - public void Destroy() { - if (session != null) - session.Destroy(); - session = null; - - //DB.Dispose(); - } - - public SessionContainer() { - _sessionConfig = null; - _runtimeConfig = null; - } - - public SessionContainer(DeterministicSessionConfig sessionConfig, RuntimeConfig runtimeConfig) { - _sessionConfig = sessionConfig; - _runtimeConfig = runtimeConfig; - } - } -} - - -// Systems/Base/SystemArrayComponent.cs - -namespace Quantum.Task { - public abstract unsafe class SystemArrayComponent : SystemBase where T : unmanaged, IComponent { - private TaskDelegateHandle _arrayTaskDelegateHandle; - - // internal max slices - private const int MAX_SLICES_COUNT = 32; - - public virtual int SlicesCount => MAX_SLICES_COUNT / 2; - - public sealed override void OnInit(Frame f) { - f.Context.TaskContext.RegisterDelegate(TaskArrayComponent, GetType().Name + ".Update", ref _arrayTaskDelegateHandle); - OnInitUser(f); - } - - protected virtual void OnInitUser(Frame f) { - - } - - protected override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - var slicesCount = Math.Max(1, Math.Min(SlicesCount, MAX_SLICES_COUNT)); - return f.Context.TaskContext.AddArrayTask(_arrayTaskDelegateHandle, null, f.ComponentCount(includePendingRemoval: true), taskHandle, slicesCount); - } - - private void TaskArrayComponent(FrameThreadSafe f, int start, int count, void* arg) { - var iterator = f.GetComponentBlockIterator(start, count).GetEnumerator(); - while (iterator.MoveNext()) { - var (entity, component) = iterator.Current; - Update(f, entity, component); - } - } - - public abstract void Update(FrameThreadSafe f, EntityRef entity, T* component); - } -} - -// Systems/Base/SystemArrayFilter.cs - -namespace Quantum.Task { - public abstract unsafe class SystemArrayFilter : SystemBase where T : unmanaged { - private TaskDelegateHandle _arrayTaskDelegateHandle; - private ComponentFilterStructMeta _filterMeta; - - // internal max slices - private const int MAX_SLICES_COUNT = 32; - - public virtual int SlicesCount => MAX_SLICES_COUNT / 2; - - public virtual bool UseCulling => true; - - public virtual ComponentSet Without => default; - - public virtual ComponentSet Any => default; - - public sealed override void OnInit(Frame f) { - _filterMeta = ComponentFilterStructMeta.Create(); - Assert.Check(_filterMeta.ComponentCount > 0, "Filter Struct '{0}' must have at least one component pointer.", typeof(T)); - - f.Context.TaskContext.RegisterDelegate(TaskArrayFilter, GetType().Name + ".Update", ref _arrayTaskDelegateHandle); - OnInitUser(f); - } - - protected virtual void OnInitUser(Frame f) { - - } - - protected override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - // figure out smallest block iterator - var taskSize = f.ComponentCount(_filterMeta.ComponentTypes[0], includePendingRemoval: true); - - for (var i = 1; i < _filterMeta.ComponentCount; ++i) { - var otherCount = f.ComponentCount(_filterMeta.ComponentTypes[i], includePendingRemoval: true); - if (otherCount < taskSize) { - taskSize = otherCount; - } - } - - var slicesCount = Math.Max(1, Math.Min(SlicesCount, MAX_SLICES_COUNT)); - return f.Context.TaskContext.AddArrayTask(_arrayTaskDelegateHandle, null, taskSize, taskHandle, slicesCount); - } - - private void TaskArrayFilter(FrameThreadSafe f, int start, int count, void* userData) { - // grab iterator - var iterator = f.FilterStruct(Without, Any, start, count); - - // set culling flag - iterator.UseCulling = UseCulling; - - var filter = default(T); - - // execute filter loop - while (iterator.Next(&filter)) { - Update(f, ref filter); - } - } - - public abstract void Update(FrameThreadSafe f, ref T filter); - } -} - -// Systems/Base/SystemBase.cs - -namespace Quantum { - public abstract partial class SystemBase { - Int32? _runtimeIndex; - String _scheduleSample; - SystemBase _parentSystem; - - public Int32 RuntimeIndex { - get { - return (Int32)_runtimeIndex; - } - set { - if (_runtimeIndex.HasValue) { - Log.Error("Can't change systems runtime index after game has started"); - } else { - _runtimeIndex = value; - } - } - } - - public SystemBase ParentSystem { - get { - return _parentSystem; - } - internal set { - _parentSystem = value; - } - } - - public virtual IEnumerable ChildSystems { - get { - return new SystemBase[0]; - } - } - - public IEnumerable Hierarchy { - get { - yield return this; - - foreach (var child in ChildSystems) { - foreach (var childHierarchy in child.Hierarchy) { - if (childHierarchy != null) { - yield return childHierarchy; - } - } - } - } - } - - public virtual Boolean StartEnabled { - get { return true; } - } - - public SystemBase() { - _scheduleSample = GetType().Name + ".Schedule"; - } - - public SystemBase(string scheduleSample) { - _scheduleSample = scheduleSample; - } - - public virtual void OnInit(Frame f) { - - } - - public virtual void OnEnabled(Frame f) { - - } - - public virtual void OnDisabled(Frame f) { - - } - - public TaskHandle OnSchedule(Frame f, TaskHandle taskHandle) { -#if DEBUG - var profiler = f.Context.ProfilerContext.GetProfilerForTaskThread(0); - try { - profiler.Start(_scheduleSample); -#endif - - return Schedule(f, taskHandle); - -#if DEBUG - } finally { - profiler.End(); - } -#endif - } - - protected abstract TaskHandle Schedule(Frame f, TaskHandle taskHandle); - } -} - -// Systems/Base/SystemGroup.cs - -namespace Quantum { - public unsafe class SystemGroup : SystemBase { - SystemBase[] _children; - - public sealed override IEnumerable ChildSystems { - get { return _children; } - } - - public SystemGroup(String name, params SystemBase[] children) : base(name + ".Schedule") { - _children = children; - - for (int i = 0; i < _children.Length; i++) { - _children[i].ParentSystem = this; - } - } - - protected sealed override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - if (_children != null) { - for (var i = 0; i < _children.Length; ++i) { - if (f.SystemIsEnabledSelf(_children[i])) { - try { - taskHandle = _children[i].OnSchedule(f, taskHandle); - } catch (Exception exn) { - Log.Exception(exn); - } - } - } - } - - return taskHandle; - } - - public override void OnEnabled(Frame f) { - base.OnEnabled(f); - - for (int i = 0; i < _children.Length; ++i) { - if (f.SystemIsEnabledSelf(_children[i])) { - _children[i].OnEnabled(f); - } - } - } - - public override void OnDisabled(Frame f) { - base.OnDisabled(f); - - for (int i = 0; i < _children.Length; ++i) { - if (f.SystemIsEnabledSelf(_children[i])) { - _children[i].OnDisabled(f); - } - } - } - } -} - -// Systems/Base/SystemMainThread.cs - -namespace Quantum { - public abstract unsafe class SystemMainThread : SystemBase { - TaskDelegateHandle _updateHandle; - - String _update; - - public SystemMainThread(string name) { - _update = name + ".Update"; - } - - public SystemMainThread() { - _update = GetType().Name + ".Update"; - } - - protected TaskHandle ScheduleUpdate(Frame f, TaskHandle taskHandle) { - if (_updateHandle.IsValid == false) { - f.Context.TaskContext.RegisterDelegate(TaskCallback, _update, ref _updateHandle); - } - - return f.Context.TaskContext.AddMainThreadTask(_updateHandle, null, taskHandle); - } - - protected override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - return ScheduleUpdate(f, taskHandle); - } - - void TaskCallback(FrameThreadSafe frame, int start, int count, void* arg) { - Update((Frame)frame); - - if (((FrameBase)frame).CommitCommandsMode == CommitCommandsModes.InBetweenSystems) { - ((FrameBase)frame).Unsafe.CommitAllCommands(); - } - } - - public abstract void Update(Frame f); - } -} - -// Systems/Base/SystemMainThreadFilter.cs - -namespace Quantum { - public abstract unsafe class SystemMainThreadFilter : SystemMainThread where T : unmanaged { - public virtual bool UseCulling { - get { return true; } - } - - public virtual ComponentSet Without { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => default; - } - - public virtual ComponentSet Any { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => default; - } - - public sealed override void Update(Frame f) { - // grab iterator - var it = f.Unsafe.FilterStruct(Without, Any); - - // set culling flag - it.UseCulling = UseCulling; - - // execute filter loop - var filter = default(T); - - while (it.Next(&filter)) { - Update(f, ref filter); - } - } - - public abstract void Update(Frame f, ref T filter); - } -} - -// Systems/Base/SystemMainThreadGroup.cs - -namespace Quantum { - public unsafe class SystemMainThreadGroup : SystemMainThread { - SystemMainThread[] _children; - - public SystemMainThreadGroup(string name, params SystemMainThread[] children) - : base(name + ".Schedule") { - Assert.Check(name != null); - Assert.Check(children != null); - - _children = children; - - for (int i = 0; i < _children.Length; i++) { - _children[i].ParentSystem = this; - } - } - - public sealed override IEnumerable ChildSystems { - get { return _children; } - } - - protected override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - if (_children != null) { - for (var i = 0; i < _children.Length; ++i) { - if (f.SystemIsEnabledSelf(_children[i])) { - try { - taskHandle = _children[i].OnSchedule(f, taskHandle); - } catch (Exception exn) { - Log.Exception(exn); - } - } - } - } - - return taskHandle; - } - - public override void OnEnabled(Frame f) { - base.OnEnabled(f); - - for (int i = 0; i < _children.Length; ++i) { - if (f.SystemIsEnabledSelf(_children[i])) { - _children[i].OnEnabled(f); - } - } - } - - public override void OnDisabled(Frame f) { - base.OnDisabled(f); - - for (int i = 0; i < _children.Length; ++i) { - if (f.SystemIsEnabledSelf(_children[i])) { - _children[i].OnDisabled(f); - } - } - } - - public sealed override void Update(Frame f) { - } - } -} - -// Systems/Base/SystemSignalsOnly.cs - -namespace Quantum { - public class SystemSignalsOnly : SystemBase { - protected sealed override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - return taskHandle; - } - } -} - -// Systems/Base/SystemThreadedComponent.cs - -namespace Quantum.Task { - public abstract unsafe class SystemThreadedComponent : SystemBase where T : unmanaged, IComponent { - private TaskDelegateHandle _threadedTaskDelegateHandle; - private int _sliceIndexer; - private int _sliceSize; - - public const int DEFAULT_SLICE_SIZE = 16; - - public virtual int SliceSize => DEFAULT_SLICE_SIZE; - - public sealed override void OnInit(Frame f) { - f.Context.TaskContext.RegisterDelegate(TaskThreadedComponent, GetType().Name + ".Update", ref _threadedTaskDelegateHandle); - OnInitUser(f); - } - - protected virtual void OnInitUser(Frame f) { - - } - - protected override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - // reset indexer - _sliceIndexer = -1; - - // cache slice size safely in main-thread - _sliceSize = Math.Max(1, SliceSize); - - return f.Context.TaskContext.AddThreadedTask(_threadedTaskDelegateHandle, null, taskHandle); - } - - private void TaskThreadedComponent(FrameThreadSafe f, int start, int count, void* userData) { - while (true) { - var sliceIndex = Interlocked.Increment(ref _sliceIndexer); - var iterator = f.GetComponentBlockIterator(sliceIndex * _sliceSize, _sliceSize).GetEnumerator(); - - if (iterator.MoveNext() == false) { - // chunk is out of buffer range, we're done - return; - } - - do { - var (entity, component) = iterator.Current; - Update(f, entity, component); - } while (iterator.MoveNext()); - } - } - - public abstract void Update(FrameThreadSafe f, EntityRef entity, T* component); - } -} - -// Systems/Base/SystemThreadedFilter.cs - -namespace Quantum.Task { - public abstract unsafe class SystemThreadedFilter : SystemBase where T : unmanaged { - private TaskDelegateHandle _threadedTaskDelegateHandle; - private int _sliceIndexer; - private int _sliceSize; - - public const int DEFAULT_SLICE_SIZE = 16; - - public virtual int SliceSize => DEFAULT_SLICE_SIZE; - - public virtual bool UseCulling => true; - - public virtual ComponentSet Without => default; - - public virtual ComponentSet Any => default; - - public sealed override void OnInit(Frame f) { - f.Context.TaskContext.RegisterDelegate(TaskThreadedFilter, GetType().Name + ".Update", ref _threadedTaskDelegateHandle); - OnInitUser(f); - } - - protected virtual void OnInitUser(Frame f) { - - } - - protected override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - // reset indexer - _sliceIndexer = -1; - - // cache slice size safely in main-thread - _sliceSize = Math.Max(1, SliceSize); - - return f.Context.TaskContext.AddThreadedTask(_threadedTaskDelegateHandle, null, taskHandle); - } - - private void TaskThreadedFilter(FrameThreadSafe f, int start, int count, void* userData) { - var iterator = f.FilterStruct(Without, Any); - - // set culling flag - iterator.UseCulling = UseCulling; - - var filter = default(T); - - while (true) { - var sliceIndex = Interlocked.Increment(ref _sliceIndexer); - - // reset iterator - iterator.Reset(sliceIndex * _sliceSize, _sliceSize); - - // execute filter loop - if (iterator.Next(&filter) == false) { - // chunk is out of buffer range, we're done - return; - } - - do { - Update(f, ref filter); - } while (iterator.Next(&filter)); - } - } - - public abstract void Update(FrameThreadSafe f, ref T filter); - } -} - -// Systems/Core/DebugSystem.cs - -namespace Quantum.Core { - - public static partial class DebugCommandType { - public const int Create = 0; - public const int Destroy = 1; - public const int UserCommandTypeStart = 1000; - } - - public static partial class DebugCommand { - - public static event Action CommandExecuted -#if DEBUG && !QUANTUM_DEBUG_COMMAND_DISABLED - { - add => _commandExecuted += value; - remove => _commandExecuted -= value; - } - private static Action _commandExecuted; -#else - { add { } remove { } } -#endif - - public static bool IsEnabled => -#if DEBUG && !QUANTUM_DEBUG_COMMAND_DISABLED - true; -#else - false; -#endif - - public static void Send(QuantumGame game, params Payload[] payload) { -#if DEBUG && !QUANTUM_DEBUG_COMMAND_DISABLED - game.SendCommand(new InternalCommand() { - Data = payload - }); -#else - Log.Warn("DebugCommand works only in DEBUG builds without QUANTUM_DEBUG_COMMAND_DISABLED define."); -#endif - } - - public static void Reset() { -#if DEBUG && !QUANTUM_DEBUG_COMMAND_DISABLED - _commandExecuted = null; -#endif - } - - - public partial struct Payload { - public long Id; - public int Type; - public EntityRef Entity; - public ComponentSet Components; - public byte[] Data; - } - - - public static Payload CreateDestroyPayload(EntityRef entityRef) { - return new Payload() { - Type = DebugCommandType.Destroy, - Entity = entityRef - }; - } - - public static Payload CreateMaterializePayload(EntityRef entityRef, EntityPrototype prototype, IAssetSerializer serializer) { - ComponentSet componentSet = default; - foreach (var component in prototype.Container.Components) { - componentSet.Add(ComponentTypeId.GetComponentIndex(component.ComponentType)); - } - return new Payload() { - Type = DebugCommandType.Create, - Entity = entityRef, - Data = serializer.SerializeAssets(new[] { prototype }), - Components = componentSet - }; - } - - public static Payload CreateRemoveComponentPayload(EntityRef entityRef, Type componentType) { - var components = new ComponentSet(); - components.Add(ComponentTypeId.GetComponentIndex(componentType)); - - return new Payload() { - Type = DebugCommandType.Destroy, - Entity = entityRef, - Components = components - }; - } - -#if QUANTUM_DEBUG_COMMAND_DISABLED - internal static DeterministicCommand CreateCommand() => null; - internal static SystemBase CreateSystem() => null; -#else - internal static DeterministicCommand CreateCommand() => new InternalCommand(); - internal static SystemBase CreateSystem() => new InternalSystem(); - -#if DEBUG - private static void Execute(Frame f, ref Payload payload) { - Exception error = null; - try { - switch (payload.Type) { - case DebugCommandType.Create: - payload.Entity = ExecuteCreate(f, payload.Entity, payload.Data); - break; - case DebugCommandType.Destroy: - ExecuteDestroy(f, payload.Entity, payload.Components); - break; - default: - if (payload.Type >= DebugCommandType.UserCommandTypeStart) { - ExecuteUser(f, ref payload); - } else { - throw new InvalidOperationException($"Unknown command type: {payload.Type}"); - } - break; - } - } catch (Exception ex) { - error = ex; - } - _commandExecuted?.Invoke(payload, error); - } - - private static void ExecuteDestroy(Frame f, EntityRef entity, ComponentSet components) { - if (!f.Exists(entity)) { - Log.Error("Entity does not exist: {0}", entity); - } else if (components.IsEmpty) { - if (!f.Destroy(entity)) { - Log.Error("Failed to destroy entity {0}", entity); - } - } else { - for (int i = 1; i < ComponentTypeId.Type.Length; ++i) { - if (!components.IsSet(i)) { - continue; - } - var type = ComponentTypeId.Type[i]; - if (!f.Remove(entity, type)) { - Log.Error("Failed to destroy component {0} of entity {1}", type, entity); - } - } - - } - } - - private static EntityRef ExecuteCreate(Frame f, EntityRef entity, byte[] data) { - EntityPrototype prototype = null; - if (data?.Length > 0) { - prototype = f.Context.AssetSerializer.DeserializeAssets(data).OfType().FirstOrDefault(); - if (prototype == null) { - Log.Error("No prototype found"); - } - } - - if (!entity.IsValid) { - if (prototype != null) { - entity = f.Create(prototype); - } else { - entity = f.Create(); - } - } else if (prototype != null) { - f.Set(entity, prototype, out _); - } - - return entity; - } - - static partial void ExecuteUser(Frame f, ref Payload payload); - static partial void SerializeUser(BitStream stream, ref Payload payload); - - private class InternalCommand : DeterministicCommand { - public Payload[] Data = { }; - - public override void Serialize(BitStream stream) { - stream.SerializeArrayLength(ref Data); - - for (int i = 0; i < Data.Length; ++i) { - stream.Serialize(ref Data[i].Id); - stream.Serialize(ref Data[i].Type); - stream.Serialize(ref Data[i].Entity); - stream.Serialize(ref Data[i].Data); - unsafe { - var set = Data[i].Components; - for (int block = 0; block < ComponentSet.BLOCK_COUNT; ++block) { - stream.Serialize((&set)->_set + block); - } - Data[i].Components = set; - } - SerializeUser(stream, ref Data[i]); - } - } - } - - private class InternalSystem : SystemMainThread { - public override void Update(Frame f) { - for (int p = 0; p < f.PlayerCount; ++p) { - if (f.GetPlayerCommand(p) is InternalCommand cmd) { - for (int i = 0; i < cmd.Data.Length; ++i) { - Execute(f, ref cmd.Data[i]); - } - } - } - } - } -#else - private class InternalCommand : DeterministicCommand { - public override void Serialize(BitStream stream) { - throw new NotSupportedException("DebugCommands only work in DEBUG mode"); - } - } - - private class InternalSystem : SystemBase { - protected override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - return taskHandle; - } - } -#endif -#endif - } -} - - -// Systems/Core/NavigationSystem.cs - -namespace Quantum.Core { - public unsafe class NavigationSystem : SystemBase, INavigationCallbacks { - Frame _f; - - protected override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - _f = f; - return f.Navigation.Update(f, f.DeltaTime, this, taskHandle); - } - - public void OnWaypointReached(EntityRef entity, FPVector3 waypoint, Navigation.WaypointFlag waypointFlags, ref bool resetAgent) { - _f.Signals.OnNavMeshWaypointReached(entity, waypoint, waypointFlags, ref resetAgent); - } - - public void OnSearchFailed(EntityRef entity, ref bool resetAgent) { - _f.Signals.OnNavMeshSearchFailed(entity, ref resetAgent); - } - - public void OnMoveAgent(EntityRef entity, FPVector2 desiredDirection) { - _f.Signals.OnNavMeshMoveAgent(entity, desiredDirection); - } - } -} - -// Systems/Core/EntityPrototypeSystem.cs -namespace Quantum.Core { - public unsafe sealed partial class EntityPrototypeSystem : SystemSignalsOnly, ISignalOnMapChanged { - public override void OnInit(Frame f) { - OnMapChanged(f, default); - } - - public void OnMapChanged(Frame f, AssetRefMap previousMap) { - if (previousMap.Id.IsValid) { - foreach (var (entity, _) in f.GetComponentIterator()) { - f.Destroy(entity); - } - } - - if (f.Map != null) { - f.Create(f.Map.MapEntities, f.Map); - } - } - } -} - -// Systems/Core/CullingSystem.cs - -namespace Quantum.Core { - public unsafe class CullingSystem2D : SystemBase { - protected override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - return f.Context.Culling.Schedule2D(f, taskHandle); - } - } - - public unsafe class CullingSystem3D : SystemBase { - protected override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - return f.Context.Culling.Schedule3D(f, taskHandle); - } - } -} - -// Systems/Core/PlayerConnectedSystem.cs -namespace Quantum.Core { - unsafe class PlayerConnectedSystem : SystemMainThread { - public override void Update(Frame f) { - if (f.IsVerified == false) { - return; - } - - for (int p = 0; p < f.PlayerCount; p++) { - var isPlayerConnected = (f.GetPlayerInputFlags(p) & Photon.Deterministic.DeterministicInputFlags.PlayerNotPresent) == 0; - if (isPlayerConnected != f.Global->PlayerLastConnectionState.IsSet(p)) { - if (isPlayerConnected) { - f.Signals.OnPlayerConnected(p); - } else { - f.Signals.OnPlayerDisconnected(p); - } - - if (isPlayerConnected) { - f.Global->PlayerLastConnectionState.Set(p); - } - else { - f.Global->PlayerLastConnectionState.Clear(p); - } - } - } - } - } -} - - - -// Systems/Core/PhysicsSystem.cs - -namespace Quantum.Core { - public unsafe partial class PhysicsSystem2D : SystemBase, ICollisionCallbacks2D { - public override void OnInit(Frame f) { - f.Physics2D.Init(); - } - - protected override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - return f.Physics2D.Update(this, f.DeltaTime, taskHandle); - } - - public void OnCollision2D(FrameBase f, CollisionInfo2D info) { - ((Frame)f).Signals.OnCollision2D(info); - } - - public void OnCollisionEnter2D(FrameBase f, CollisionInfo2D info) { - ((Frame)f).Signals.OnCollisionEnter2D(info); - } - - public void OnCollisionExit2D(FrameBase f, ExitInfo2D info) { - ((Frame)f).Signals.OnCollisionExit2D(info); - } - - public void OnTrigger2D(FrameBase f, TriggerInfo2D info) { - ((Frame)f).Signals.OnTrigger2D(info); - } - - public void OnTriggerEnter2D(FrameBase f, TriggerInfo2D info) { - ((Frame)f).Signals.OnTriggerEnter2D(info); - } - - public void OnTriggerExit2D(FrameBase f, ExitInfo2D info) { - ((Frame)f).Signals.OnTriggerExit2D(info); - } - } - - public unsafe partial class PhysicsSystem3D : SystemBase, ICollisionCallbacks3D { - public override void OnInit(Frame f) { - f.Physics3D.Init(); - } - - protected override TaskHandle Schedule(Frame f, TaskHandle taskHandle) { - return f.Physics3D.Update(this, f.DeltaTime, taskHandle); - } - - public void OnCollision3D(FrameBase f, CollisionInfo3D info) { - ((Frame)f).Signals.OnCollision3D(info); - } - - public void OnCollisionEnter3D(FrameBase f, CollisionInfo3D info) { - ((Frame)f).Signals.OnCollisionEnter3D(info); - } - - public void OnCollisionExit3D(FrameBase f, ExitInfo3D info) { - ((Frame)f).Signals.OnCollisionExit3D(info); - } - - public void OnTrigger3D(FrameBase f, TriggerInfo3D info) { - ((Frame)f).Signals.OnTrigger3D(info); - } - - public void OnTriggerEnter3D(FrameBase f, TriggerInfo3D info) { - ((Frame)f).Signals.OnTriggerEnter3D(info); - } - - public void OnTriggerExit3D(FrameBase f, ExitInfo3D info) { - ((Frame)f).Signals.OnTriggerExit3D(info); - } - } -} diff --git a/data/DSL.txt b/data/DSL.txt deleted file mode 100644 index eade94df496a3953cf1cbf86c3b6982e987e2c10..0000000000000000000000000000000000000000 --- a/data/DSL.txt +++ /dev/null @@ -1,502 +0,0 @@ -Introduction -Quantum requires components and other runtime game state data types to be declared with its own DSL (domain-specific-language). - -These definitions are written into text files with the .qtn extension. The Quantum compiler will parse them into an AST, and generate partial C# struct definitions for each type (definitions can be split across as many files if needed, the compiler will merge them accordingly). - -The goal of the DSL is to abstract away from the developer the complex memory alignment requirements imposed by Quantum's ECS sparse set memory model, required to support the deterministic predict/rollback approach to simulation. - -This code generation approach also eliminates the need to write "boiler-plate" code for type serialization (used for snapshots, game saves, killcam replays), checksumming and other functions, like printing/dumping frame data for debugging purposes. - -The quantum dsl integration section shows how to add qtn files to the workflow. - -Components -Components are special structs that can be attached to entities, and used for filtering them (iterating only a subset of the active entities based on its attached components). This is a basic example definition of a component: - -component Action -{ - FP Cooldown; - FP Power; -} -These will be turned into regular C# structs. Labelling them as components (like above) will generate the appropriate code structure (marker interface, id property, etc). - -Aside from custom components, Quantum comes with several pre-built ones: - -Transform2D/Transform3D: position and rotation using Fixed Point (FP) values; -PhysicsCollider, PhysicsBody, PhysicsCallbacks, PhysicsJoints (2D/3D): used by Quantum's stateless physics engines; -PathFinderAgent, SteeringAgent, AvoidanceAgent, AvoidanceObstacle: navmesh-based path finding and movement. -Back To Top - - -Structs -Structs can be defined in both the DSL and C#. - - -DSL Defined -The Quantum DSL also allows the definition of regular structs (just like components, memory alignment, and helper functions will be taken care of): - -struct ResourceItem -{ - FP Value; - FP MaxValue; - FP RegenRate; -} -The fields will be ordered in alphabetical order when the struct is generated. If you need / want to have them appear in a specific order, you will have to define the structs in C# (see section below). - -This would let you use the "Resources" struct as a type in all other parts of the DSL, for example using it inside a component definition: - -component Resources -{ - ResourceItem Health; - ResourceItem Strength; - ResourceItem Mana; -} -The generated struct is partial and can be extended in C# if so desired. - -Back To Top - - -CSharp Defined -You can define structs in C# as well; however, in this case you will have to manually define the memory aligned of the struct. - -[StructLayout(LayoutKind.Explicit)] -public struct Foo { - public const int SIZE = 12; // the size in bytes of all members in bytes. - - [FieldOffset(0)] - public int A; - - [FieldOffset(4)] - public int B; - - [FieldOffset(8)] - public int C; -} -When using C# defined structs in the DSL (e.g. inside components), you will have to manually import the struct definition. - -import struct Foo(12); -N.B.: The import does not support constants in the size; you will have to specify the exact numerical value each time. - -Back To Top - - -Components Vs. Structs -An important question is why and when should components be used instead of regular structs (components, in the end, are also structs). - -Components contain generated meta-data that turns them into a special type with the following features: - -Can be attached directly to entities; -Used to filter entities when traversing the game state (next chapter will dive into the filter API); -Components can accessed, used or passed as parameters as either pointers or as value types, just like any other struct. - -Back To Top - - -Dynamic Collections -Quantum's custom allocator exposes blittable collections as part of the rollback-able game state. Collections only support support blittable types (i.e. primitive and DSL-defined types). - -To manage collection, the Frame API offers 3 methods for each: - -Frame.AllocateXXX: To allocate space for the collection on the heap. -Frame.FreeXXX: To free/deallocate the collection's memory. -Frame.ResolveXXX: To access the collection by resolving the pointer it. -Note: After freeing a collection, it HAS TO be nullified by setting it to default. This is required for serialization of the game state to work properly. Omitting the nullification will result in indeterministic behavior and desynchronisation. As alternative to freeing a collection and nullifying its Ptrs manually, it possible to use the FreeOnComponentRemoved attribute on the field in question. - -Back To Top - - -Important Notes -Several components can reference the same collection instance. -Dynamic collections are stored as references inside components and structs. They therefore have to to be allocated when initializing them, and more importantly, freed when they are not needed any more. If the collection is part of a component, two options are available: -implement the reactive callbacks ISignalOnAdd and ISignalOnRemove and allocate/free the collections there. (For more information on these specific signals, see the Components page in the ECS section of the Manual); or, -use the [AllocateOnComponentAdded] and [FreeOnComponentRemoved] attributes to let Quantum handle the allocation and deallocation when the component is added and removed respectively. -Quantum do NOT pre-allocate collections from prototypes, unless there is at least value. If the collection is empty, the memory has to be manually allocated. -Attempting to free a collection more than once will throw an error and puts the heap in an invalid state internally. -Back To Top - - -Lists -Dynamic lists can be defined in the DSL using list MyList. - -component Targets { - list Enemies; -} -The basic API methods for dealing with these Lists are: - -Frame.AllocateList() -Frame.FreeList(QListPtr ptr) -Frame.ResolveList(QListPtr ptr) -Once resolved, a list can be iterated over or manipulated with all the expected API methods of a list such as Add, Remove, Contains, IndexOf, RemoveAt, [], etc... . - -To use the list in the component of type Targets defined in the code snippet above, you could create the following system: - -namespace Quantum -{ - public unsafe class HandleTargets : SystemMainThread, ISignalOnComponentAdded, ISignalOnComponentRemoved - { - public override void Update(Frame f) - { - var targets = f.GetComponentIterator(); - - for (int i = 0; i < targets.Count; i++) - { - var target = targets[i]; - - // To use a list, you must first resolve its pointer via the frame - var list = frame.ResolveList(target.Enemies); - - // Do stuff - } - } - - public void OnAdded(Frame f, EntityRef entity, Targets* component) - { - // allocating a new List (returns the blittable reference type - QListPtr) - component->Enemies = f.AllocateList(); - } - - public void OnRemoved(Frame f, EntityRef entity, Targets* component) - { - // A component HAS TO de-allocate all collection it owns from the frame data, otherwise it will lead to a memory leak. - // receives the list QListPtr reference. - f.FreeList(component->Enemies); - - // All dynamic collections a component points to HAVE TO be nullified in a component's OnRemoved - // EVEN IF is only referencing an external one! - // This is to prevent serialization issues that otherwise lead to a desynchronisation. - component->Enemies = default; - } - } -} -Back To Top - - -Dictionaries -Dictionaries can be declared in the DSL like so dictionary MyDictionary. - -component Hazard{ - dictionary DamageDealt; -} -The basic API methods for dealing with these dictionaries are: - -Frame.AllocateDictionary() -Frame.FreeDictionary(QDictionaryPtr ptr) -Frame.ResolveDictionary(QDictionaryPtr ptr) -Just like with any other dynamic collection it is mandatory to allocate it before using it, as well as de-allocate it from the frame data and nullified it once the dictionary is no longer used. See the example provided in the section about lists here above. - -Back To Top - - -HashSet -HashSets can be declared in the DSL like so hash_set MyHashSet. - -component Nodes{ - hash_set ProcessedNodes; -} -The basic API methods for dealing with these dictionaries are: - -Frame.AllocateHashSet(QHashSetPtr ptr, int capacity = 8) -Frame.FreeHashSet(QHashSetPtr ptr) -Frame.ResolveHashSet(QHashSetPtr ptr) -Just like with any other dynamic collection it is mandatory to allocate it before using it, as well as de-allocate it from the frame data and nullified it once the hash set is no longer used. See the example provided in the section about lists here above. - -Back To Top - - -Unions, Enums And Bitsets -C-like unions and enums can be generated as well. The example below demonstrates how to save data memory by overlapping some mutually exclusive data types/values into a union: - -struct DataA -{ - FPVector2 Something; - FP Anything; -} - -struct DataB -{ - FPVector3 SomethingElse; - Int32 AnythingElse; -} - -union Data -{ - DataA A; - DataB B; -} -The generated type Data will also include a differentiator property (to tell which union-type has been populated). "Touching" any of the union sub-types will set this property to the appropriate value. - -Bitsets can be used to declared fixed-size memory blocks for any desired purpose (for example fog-of-war, grid-like structures for pixel perfect game mechanics, etc.): - -struct FOWData -{ - bitset[256] Map; -} -Back To Top - - -Input -In Quantum, the runtime input exchanged between clients is also declared in the DSL. This example defines a simple movement vector and a Fire button as input for a game: - -input -{ - FPVector2 Movement; - button Fire; -} -The input struct is polled every tick and sent to the server (when playing online). - -For more information about input, such as best practices and recommended approaches to optimization, refer to this page: input - -Back To Top - - -Signals -Signals are function signatures used as a decoupled inter-system communication API (a form of publisher/subscriber API). This would define a simple signal (notice the special type entity_ref - these will be listed at the end of this chapter): - -signal OnDamage(FP damage, entity_ref entity); -This would generate the following interface (that can be implemented by any System): - -public interface ISignalOnDamage -{ - public void OnDamage(Frame f, FP damage, EntityRef entity); -} -Signals are the only concept which allows the direct declaration of a pointer in Quantum's DSL, so passing data by reference can be used to modify the original data directly in their concrete implementations: - -signal OnBeforeDamage(FP damage, Resources* resources); -Notice this allows the passing of a component pointer (instead of the entity reference type). - -Back To Top - - -Events -Events are a fine-grained solution to communicate what happens inside the simulation to the rendering engine / view (they should never be used to modify/update part of the game state). Use the "event" keyword to define its name and data: - -Find detailed information about events in the game events manual. - -Define an event using the Quantum DSL - -event MyEvent{ - int Foo; -} -Trigger the event from the simulation - -f.Events.MyEvent(2022); -And subscribe and consume the event in Unity - -QuantumEvent.Subscribe(listener: this, handler: (MyEvent e) => Debug.Log($"MyEvent {e.Foo}")); -Back To Top - - -Globals -It is possible to define globally accessible variables in the DSL. Globals can be declared in any .qtn file by using the global scope. - -global { - // Any type that is valid in the DSL can also be used. - FP MyGlobalValue; -} -Like all things DSL-defined, global variables are part of the state and are fully compatible with the predict-rollback system. - -Variables declared in the global scope are made available through the Frame API. They can be accessed (read/write) from any place that has access to the frame - see the Systems document in the ECS section. - -N.B.: An alternative to global variables are the Singleton Components; for more information please refer to the Components page in the ECS section of the manual. - -Back To Top - - -Special Types -Quantum has a few special types that are used to either abstract complex concepts (entity reference, player indexes, etc.), or to protect against common mistakes with unmanaged code, or both. The following special types are available to be used inside other data types (including in components, also in events, signals, etc.): - -player_ref: represents a runtime player index (also cast to and from Int32). When defined in a component, can be used to store which player controls the associated entity (combined with Quantum's player-index-based input). -entity_ref: because each frame/tick data in quantum resides on a separate memory region/block (Quantum keeps a a few copies to support rollbacks), pointers cannot be cached in-between frames (nor in the game state neither in Unity scripts). An entity ref abstracts an entity's index and version properties (protecting the developer from accidentally accessing deprecated data over destroyed or reused entity slots with old refs). -asset_ref: rollback-able reference to a data asset instance from the Quantum asset database (please refer to the data assets chapter). -list, dictionary: dynamic collection references (stored in Quantum's frame heap). Only supports blittable types (primitives + DSL-defined types). -array[size]: fixed sized "arrays" to represent data collections. A normal C# array would be a heap-allocated object reference (it has properties, etc.), which violates Quantum's memory requirements, so the special array type generates a pointer based simple API to keep rollback-able data collections inside the game state; -Back To Top - - -A Note On Assets -Assets are a special feature of Quantum that let the developer define data-driven containers (normal classes, with inheritance, polymorphic methods, etc.) that end up as immutable instances inside an indexed database. The "asset" keyword is used to assign an (existing) class as a data asset that can have references assigned inside the game state (please refer to the Data Assets chapter to learn more about features and restrictions): - -asset CharacterData; // the CharacterData class is partially defined in a normal C# file by the developer -The following struct show some valid examples of the types above (sometimes referencing previously defined types): - -struct SpecialData -{ - player_ref Player; - entity_ref Character; - entity_ref AnotherEntity; - asset_ref CharacterData; - array[10] TenNumbers; -} -Back To Top - - -Available Types -When working in the DSL, you can use a variety of types. Some are pre-imported by the parsers, while others need to be manually imported. - - -By Default -Quantum's DSL parser has a list of pre-imported cross-platform deterministic types that can be used in the game state definition: - -Boolean / bool - internally gets wrapped in QBoolean which works identically (get/set, compare, etc...) -Byte -SByte -UInt16 / Int16 -UInt32 / Int32 -UInt64 / Int64 -FP -FPVector2 -FPVector3 -FPMatrix -FPQuaternion -PlayerRef / player_ref in the DSL -EntityRef / entity_ref in the DSL -LayerMask -NullableFP / FP? in the DSL -NullableFPVector2 / FPVector2? in the DSL -NullableFPVector3 / FPVector3? in the DSL -QString is for UTF-16 (aka Unicode in .NET) -QStringUtf8 is always UTF-8 -Hit -Hit3D -Shape2D -Shape3D -Joint, DistanceJoint, SpringJoint and HingeJoint -Note on QStrings: N represents the total size of the string in bytes minus 2 bytes used for bookkeeping. In other words QString<64> will use 64 bytes for a string with a max byte length of 62 bytes, i.e. up to 31 UTF-16 characters. - -Back To Top - - -Manual Import -If you need a type that is not listed in the previous section, you will have to import it manually when using it in QTN files. - - -Namespaces / Types Outside Of Quantum -To import types defined in other namespaces, you can use the following syntax: - -import MyInterface; -or -import MyNameSpace.Utils; -For an enum the syntax is as follows: - -import enum MyEnum(underlying_type); - -// This syntax is identical for Quantum specific enums -import enum Shape3DType(byte); -Back To Top - - -Built-In Quantum Type And Custom Type -When importing a Quantum built-in type or a custom type, the struct size is predefined in their C# declaration. It is therefore important to add some safety measures. - -namespace Quantum { - [StructLayout(LayoutKind.Explicit)] - public struct Foo { - public const int SIZE = sizeof(Int32) * 2; - [FieldOffset(0)] - public Int32 A; - [FieldOffset(sizeof(Int32))] - public Int32 B; - } -} -#define FOO_SIZE 8 // Define a constant value with the known size of the struct -import struct Foo(8); -To ensure the expected size of the struct is equal to the actual size, it is recommended to add an Assert as shown below in one of your systems. - -public unsafe class MyStructSizeCheckingSystem : SystemMainThread{ - public override void OnInit(Frame f) - { - Assert.Check(Constants.FOO_SIZE == Foo.SIZE); - } -} -If the size of the built-in struct changes during an upgrade, this Assert will throw and allow you to update the values in the DSL. - -Back To Top - - -Attributes -Quantum supports several attributes to present parameters in the Inspector. - -The attributes are contained within the Quantum.Inspector namespace. - -Attribute Parameters Description -DrawIf string fieldName -long value -CompareOperator compare -HideType hide Displays the property only if the condition evaluates to true. - -fieldName = the name of the property to evaluate. -value = the value used for comparison. -compare = the comparison operation to be performed Equal, NotEqual, Less, LessOrEqual, GreaterOrEqual or Greater. -hide = the field's behavior when the expression evaluates to False:Hide or ReadOnly. - -For more information on compare and hide, see below. -Header string header Adds a header above the property. - -header = the header text to display. -HideInInspector - Serializes the field and hides the following property in the Unity inspector. -Layer - Can only be applied to type int. -Will call EditorGUI.LayerField on the field. -Optional string enabledPropertyPath Allows to turn the display of a property on/off. - -enabledPropertyPath = the path to the bool used to evaluate the toggle. -Space - Adds a space above the property -Tooltip string tooltip Displays a tool tip when hovering over the property. - -tooltip = the tip to display. -ArrayLength (since 2.1) -FixedArray (in 2.0) -ONLY FOR CSharp int length --------- -int minLength -int maxLength Using length allows to define the size of a an array. ------- -Using minLength and maxLength allows to define a range for the size in the Inspector. -The final size can then be set in the Inspector. -(minLength and maxLength are inclusive) -ExcludeFromPrototype - Can be applied to both a component and component fields. ------- -- Field: Excludes field from a the prototype generated for the component. -- Component: No prototype will be generated for this component. -PreserveInPrototype - Added to a type marks it as usable in prototypes and prevents prototype class from being emit. -Added to a field only affects a specific field. Useful for simple [Serializable] structs as it avoids having to use _Prototype types on Unity side. -AllocateOnComponentAdded - Can be applied to dynamic collections. -This will allocate memory for the collection if it has not already been allocated when the component holding the collection is added to an entity. -FreeOnComponentRemoved - Can be applied to dynamic collections and Ptrs. -This will deallocate the associated memory and nullify the Ptr held in the field when the component is removed. ------- -IMPORTANT: Do NOT use this attribute in combination with cross-referenced collections as it only nullifies the Ptr held in that particular field and the others will be pointing to invalid memory. -The *Attributes* can be used in both C# and qtn files unless otherwise specified; however, there are some syntactic differences. -Back To Top - - -Use In CSharp -In C# files, attributes can be used and concatenated like any other attribute. - -// Multiple single attributes -[Header("Example Array")][Tooltip("min = 1\nmax = 20")] public FP[] TestArray = new FP[20]; - -// Multiple concatenated attributes -[Header("Example Array"), Tooltip("min = 1\nmax = 20")] public FP[] TestArray = new FP[20]; -Back To Top - - -Use In Qtn -In qtn files, the usage of single attributes remains the same as in C#. - -[Header("Example Array")] array[20] TestArray; -When combining multiple attributes, they have to be concatenated. - -[Header("Example Array"), Tooltip("min = 1\nmax = 20")] array[20] TestArray; -Back To Top - - -Compiler Options -The following compiler options are currently available to be used inside Quantum's DSL files (more will be added in the future): - -// pre defining max number of players (default is 6, absolute max is 64) -#pragma max_players 16 - -// numeric constants (useable inside the DSL by MY_NUMBER and useable in code by Constants.MY_NUMBER) -#define MY_NUMBER 10 - -// overriding the base class name for the generated constants (default is "Constants") -#pragma constants_class_name MyFancyConstants \ No newline at end of file diff --git a/data/DeadPieceSlot.cs b/data/DeadPieceSlot.cs deleted file mode 100644 index 0bc0fa3a8747e118cf44472cb59dac24b4823d8b..0000000000000000000000000000000000000000 --- a/data/DeadPieceSlot.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -public class DeadPieceSlot : MonoBehaviour { - - public PieceView Piece; -} diff --git a/data/DebugAction.cs b/data/DebugAction.cs deleted file mode 100644 index 6f29e63a2e0075fb96874110637454c5525f27a0..0000000000000000000000000000000000000000 --- a/data/DebugAction.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Quantum -{ - [System.Serializable] - public unsafe class DebugAction : AIAction - { - public string Message; - - public override void Update(Frame frame, EntityRef entity) - { - Log.Info(Message); - } - } -} diff --git a/data/DebugLeaf.cs b/data/DebugLeaf.cs deleted file mode 100644 index f81dfae67085ed29e3d9294fd941b8824fefdcfc..0000000000000000000000000000000000000000 --- a/data/DebugLeaf.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace Quantum -{ - [Serializable] - public unsafe partial class DebugLeaf : BTLeaf - { - - public string Message; - - /// - /// When Update is called, we just write a message on the console. - /// This Leaf never fails, nor takes more than one frame to finish, - /// so we always return Success. - /// - protected override BTStatus OnUpdate(BTParams btParams) - { - Log.Info(Message + " | Frame: " + btParams.Frame.Number); - - return BTStatus.Success; - } - } -} diff --git a/data/DebugService.cs b/data/DebugService.cs deleted file mode 100644 index 1174d795f138031852913bb3c19967bae32678dd..0000000000000000000000000000000000000000 --- a/data/DebugService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Quantum -{ - [Serializable] - public unsafe partial class DebugService : BTService - { - public string Message; - protected unsafe override void OnUpdate(BTParams btParams) - { - Log.Info($"[BT SERVICE] { Message } | Frame: {btParams.Frame.Number}"); - } - } -} diff --git a/data/EffectArea.cs b/data/EffectArea.cs deleted file mode 100644 index a03b69d940c0978f076680ee4d4a732ea1ebabb0..0000000000000000000000000000000000000000 --- a/data/EffectArea.cs +++ /dev/null @@ -1,81 +0,0 @@ -namespace Quantum -{ - using Photon.Deterministic; - - public unsafe partial struct EffectArea - { - public void Update(Frame frame, EntityRef entity) - { - StateTime -= frame.DeltaTime; - - if (State == EEffectAreaState.Init) - { - if (StateTime <= FP._0) - { - State = EEffectAreaState.Active; - TickCount = System.Math.Max((byte)1, TickCount); - - var behaviors = frame.ResolveList(Behaviors); - - for (int idx = 0, count = behaviors.Count; idx < count; idx++) - { - behaviors.GetPointer(idx)->Initialize(Level); - } - } - } - - if (State == EEffectAreaState.Active) - { - if (StateTime <= FP._0) - { - TickCount -= 1; - ProcessEffect(frame, entity); - - StateTime = TickTime; - } - - if (TickCount == 0) - { - State = EEffectAreaState.Finished; - StateTime = FP._0_50; - } - } - - if (State == EEffectAreaState.Finished) - { - if (StateTime <= FP._0) - { - frame.Destroy(entity); - } - } - } - - private void ProcessEffect(Frame frame, EntityRef entity) - { - var position = frame.Unsafe.GetPointer(entity)->Position; - var behaviors = frame.ResolveList(Behaviors); - - foreach (var targetPair in frame.Unsafe.GetComponentBlockIterator()) - { - if (TargetType == EEffectAreaTarget.Enemy && targetPair.Component->OwnerPlayerRef == Owner) - continue; - if (TargetType == EEffectAreaTarget.Friendly && targetPair.Component->OwnerPlayerRef != Owner) - continue; - - var targetPosition = frame.Unsafe.GetPointer(targetPair.Entity)->Position; - var distance = (targetPosition - position).SqrMagnitude; - - var radiusSqr = Radius + targetPair.Component->Size; - radiusSqr *= radiusSqr; - - if (distance > radiusSqr) - continue; - - for (int idx = 0, count = behaviors.Count; idx < count; idx++) - { - behaviors.GetPointer(idx)->ProcessEffect(frame, entity, targetPair.Entity, Level); - } - } - } - } -} diff --git a/data/EffectArea.qtn b/data/EffectArea.qtn deleted file mode 100644 index afc97d7b46a7f04634b27104ad8c3556b8c2974f..0000000000000000000000000000000000000000 --- a/data/EffectArea.qtn +++ /dev/null @@ -1,42 +0,0 @@ -component EffectArea -{ - [ExcludeFromPrototype] list Behaviors; - [ExcludeFromPrototype] PlayerRef Owner; - [ExcludeFromPrototype] FP Radius; - [ExcludeFromPrototype] FP StateTime; - [ExcludeFromPrototype] EEffectAreaState State; - [ExcludeFromPrototype] FP TickTime; - [ExcludeFromPrototype] byte TickCount; - [ExcludeFromPrototype] byte Level; - [ExcludeFromPrototype] EEffectAreaTarget TargetType; -} - -enum EEffectAreaState : Byte -{ - Init, - Active, - Finished, -} - -enum EEffectAreaTarget : Byte -{ - Enemy, - Friendly, -} - -union EffectAreaBehavior -{ - EffectAreaBehavior_Damage Damage; - EffectAreaBehavior_Buff Buff; -} - -struct EffectAreaBehavior_Damage -{ - FP Damage; - FP DamagePerLevelPercent; -} - -struct EffectAreaBehavior_Buff -{ - AssetRefEntityPrototype Buff; -} diff --git a/data/EffectAreaBehavior.cs b/data/EffectAreaBehavior.cs deleted file mode 100644 index e77a04880afea8589de9a2f3c6fbf1a5e21885d8..0000000000000000000000000000000000000000 --- a/data/EffectAreaBehavior.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Quantum -{ - public unsafe partial struct EffectAreaBehavior - { - public void Initialize(byte level) - { - switch (Field) - { - case DAMAGE: _Damage.Initialize(level); break; - - case BUFF: break; - - default: - throw new System.NotImplementedException(); - } - } - - public void ProcessEffect(Frame frame, EntityRef entity, EntityRef target, byte level) - { - switch (Field) - { - case DAMAGE: _Damage.ProcessEffect(frame, entity, target); break; - case BUFF: _Buff.ProcessEffect(frame, entity, target, level); break; - - default: - throw new System.NotImplementedException(); - } - } - } -} diff --git a/data/EffectAreaBehavior_Buff.cs b/data/EffectAreaBehavior_Buff.cs deleted file mode 100644 index 8027d25a34de7cffb70bff433da62e36b4f1d3c2..0000000000000000000000000000000000000000 --- a/data/EffectAreaBehavior_Buff.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Quantum -{ - public unsafe partial struct EffectAreaBehavior_Buff - { - // PUBLIC METHODS - - public void ProcessEffect(Frame frame, EntityRef entity, EntityRef target, byte level) - { - if (frame.Unsafe.TryGetPointer(target, out var buffs) == false) - return; - - - buffs->AddBuff(frame, entity, target, Buff, level); - } - } -} diff --git a/data/EffectAreaBehavior_Damage.cs b/data/EffectAreaBehavior_Damage.cs deleted file mode 100644 index cbc491277fa6aaca104331585d8fb869c6dc304d..0000000000000000000000000000000000000000 --- a/data/EffectAreaBehavior_Damage.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Quantum -{ - using Photon.Deterministic; - - public unsafe partial struct EffectAreaBehavior_Damage - { - public void Initialize(byte level) - { - var perLevel = FP._1 + DamagePerLevelPercent * FP._0_01; - - for (int idx = 1; idx < level; idx++) - { - Damage *= perLevel; - } - } - - public void ProcessEffect(Frame frame, EntityRef entity, EntityRef target) - { - var position = frame.Unsafe.GetPointer(entity)->Position; - var healthData = new HealthData() - { - Action = EHealthAction.Remove, - Value = Damage, - Target = target, - }; - - var targetHealth = frame.Unsafe.GetPointer(target); - targetHealth->ApplyHealthData(frame, healthData); - } - } -} diff --git a/data/EffectAreaSettings.cs b/data/EffectAreaSettings.cs deleted file mode 100644 index 4a3e352701af07e1a2b3eb22a4a196ac596f4e23..0000000000000000000000000000000000000000 --- a/data/EffectAreaSettings.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Quantum -{ - using Quantum.Prototypes; - using Quantum.Inspector; - using Photon.Deterministic; - - [System.Serializable] - public class EffectAreaSettings : CardSettings - { - [Header("Effect Area")] - public FP TickTime; - public byte TickCount; - public FP Radius; - public EEffectAreaTarget Target; - [Space] - public EffectAreaBehavior_Prototype[] Behaviors; - } -} diff --git a/data/EffectAreaSystem.cs b/data/EffectAreaSystem.cs deleted file mode 100644 index 0b4b22f582bd4e139c923dbeb9c82fcce27dabfc..0000000000000000000000000000000000000000 --- a/data/EffectAreaSystem.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Quantum -{ - unsafe class EffectAreaSystem : SystemMainThread - { - public override void Update(Frame frame) - { - foreach (var pair in frame.Unsafe.GetComponentBlockIterator()) - { - pair.Component->Update(frame, pair.Entity); - } - } - } -} diff --git a/data/Fixed Point.txt b/data/Fixed Point.txt deleted file mode 100644 index 3d107969b57306407d96400302a86735fe3ef1ec..0000000000000000000000000000000000000000 --- a/data/Fixed Point.txt +++ /dev/null @@ -1,256 +0,0 @@ -Introduction -In Quantum the FP struct (Fixed Point) completely replaces all usages of floats and doubles to ensure cross-platform determinism. It offers versions of common math data structures like FPVector2, FPVector3, FPMatrix, FPQuaternion, RNGSession, FPBounds2, etc. All systems in Quantum, including physics and navigation, exclusively use FP values in their computations. - -The fixed-point type implemented in Quantum is Q48.16. It has proved to a good balance between precision and performance with a bias towards the latter. - -Internally FP uses one long to represent the combined fixed-point number (whole number + decimal part); the long value can be accessed and set via FP.RawValue. - -Quantum's FP Math uses carefully tuned look up tables for fast trigonometric and square root functions (see QuantumSDK\quantum_unity\Assets\Photon\Quantum\Resources\LUT). - -Back To Top - - -Parsing FPs -The representable FP fraction is limited and never as accurate as a double. Parsing is an approximation and will round to the nearest possible FP precision. This is reflected in: - -parsing an FP as the float 1.1f and then converting back to float possibly resulting in 1.09999999f; and, -different parsing methods yielding different results on the same machine. -FP.FromFloat(1.1f).RawValue != FP.FromString("1.1").RawValue -Back To Top - - -TLDR -Use from float only during edit time, never inside the simulation or at runtime. -It is best to convert from raw whenever possible. -Back To Top - - -FP.FromFloat_UNSAFE() -Converting from float is not deterministic due to rounding errors and should never be done inside the simulation. Doing such a conversion in the simulation will cause desyncs 100% of the time. - -However, it can be used during edit or build time, when the converted (FP) data is first created and then shared with everyone. IMPORTANT: Data generated this way on different machines may not be compatible. - -var v = FP.FromFloat_UNSAFE(1.1f); -Back To Top - - -FP.FromString_UNSAFE() -This will internally parse the string as a float and then convert to FP. All caveats from FromFloat_UNSAFE() apply here as well. - -var v = FP.FromFloat_UNSAFE("1.1"); -Back To Top - - -FP.FromString() -This is deterministic and therefore safe to use anywhere but may not be the most performant option. A typical use case is balancing information (patch) that clients load from a server and then use to update data in Quantum assets. - -Be aware of the string locale! It only parses English number formatting for decimals and requires a dot (e.g. 1000.01f). - -var v = FP.FromFloat("1.1"); -Back To Top - - -FP.FromRaw() -This is secure and fast as it mimics the internal representation. - -var v = FP.FromRaw(72089); -This snippet can be used to create a FP converter window in Unity for convient conversion. - -using System; -using UnityEditor; -using Photon.Deterministic; - -public class FPConverter : EditorWindow { - private float _f; - private FP _fp; - - [MenuItem("Quantum/FP Converter")] - public static void ShowWindow() { - GetWindow(typeof(FPConverter), false, "FP Converter"); - } - - public virtual void OnGUI() { - _f = EditorGUILayout.FloatField("Input", _f); - try { - _fp = FP.FromFloat_UNSAFE(_f); - var f = FPPropertyDrawer.GetRawAsFloat(_fp.RawValue); - var rect = EditorGUILayout.GetControlRect(true); - EditorGUI.FloatField(rect, "Output FP", f); - QuantumEditorGUI.Overlay(rect, "(FP)"); - EditorGUILayout.LongField("Output Raw", _fp.RawValue); - } - catch (OverflowException e) { - EditorGUILayout.LabelField("Out of range"); - } - } -} -Back To Top - - -Const Variables -The `FP._1_10` syntax can not be extended or generated. -FP is a struct and can therefore not be used as a constant. It is, however, possible to hard-code and use "FP" values in const variables: - -Combine pre-defined FP._1 static getters or FP.Raw._1 const variables. -FP foo = FP._1 + FP._0_10; -// or -foo.RawValue = FP.Raw._1 + FP.Raw._0_10; -const long MagicNumber = FP.Raw._1 + FP.Raw._0_10; - -FP foo = default; -foo.RawValue = MagicNumber; -// or -foo = FP.FromRaw(MagicNumber); -Convert the specific float once to FP and save the raw value as a constant -const long MagicNumber = 72089; // 1.1 - -var foo = FP.FromRaw(MagicNumber); -// or -foo.RawValue = MagicNumber; -Create the constant inside the Quantum DSL -#define FPConst 1.1 -Then use like this: - -var foo = default(FP); -foo += Constants.FPConst; -// or -foo.RawValue += Constants.Raw.FPConst; -It will generate code to represent the constant in the following way: - -public static unsafe partial class Constants { - public const Int32 PLAYER_COUNT = 8; - /// 1.100006 - - public static FP FPConst { - [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - FP result; - result.RawValue = 72090; - return result; - } - } - public static unsafe partial class Raw { - /// 1.100006 - public const Int64 FPConst = 72090; - } -} -Define readonly static variables in the class. -private readonly static FP MagicNumber = FP._1 + FP._0_10; -There is a performance penalty compared to const variables and don't forget to mark readonly because randomly changing the value during runtime could lead to desyncs. - -Back To Top - - -Casting -Implicit casting to FP from int, uint, short, ushort, byte, sbyte is allowed and safe. - -FP v = (FP)1; -FP v = 1; -Explicit casting from FP to float or double is possible, but obviously should not be used inside the simulation. - -var v = (double)FP._1; -var v = (float)FP._1; -Casting to integer and back is safe though. - -FP v = (FP)(int)FP._1; -Unsafe casts are marked as [obsolete] and will cause a InvalidOperationException. - -FP v = 1.1f; // ERROR -FP v = 1.1d; // ERROR -Back To Top - - -Inlining -All low-level Quantum systems use manual inlined FP arithmetic to extract every ounce of performance possible. Fixed point math uses integer division and multiplication. To achieve this, the result or dividend are bit-shifted by the FP-Precision (16) before or after the calculation. - -var v = parameter * FP._0_01; - -// inlined integer math -FP v = default; -v.RawValue = (parameter.RawValue * FP._0_01.RawValue) >> FPLut.PRECISION; -var v = parameter / FP._0_01; - -// inlined integer math -FP v = default; -v.RawValue = (parameter.RawValue << FPLut.PRECISION) / FP._0_01.RawValue; -Back To Top - - -Overflow -FP.UseableMax represents the highest FP number that can be multiplied with itself and not cause an overflow (exceeding long range). - -FP.UseableMax - Decimal: 32767.9999847412 - Raw: 2147483647 - Binary: 1111111111111111111111111111111 = 31 bit -FP.UseableMin - Decimal: -32768 - Raw: -2147483648 - Binary: 10000000000000000000000000000000 = 32 bit -Back To Top - - -Precision -The general FP precision is decent when the numbers are kept within a certain range (0.01..1000). FP-math related precision problems usually produce inaccurate results and tend to make systems (based on math) unstable. A very common case is to multiply very high or small numbers and than returning to the original range by division for example. The resulting numbers lose precision. - -Another example is this method excerpt from ClosestDistanceToTriangle. Where t0 is calculated from multiplying two dot products with each other, where a dot-product is already also a result of multiplications. This is a problem when very accurate results are expected. A way to mitigate this issue is shifting the values artificially before the calculation then shift the result back. This will work when the ranges of the input are somewhat known. - -var diff = p - v0; -var edge0 = v1 - v0; -var edge1 = v2 - v0; -var a00 = Dot(edge0, edge0); -var a01 = Dot(edge0, edge1); -var a11 = Dot(edge1, edge1); -var b0 = -Dot(diff, edge0); -var b1 = -Dot(diff, edge1); -var t0 = a01 * b1 - a11 * b0; -var t1 = a01 * b0 - a00 * b1; -// ... -closestPoint = v0 + t0 * edge0 + t1 * edge1; -Back To Top - - -FPAnimationCurves -Unity comes with a set of tools which are useful to express some values in the form of curves. It comes with a custom editor for such curves, which are then serialised and can be used in runtime in order to evaluate the value of the curve in some specific point. - -There are many situations where curves can be used, such as expressing steering information when implementing vehicles, the utility value during the decision making for an AI agent (as done in Bot SDK's Utility Theory), getting the multiplier value for some attack's damage, and so on. - -The Quantum SDK already comes with its own implementation of an animation curve, named FPAnimationCurve, evaluated as FPs. This custom type, when inspected directly on Data Assets and Components in Unity, are drawn with Unity's default Animation Curve editors, whose data are then internally baked into the deterministic type. - -Back To Top - - -Polling Data From An FPAnimationCurve -The code needed to poll some data from a curve, with Quantum code, is very similar to the Unity's version: - -// This returns the pre-baked value, interpolated accordingly to the curve's configuration such as it's key points, curve's resolution, tangets modes, etc -FP myValue = myCurve.Evaluate(FP._0_50); -Back To Top - - -Creating FPAnimationCurves Directly On The Simulation -Here are the snippets to create a deterministic animation curve from scratch, directly on the simulation: - -// Creating a simple, linear curve with five key points -// Change the parameter as prefered -public static class FPAnimationCurveUtils -{ - public static FPAnimationCurve CreateLinearCurve(FPAnimationCurve.WrapMode preWrapMode, FPAnimationCurve.WrapMode postWrapMode) - { - return new FPAnimationCurve - { - Samples = new FP[5] { FP._0, FP._0_25, FP._0_50, FP._0_75, FP._1 }, - PostWrapMode = (int)postWrapMode, - PreWrapMode = (int)preWrapMode, - StartTime = 0, - EndTime = 1, - Resolution = 32 - }; - } -} -// Storing a curve into a local variable -var curve = FPAnimationCurveUtils.CreateLinearCurve(FPAnimationCurve.WrapMode.Clamp, FPAnimationCurve.WrapMode.Clamp); - -// It can also be used directly to pre-initialise a curve in an asset -public unsafe partial class CollectibleData -{ - public FPAnimationCurve myCurve = FPAnimationCurveUtils.CreateLinearCurv \ No newline at end of file diff --git a/data/Frame.User.cs b/data/Frame.User.cs deleted file mode 100644 index 58c94b3117434a064fb35156ed0cab4aef767db2..0000000000000000000000000000000000000000 --- a/data/Frame.User.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Quantum -{ - unsafe partial class Frame - { - } -} diff --git a/data/FrameContext.User.cs b/data/FrameContext.User.cs deleted file mode 100644 index 5a3b8b0d45f0b31a0f4233451086c1d86655004e..0000000000000000000000000000000000000000 --- a/data/FrameContext.User.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Quantum -{ - public partial class FrameContextUser - { - - } -} \ No newline at end of file diff --git a/data/GOAP.User.cs b/data/GOAP.User.cs deleted file mode 100644 index c1e35b2bfaaea73659018806d8e72e014d937c89..0000000000000000000000000000000000000000 --- a/data/GOAP.User.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Quantum -{ - public unsafe partial struct GOAPAgent - { - public AIConfig GetConfig(Frame frame) - { - return frame.FindAsset(Config.Id); - } - } -} diff --git a/data/GOAP.qtn b/data/GOAP.qtn deleted file mode 100644 index a2c3ad40ef5fdefab640ffb944c08c7a8cfe9941..0000000000000000000000000000000000000000 --- a/data/GOAP.qtn +++ /dev/null @@ -1,39 +0,0 @@ -#define MAX_PLAN_SIZE 6 - -asset GOAPRoot; -asset GOAPGoal; -asset GOAPAction; -asset GOAPBackValidation; - -component GOAPAgent -{ - asset_ref Root; - AssetRefAIConfig Config; - - [ExcludeFromPrototype] - GOAPState CurrentState; - - [ExcludeFromPrototype] - asset_ref CurrentGoal; - [ExcludeFromPrototype] - GOAPState GoalState; - - [ExcludeFromPrototype] - sbyte CurrentActionIndex; - [ExcludeFromPrototype] - sbyte LastProcessedActionIndex; - [ExcludeFromPrototype] - byte CurrentPlanSize; - [ExcludeFromPrototype] - array>[MAX_PLAN_SIZE] Plan; - - [ExcludeFromPrototype] - FP CurrentActionTime; - [ExcludeFromPrototype] - FP CurrentGoalTime; - [ExcludeFromPrototype] - FP InterruptionCheckCooldown; - - [ExcludeFromPrototype] - list GoalDisableTimes; -} \ No newline at end of file diff --git a/data/GOAPAStar.cs b/data/GOAPAStar.cs deleted file mode 100644 index 8127ae3b80267dbdce50a29d56e098aa992a7f2b..0000000000000000000000000000000000000000 --- a/data/GOAPAStar.cs +++ /dev/null @@ -1,311 +0,0 @@ -using System; -using System.Collections.Generic; -using Photon.Deterministic; - -namespace Quantum -{ - using System.Runtime.InteropServices; - - [StructLayout(LayoutKind.Auto)] - public struct GOAPNode - { - public int Hash; - public byte Depth; - public int Parent; - public sbyte ActionIndex; - public GOAPState State; - public short F; - public short G; - - public string ToString(GOAPAction[] actions) - { - string action = ActionIndex >= 0 && ActionIndex < actions.Length ? actions[ActionIndex].Path : "NoAction"; - return $"{action}, real cost (G): {G / 100f}, total heuristic cost (F): {F / 100f}, hash: {Hash}, parent: {Parent}"; - } - } - - public struct StateBackValidation - { - public GOAPState ValidatedState; - public FP CostToNextState; - - public StateBackValidation(GOAPState validatedState, FP costToNextState) - { - ValidatedState = validatedState; - CostToNextState = costToNextState; - } - } - - public unsafe class GOAPAStar - { - public delegate int HeuristicCost(GOAPState fromState, GOAPState toState); - - // PUBLIC MEMBERS - - public StatisticsData Statistics; - - // PRIVATE MEMBERS - - private readonly Dictionary _active = new Dictionary(); - private readonly Dictionary _closed = new Dictionary(); - - private readonly GOAPHeap _open = new GOAPHeap(); - - private ActionData[] _actionData; - private List _plan = new List(16); - - private List _planStateValidations = new List(8); - - // PUBLIC METHODS - - public List Run(Frame frame, GOAPEntityContext context, GOAPState start, GOAPState end, GOAPGoal goal, - GOAPAction[] availableActions, HeuristicCost heuristic, int maxPlanSize) - { - Statistics = default; - - int foundPathEndHash = BackwardAStar(frame, context, start, end, goal, availableActions, heuristic, maxPlanSize); - - Statistics.Success = foundPathEndHash != 0; - Statistics.ClosedNodes = _closed.Count; - Statistics.OpenNodes = _open.Size; - Statistics.ActiveNodes = _active.Count; - - if (foundPathEndHash == 0) - return null; - - _plan.Clear(); - - int nodeHash = foundPathEndHash; - while (nodeHash != 0) - { - if (_closed.TryGetValue(nodeHash, out GOAPNode node)) - { - if (node.ActionIndex >= 0) - { - _plan.Add(availableActions[node.ActionIndex]); - } - - nodeHash = node.Parent; - } - } - - return _plan; - } - - // PRIVATE METHODS - - private int BackwardAStar(Frame frame, GOAPEntityContext context, GOAPState start, GOAPState end, GOAPGoal goal, - GOAPAction[] availableActions, HeuristicCost heuristic, int maxPlanSize) - { - _open.Clear(); - _closed.Clear(); - _active.Clear(); - - PrepareActionData(ref _actionData, availableActions.Length); - - GOAPNode startNode = new GOAPNode - { - Hash = end.GetHashCode(), - State = end, - F = 0, - G = 0, - Depth = 0, - ActionIndex = (sbyte)-1, - }; - - _open.Push(startNode); - _active.Add(startNode.Hash, startNode); - - while (_open.Size > 0) - { - GOAPNode currentNode = _open.Pop(); - _closed.Add(currentNode.Hash, currentNode); - - //Log.Warn($"Closing node {currentNode.ToString()}"); - - if (start.Contains(currentNode.State) == true) - return currentNode.Hash; - - if (currentNode.Depth >= maxPlanSize) - continue; - - for (int i = availableActions.Length - 1; i >= 0; i--) - { - var action = availableActions[i]; - var actionData = _actionData[i]; - - if (actionData.IsProcessed == true && actionData.IsValid == false) - continue; - - // Check if action can satisfy state at least partially (backward search) - if (currentNode.State.ContainsAny(action.Effects) == false) - continue; - - // Check if action will not incorrectly override current state (backward search) - // Note: Next few lines are a bit difficult to understand. - // Do not modify it unless you know what you are doing. - // Mistake here will lead in failed backward search in more complex situations. - // Help: Continued state says how state should look like after this action will be executed. - // With applied state we are checking whether the current state contains continued state, - // merge helps with checking only part of the current state that matters. - var continuedState = GOAPState.Merge(action.Conditions, action.Effects); - var appliedState = GOAPState.Merge(continuedState, currentNode.State); - - if (appliedState.Contains(continuedState) == false) - continue; - - // We are validating action after we decide it fits the plan to not do this - // potentially expensive call unnecessary - if (actionData.IsProcessed == false) - { - actionData.IsValid = action.ValidateAction(frame, context, start, out FP cost) && cost > 0; - actionData.Cost = cost; - actionData.IsProcessed = true; - - Assert.Check(cost > 0, $"GOAP: Action cost has to be greater than zero. Action: {action.Path} Cost: {cost}"); - - if (actionData.IsValid == false) - { - Statistics.ValidationReturns++; - continue; - } - } - - Statistics.ValidationCalls++; - - // Remove effects, apply conditions to state (= backward apply action) - GOAPState newState = GOAPState.Remove(currentNode.State, action.Effects); - newState.Merge(action.Conditions); - - if (action.UsePlanStateValidation == true) - { - _planStateValidations.Clear(); - - action.ValidatePlanState(frame, context, newState, currentNode.State, actionData.Cost, _planStateValidations); - - Statistics.PlanStateValidationCalls++; - - // With plan state validation the plan can branch - for (int j = 0; j < _planStateValidations.Count; j++) - { - var validation = _planStateValidations[j]; - TryAddNode(currentNode, validation.ValidatedState, i, validation.CostToNextState, start, heuristic); - } - } - else - { - TryAddNode(currentNode, newState, i, actionData.Cost, start, heuristic); - } - } - } - - return 0; - } - - private void TryAddNode(GOAPNode currentNode, GOAPState newState, int actionIndex, FP actionCost, GOAPState start, HeuristicCost heuristic) - { - int newStateHash = newState.GetHashCode(); - - if (_closed.ContainsKey(newStateHash) == true) - { - Statistics.InClosedReturns++; - return; - } - - short h = (short)(heuristic(start, newState) * 100); - short g = (short)(currentNode.G + actionCost * 100); - - Statistics.ProcessedNodes++; - - GOAPNode node = new GOAPNode - { - Hash = newStateHash, - State = newState, - ActionIndex = (sbyte)actionIndex, - G = g, - F = (short)(h + g), - Parent = currentNode.Hash, - Depth = (byte)(currentNode.Depth + 1), - }; - - if (_active.TryGetValue(node.Hash, out GOAPNode existing)) - { - if (node.F >= existing.F) - return; - - _open.Update(node); - _active[node.Hash] = node; - - //Log.Warn($"Updating node {node.ToString()}"); - } - else - { - _open.Push(node); - _active.Add(node.Hash, node); - - //Log.Warn($"Adding node {node.ToString()}"); - } - } - - private static void PrepareActionData(ref ActionData[] actionData, int length) - { - int originalLength = actionData != null ? actionData.Length : 0; - - if (originalLength < length) - { - Array.Resize(ref actionData, length); - } - - for (int i = 0; i < length; i++) - { - if (i < originalLength) - { - actionData[i].IsProcessed = false; - } - else - { - actionData[i] = new ActionData(); - } - } - } - - private void PrintHeap(GOAPAction[] actions) - { - string heap = "HEAP: "; - int index = 0; - - foreach (GOAPNode node in _open) - { - heap += $"{index}: COST: {node.F} {actions[node.ActionIndex].Path}\n"; - index++; - } - - Log.Info(heap); - } - - private class ActionData - { - public bool IsProcessed; - public bool IsValid; - public FP Cost; - } - - public struct StatisticsData - { - public bool Success; - public int ClosedNodes; - public int OpenNodes; - public int ActiveNodes; - public int ValidationCalls; - public int ValidationReturns; - public int PlanStateValidationCalls; - public int InClosedReturns; - public int ProcessedNodes; - - public new string ToString() - { - return $"Success: {Success}, Closed: {ClosedNodes}, Open: {OpenNodes}, Active: {ActiveNodes}\nValidation calls: {ValidationCalls}\nValidation returns: {ValidationReturns}\nPlan State Validation calls: {PlanStateValidationCalls}\nIn Closed returns: {InClosedReturns}\n Processed nodes: {ProcessedNodes}"; - } - } - } -} \ No newline at end of file diff --git a/data/GOAPAction.cs b/data/GOAPAction.cs deleted file mode 100644 index 1ea9cc74ba2e37bfc4dbcb9e26cb98e6ab99947b..0000000000000000000000000000000000000000 --- a/data/GOAPAction.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Photon.Deterministic; -using System.Collections.Generic; - -namespace Quantum -{ - public abstract unsafe partial class GOAPAction - { - public enum EResult - { - Continue, - IsDone, - IsFailed, - } - - // PUBLIC MEMBERS - - public string Label; - - [BotSDKHidden] - public GOAPState Conditions; - [BotSDKHidden] - public GOAPState Effects; - - public bool Interruptible; - - public abstract bool UsePlanStateValidation { get; } - - // PUBLIC METHODS - - public virtual bool ValidateAction(Frame frame, GOAPEntityContext context, GOAPState startState, out FP cost) - { - cost = 1; - return true; - } - - public virtual void ValidatePlanState(Frame frame, GOAPEntityContext context, GOAPState stateToValidate, GOAPState nextState, FP costToNextState, List validatedStates) - { - } - - public virtual void Activate(Frame frame, GOAPEntityContext context) - { - } - - public virtual EResult Update(Frame frame, GOAPEntityContext context) - { - return EResult.Continue; - } - - public virtual void Deactivate(Frame frame, GOAPEntityContext context) - { - } - } -} \ No newline at end of file diff --git a/data/GOAPBackValidation.cs b/data/GOAPBackValidation.cs deleted file mode 100644 index 9b4bef4e9b17b142ada7a2feb6b4e317d1d336b8..0000000000000000000000000000000000000000 --- a/data/GOAPBackValidation.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using Photon.Deterministic; - - -namespace Quantum -{ - public abstract partial class GOAPBackValidation - { - public abstract void ValidatePlanState(Frame frame, EntityRef entity, GOAPState stateToValidate, GOAPState nextState, FP costToNextState, List validatedStates); - } - - public abstract class GOAPSingleBackValidation : GOAPBackValidation - { - public override sealed void ValidatePlanState(Frame frame, EntityRef entity, GOAPState stateToValidate, GOAPState nextState, FP costToNextState, - List validatedStates) - { - if (ValidatePlanState(frame, entity, ref stateToValidate, nextState, ref costToNextState) == true) - { - validatedStates.Add(new StateBackValidation(stateToValidate, costToNextState)); - } - } - - protected virtual bool ValidatePlanState(Frame frame, EntityRef entity, ref GOAPState stateToValidate, GOAPState nextState, ref FP costToNextState) - { - return false; - } - } -} \ No newline at end of file diff --git a/data/GOAPDefaultAction.cs b/data/GOAPDefaultAction.cs deleted file mode 100644 index 5452b51d23a9bf51c27b121edcc9ca97687649d9..0000000000000000000000000000000000000000 --- a/data/GOAPDefaultAction.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Photon.Deterministic; -using System.Collections.Generic; -using System; - -namespace Quantum -{ - [Serializable] - public unsafe partial class GOAPDefaultAction : GOAPAction - { - // PUBLIC MEMBERS - - public AIParamBool Validation = true; - public AIParamFP Cost = FP._1; - public AssetRefGOAPBackValidation PlanStateValidationLink; - - public AssetRefAIAction[] OnActivateLinks; - public AssetRefAIAction[] OnUpdateLinks; - public AssetRefAIAction[] OnDeactivateLinks; - - public AIParamBool IsDone; - public AIParamBool IsFailed; - - [NonSerialized] - public GOAPBackValidation PlanStateValidation; - - [NonSerialized] - public AIAction[] OnActivate; - [NonSerialized] - public AIAction[] OnUpdate; - [NonSerialized] - public AIAction[] OnDeactivate; - - public override bool UsePlanStateValidation => PlanStateValidation != null; - - // PUBLIC METHODS - - public override bool ValidateAction(Frame frame, GOAPEntityContext context, GOAPState startState, out FP cost) - { - cost = FP.MaxValue; - - if (Validation.Resolve(frame, context.Entity, context.Blackboard, context.Config) == false) - return false; - - cost = Cost.Resolve(frame, context.Entity, context.Blackboard, context.Config); - - return cost < FP.MaxValue; - } - - public override void ValidatePlanState(Frame frame, GOAPEntityContext context, GOAPState stateToValidate, GOAPState nextState, FP costToNextState, List validatedStates) - { - PlanStateValidation.ValidatePlanState(frame, context.Entity, stateToValidate, nextState, costToNextState, validatedStates); - } - - public override void Activate(Frame frame, GOAPEntityContext context) - { - ExecuteActions(frame, context.Entity, OnActivate); - } - - public override EResult Update(Frame frame, GOAPEntityContext context) - { - if (IsDone.Resolve(frame, context.Entity, context.Blackboard, context.Config) == true) - return EResult.IsDone; - - if (IsFailed.Resolve(frame, context.Entity, context.Blackboard, context.Config) == true) - return EResult.IsFailed; - - ExecuteActions(frame, context.Entity, OnUpdate); - - return EResult.Continue; - } - - public override void Deactivate(Frame frame, GOAPEntityContext context) - { - ExecuteActions(frame, context.Entity, OnDeactivate); - } - - // AssetObject INTERFACE - - public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator) - { - base.Loaded(resourceManager, allocator); - - PlanStateValidation = (GOAPBackValidation)resourceManager.GetAsset(PlanStateValidationLink.Id); - - OnActivate = new AIAction[OnActivateLinks == null ? 0 : OnActivateLinks.Length]; - for (int i = 0; i < OnActivate.Length; i++) - { - OnActivate[i] = (AIAction)resourceManager.GetAsset(OnActivateLinks[i].Id); - } - - OnUpdate = new AIAction[OnUpdateLinks == null ? 0 : OnUpdateLinks.Length]; - for (int i = 0; i < OnUpdate.Length; i++) - { - OnUpdate[i] = (AIAction)resourceManager.GetAsset(OnUpdateLinks[i].Id); - } - - OnDeactivate = new AIAction[OnDeactivateLinks == null ? 0 : OnDeactivateLinks.Length]; - for (int i = 0; i < OnDeactivate.Length; i++) - { - OnDeactivate[i] = (AIAction)resourceManager.GetAsset(OnDeactivateLinks[i].Id); - } - } - - // PRIVATE METHODS - - private static void ExecuteActions(Frame frame, EntityRef entity, AIAction[] actions) - { - for (int i = 0; i < actions.Length; i++) - { - var action = actions[i]; - - action.Update(frame, entity); - - int nextAction = action.NextAction(frame, entity); - if (nextAction > i) - { - i = nextAction; - } - } - } - } -} \ No newline at end of file diff --git a/data/GOAPDefaultGoal.cs b/data/GOAPDefaultGoal.cs deleted file mode 100644 index d008fbeba8fb71e49cd56631a7fd13e4e04e1c5c..0000000000000000000000000000000000000000 --- a/data/GOAPDefaultGoal.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - [Serializable] - public unsafe partial class GOAPDefaultGoal : GOAPGoal - { - // PUBLIC MEMBERS - - public AIParamBool Validation = true; - public AIParamFP Relevancy = FP._1; - public AIParamFP DisableTime; - public AssetRefAIAction[] OnInitPlanningLinks; - public AssetRefAIAction[] OnActivateLinks; - public AssetRefAIAction[] OnDeactivateLinks; - public AIParamBool IsFinished; - - [NonSerialized] - public AIAction[] OnInitPlanning; - [NonSerialized] - public AIAction[] OnActivate; - [NonSerialized] - public AIAction[] OnDeactivate; - - // PUBLIC METHODS - - public override FP GetRelevancy(Frame frame, GOAPEntityContext context) - { - if (Validation.Resolve(frame, context.Entity, context.Blackboard, context.Config) == false) - return 0; - - return Relevancy.Resolve(frame, context.Entity, context.Blackboard, context.Config); - } - - public override void InitPlanning(Frame frame, GOAPEntityContext context, ref GOAPState startState, ref GOAPState targetState) - { - base.InitPlanning(frame, context, ref startState, ref targetState); - - ExecuteActions(frame, context.Entity, OnInitPlanning); - } - - public override void Activate(Frame frame, GOAPEntityContext context) - { - base.Activate(frame, context); - - ExecuteActions(frame, context.Entity, OnActivate); - } - - public override void Deactivate(Frame frame, GOAPEntityContext context) - { - ExecuteActions(frame, context.Entity, OnDeactivate); - - base.Deactivate(frame, context); - } - - public override bool HasFinished(Frame frame, GOAPEntityContext context) - { - if (base.HasFinished(frame, context) == true) - return true; - - return IsFinished.Resolve(frame, context.Entity, context.Blackboard, context.Config); - } - - public override FP GetDisableTime(Frame frame, GOAPEntityContext context) - { - return DisableTime.Resolve(frame, context.Entity, context.Blackboard, context.Config); - } - - // AssetObject INTERFACE - - public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator) - { - base.Loaded(resourceManager, allocator); - - OnInitPlanning = new AIAction[OnInitPlanningLinks == null ? 0 : OnInitPlanningLinks.Length]; - for (int i = 0; i < OnInitPlanning.Length; i++) - { - OnInitPlanning[i] = (AIAction)resourceManager.GetAsset(OnInitPlanningLinks[i].Id); - } - - OnActivate = new AIAction[OnActivateLinks == null ? 0 : OnActivateLinks.Length]; - for (int i = 0; i < OnActivate.Length; i++) - { - OnActivate[i] = (AIAction)resourceManager.GetAsset(OnActivateLinks[i].Id); - } - - OnDeactivate = new AIAction[OnDeactivateLinks == null ? 0 : OnDeactivateLinks.Length]; - for (int i = 0; i < OnDeactivate.Length; i++) - { - OnDeactivate[i] = (AIAction)resourceManager.GetAsset(OnDeactivateLinks[i].Id); - } - } - - // PRIVATE METHODS - - private static void ExecuteActions(Frame frame, EntityRef entity, AIAction[] actions) - { - for (int i = 0; i < actions.Length; i++) - { - var action = actions[i]; - - action.Update(frame, entity); - - int nextAction = action.NextAction(frame, entity); - if (nextAction > i) - { - i = nextAction; - } - } - } - } -} \ No newline at end of file diff --git a/data/GOAPDefaultHeuristic.cs b/data/GOAPDefaultHeuristic.cs deleted file mode 100644 index 7d0c38e7726e49c91cdc7e97dfde03572499a9f9..0000000000000000000000000000000000000000 --- a/data/GOAPDefaultHeuristic.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Quantum -{ - public static class GOAPDefaultHeuristic - { - public static int BitmaskDifferenceUInt32(GOAPState start, GOAPState end) - { - EWorldState positiveAchieved = start.Positive & end.Positive; - EWorldState negativeAchieved = start.Negative & end.Negative; - - EWorldState positiveMissing = end.Positive & ~positiveAchieved; - EWorldState negativeMissing = end.Negative & ~negativeAchieved; - - return CountOnesUInt32((uint) positiveMissing) + CountOnesUInt32((uint) negativeMissing); - } - - public static int BitmaskDifferenceUInt64(GOAPState start, GOAPState end) - { - EWorldState positiveAchieved = start.Positive & end.Positive; - EWorldState negativeAchieved = start.Negative & end.Negative; - - EWorldState positiveMissing = end.Positive & ~positiveAchieved; - EWorldState negativeMissing = end.Negative & ~negativeAchieved; - - return CountOnesUInt64((ulong) positiveMissing) + CountOnesUInt64((ulong) negativeMissing); - } - - public static int CountOnesUInt32(uint x) - { - x = x - ((x >> 1) & 0x55555555u); - x = (x & 0x33333333u) + ((x >> 2) & 0x33333333u); - x = (x + (x >> 4)) & 0x0F0F0F0Fu; - - return (int) ((x * 0x01010101u) >> 24); - } - - public static int CountOnesUInt64(ulong x) - { - x = x - ((x >> 1) & 0x5555555555555555ul); - x = (x & 0x3333333333333333ul) + ((x >> 2) & 0x3333333333333333ul); - x = (x + (x >> 4)) & 0xF0F0F0F0F0F0F0Ful; - - return (int) (x * 0x101010101010101ul >> 56); - } - } -} \ No newline at end of file diff --git a/data/GOAPGoal.cs b/data/GOAPGoal.cs deleted file mode 100644 index b1b5dea69b735c771dbefd413f40ea7e3b213e43..0000000000000000000000000000000000000000 --- a/data/GOAPGoal.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Photon.Deterministic; - -namespace Quantum -{ - public abstract unsafe partial class GOAPGoal - { - public enum EInterruptionBehavior - { - Never, - Always, - BasedOnActions, - } - - // PUBLIC MEMBERS - - public string Label; - - [BotSDKHidden] - public GOAPState StartState; - [BotSDKHidden] - public GOAPState TargetState; - public EInterruptionBehavior InterruptionBehavior; - - // PUBLIC INTERFACE - - public virtual FP GetRelevancy(Frame frame, GOAPEntityContext context) - { - return 1; - } - - public virtual void InitPlanning(Frame frame, GOAPEntityContext context, ref GOAPState startState, ref GOAPState targetState) - { - startState.Merge(StartState); - targetState.Merge(TargetState); - } - - public virtual void Activate(Frame frame, GOAPEntityContext context) - { - } - - public virtual void Deactivate(Frame frame, GOAPEntityContext context) - { - } - - public virtual bool HasFinished(Frame frame, GOAPEntityContext context) - { - return context.Agent->CurrentState.Contains(context.Agent->GoalState); - } - - public bool IsInterruptible(GOAPAction currentAction) - { - if (InterruptionBehavior == EInterruptionBehavior.Never) - return false; - - if (InterruptionBehavior == EInterruptionBehavior.Always) - return true; - - return currentAction != null && currentAction.Interruptible; - } - - public virtual FP GetDisableTime(Frame frame, GOAPEntityContext context) - { - return 0; - } - } -} \ No newline at end of file diff --git a/data/GOAPHeap.cs b/data/GOAPHeap.cs deleted file mode 100644 index bf75fbf82cb6cc2852ae5771f94e3d51954351ca..0000000000000000000000000000000000000000 --- a/data/GOAPHeap.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Quantum -{ - public class GOAPHeap : IEnumerable, IEnumerable - { - private GOAPNode[] _heap; - private int _size; - - public GOAPHeap(int capacity) - { - _heap = new GOAPNode[capacity]; - } - - public GOAPHeap() : this(1024) - { - } - - public int Size { get { return _size; } } - - public void Clear() - { - _size = 0; - - // remove all stuff from heap - Array.Clear(_heap, 0, _heap.Length); - } - - public void Update(GOAPNode updateNode) - { - int bubbleIndex = -1; - for (int i = 0; i < _size; i++) - { - var node = _heap[i]; - if (node.Hash == updateNode.Hash) - { - bubbleIndex = i; - break; - } - } - - if (bubbleIndex < 0) - { - Log.Error($"Cannot update node: Node with hash {updateNode.Hash} is not present in the heap"); - return; - } - - _heap[bubbleIndex] = updateNode; - - while (bubbleIndex != 0) - { - int parentIndex = (bubbleIndex - 1) / 2; - - if (_heap[parentIndex].F <= updateNode.F) - break; - - _heap[bubbleIndex] = _heap[parentIndex]; - _heap[parentIndex] = updateNode; - - bubbleIndex = parentIndex; - } - } - - public void Push(GOAPNode node) - { - if (_size == _heap.Length) - { - ExpandHeap(); - } - - int bubbleIndex = _size; - _heap[bubbleIndex] = node; - - _size++; - - while (bubbleIndex != 0) - { - int parentIndex = (bubbleIndex - 1) / 2; - if (_heap[parentIndex].F <= node.F) - break; - - _heap[bubbleIndex] = _heap[parentIndex]; - _heap[parentIndex] = node; - - bubbleIndex = parentIndex; - } - } - - public GOAPNode Pop() - { - GOAPNode returnItem = _heap[0]; - _heap[0] = _heap[_size - 1]; - - _size--; - - int swapItem = 0; - int parent = 0; - - do - { - parent = swapItem; - - int leftChild = 2 * parent + 1; - int rightChild = 2 * parent + 2; - - if (rightChild <= _size) - { - int smallerChild = _heap[leftChild].F < _heap[rightChild].F ? leftChild : rightChild; - - if (_heap[parent].F >= _heap[smallerChild].F) - { - swapItem = smallerChild; - } - } - else if (leftChild <= _size) - { - // Only one child exists - if (_heap[parent].F >= _heap[leftChild].F) - { - swapItem = leftChild; - } - } - - // One if the parent's children are smaller or equal, swap them - if (parent != swapItem) - { - GOAPNode tmpIndex = _heap[parent]; - - _heap[parent] = _heap[swapItem]; - _heap[swapItem] = tmpIndex; - } - } - while (parent != swapItem); - - return returnItem; - } - - private void ExpandHeap() - { - // Double the size - GOAPNode[] newHeap = new GOAPNode[_heap.Length * 2]; - - Array.Copy(_heap, newHeap, _heap.Length); - _heap = newHeap; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (int i = 0; i < _size; i++) - { - yield return _heap[i]; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return (this as IEnumerable).GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/data/GOAPHeuristic.cs b/data/GOAPHeuristic.cs deleted file mode 100644 index ed9e0c84fcb9aff2acefb6e1890e413b06c30003..0000000000000000000000000000000000000000 --- a/data/GOAPHeuristic.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Quantum -{ - public static class GOAPHeuristic - { - public static int BitmaskDifferenceUInt32(GOAPState start, GOAPState end) - { - EWorldState positiveAchieved = start.Positive & end.Positive; - EWorldState negativeAchieved = start.Negative & end.Negative; - - EWorldState positiveMissing = end.Positive & ~positiveAchieved; - EWorldState negativeMissing = end.Negative & ~negativeAchieved; - - return CountOnesUInt32((uint) positiveMissing) + CountOnesUInt32((uint) negativeMissing); - } - - public static int BitmaskDifferenceUInt64(GOAPState start, GOAPState end) - { - EWorldState positiveAchieved = start.Positive & end.Positive; - EWorldState negativeAchieved = start.Negative & end.Negative; - - EWorldState positiveMissing = end.Positive & ~positiveAchieved; - EWorldState negativeMissing = end.Negative & ~negativeAchieved; - - return CountOnesUInt64((ulong) positiveMissing) + CountOnesUInt64((ulong) negativeMissing); - } - - public static int CountOnesUInt32(uint x) - { - x = x - ((x >> 1) & 0x55555555u); - x = (x & 0x33333333u) + ((x >> 2) & 0x33333333u); - x = (x + (x >> 4)) & 0x0F0F0F0Fu; - - return (int) ((x * 0x01010101u) >> 24); - } - - public static int CountOnesUInt64(ulong x) - { - x = x - ((x >> 1) & 0x5555555555555555ul); - x = (x & 0x3333333333333333ul) + ((x >> 2) & 0x3333333333333333ul); - x = (x + (x >> 4)) & 0xF0F0F0F0F0F0F0Ful; - - return (int) (x * 0x101010101010101ul >> 56); - } - } -} \ No newline at end of file diff --git a/data/GOAPManager.cs b/data/GOAPManager.cs deleted file mode 100644 index 53e0548ebe669a2b632b4fb078598e86ef2a0929..0000000000000000000000000000000000000000 --- a/data/GOAPManager.cs +++ /dev/null @@ -1,450 +0,0 @@ -using System; -using Photon.Deterministic; -using System.Collections.Generic; - -namespace Quantum -{ - public static unsafe class GOAPManager - { - // PUBLIC MEMBERS - - public static EntityRef DebugEntity; - - // PRIVATE MEMBERS - - private static GOAPAStar.HeuristicCost _heuristicCost; - - // PUBLIC METHODS - - public static void Initialize(Frame frame, EntityRef entity, GOAPRoot root, GOAPAStar.HeuristicCost heuristicCost = null) - { - var agent = frame.Unsafe.GetPointer(entity); - agent->Root = root; - - var disableTimes = frame.AllocateList(root.Goals.Length); - for (int i = 0; i < root.GoalRefs.Length; i++) - { - disableTimes.Add(0); - } - - agent->GoalDisableTimes = disableTimes; - - if (heuristicCost != null) - { - _heuristicCost = heuristicCost; - } - else if (_heuristicCost == null) - { - switch (sizeof(EWorldState)) - { - case 4: - _heuristicCost = GOAPHeuristic.BitmaskDifferenceUInt32; - break; - //case 8: - // _heuristicCost = GOAPHeuristic.BitmaskDifferenceUInt64; - // break; - default: - throw new NotImplementedException($"Heuristic for EWorldState size of {sizeof(EWorldState)} bytes is not implemented"); - } - } - } - - public static void Deinitialize(Frame frame, EntityRef entity) - { - var agent = frame.Unsafe.GetPointer(entity); - - agent->Root = default; - - frame.FreeList(agent->GoalDisableTimes); - agent->GoalDisableTimes = default; - } - - public static void Update(Frame frame, EntityRef entity, FP deltaTime) - { - var context = GetContext(frame, entity); - var agent = context.Agent; - - bool debug = DebugEntity == entity; - - // Update disable times - var goalDisableTimes = frame.ResolveList(agent->GoalDisableTimes); - for (int i = 0; i < goalDisableTimes.Count; i++) - { - goalDisableTimes[i] = FPMath.Max(FP._0, goalDisableTimes[i] - deltaTime); - } - - var currentGoal = agent->CurrentGoal.Id.IsValid == true ? frame.FindAsset(agent->CurrentGoal.Id) : null; - var currentAction = GetCurrentAction(frame, agent); - - if (currentGoal != null) - { - // Decrease interruption timer - agent->InterruptionCheckCooldown = FPMath.Max(agent->InterruptionCheckCooldown - deltaTime, 0); - - if (currentGoal.HasFinished(frame, context) == true) - { - StopCurrentGoal(frame, context, ref currentGoal, ref currentAction); - } - } - - if (currentGoal == null || (agent->InterruptionCheckCooldown <= 0 && currentGoal.IsInterruptible(currentAction) == true)) - { - FindNewGoal(frame, context, ref currentGoal, ref currentAction); - } - - if (currentGoal != null) - { - UpdateCurrentGoal(frame, context, deltaTime, ref currentGoal, ref currentAction); - } - - Pool.Return(context); - } - - public static void StopCurrentGoal(Frame frame, EntityRef entity) - { - var context = GetContext(frame, entity); - - var currentGoal = context.Agent->CurrentGoal.Id.IsValid == true ? frame.FindAsset(context.Agent->CurrentGoal.Id) : null; - var currentAction = GetCurrentAction(frame, context.Agent); - - StopCurrentGoal(frame, context, ref currentGoal, ref currentAction); - } - - public static void SetGoalDisableTime(Frame frame, EntityRef entity, AssetRefGOAPGoal goal, FP disableTime) - { - if (goal.Id.IsValid == false) - return; - - var agent = frame.Unsafe.GetPointer(entity); - - if (goal == agent->CurrentGoal) - { - StopCurrentGoal(frame, entity); - } - - var root = frame.FindAsset(agent->Root.Id); - int goalIndex = Array.IndexOf(root.GoalRefs, goal); - - if (goalIndex >= 0) - { - var disableTimes = frame.ResolveList(agent->GoalDisableTimes); - disableTimes[goalIndex] = disableTime; - } - } - - // PRIVATE METHODS - - private static void UpdateCurrentGoal(Frame frame, GOAPEntityContext context, FP deltaTime, ref GOAPGoal currentGoal, ref GOAPAction currentAction) - { - var agent = context.Agent; - bool debug = DebugEntity == context.Entity; - - if (currentAction != null && agent->CurrentState.Contains(currentAction.Effects) == true) - { - // This action is done, let's choose another one in next step - StopCurrentAction(frame, context, ref currentAction); - } - - // Activate next action from the plan if needed - if (currentAction == null && agent->CurrentPlanSize > 0) - { - while (agent->CurrentActionIndex < agent->CurrentPlanSize - 1) - { - agent->LastProcessedActionIndex = agent->CurrentActionIndex; - agent->CurrentActionIndex++; - - var nextAction = frame.FindAsset(agent->Plan[agent->CurrentActionIndex].Id); - - if (agent->CurrentState.Contains(nextAction.Conditions) == false) - { - // Conditions are not met, terminate whole plan - StopCurrentGoal(frame, context, ref currentGoal, ref currentAction); - break; - } - - if (agent->CurrentState.Contains(nextAction.Effects) == false) - { - // This action is valid, activate it - currentAction = nextAction; - currentAction.Activate(frame, context); - - if (debug == true) - { - Log.Info($"GOAP: Action {currentAction.Path} activated"); - } - - agent->CurrentActionTime = 0; - break; - } - } - - if (currentAction == null && currentGoal != null) - { - if (debug == true) - { - Log.Info($"GOAP: Plan execution failed: Probably last action is finished but goal is not satisfied (state might change during execution). Goal: {currentGoal.Path}"); - } - - StopCurrentGoal(frame, context, ref currentGoal, ref currentAction); - } - } - - // Update action - if (currentAction != null) - { - var result = currentAction.Update(frame, context); - - if (result == GOAPAction.EResult.IsFailed) - { - StopCurrentGoal(frame, context, ref currentGoal, ref currentAction); - } - else if (result == GOAPAction.EResult.IsDone) - { - // This action claims to be done, apply effects and next action will be chosen next Update - agent->CurrentState.Merge(currentAction.Effects); - agent->LastProcessedActionIndex = agent->CurrentActionIndex; - - StopCurrentAction(frame, context, ref currentAction); - } - - agent->CurrentActionTime += deltaTime; - } - - if (currentGoal != null) - { - agent->CurrentGoalTime += deltaTime; - } - } - - private static void StopCurrentAction(Frame frame, GOAPEntityContext context, ref GOAPAction currentAction) - { - if (currentAction == null) - return; - - if (context.Agent->Plan[context.Agent->CurrentActionIndex] != currentAction) - { - Log.Error($"GOAP: Trying to stop action {currentAction.Path} that isn't currently active."); - return; - } - - currentAction.Deactivate(frame, context); - context.Agent->LastProcessedActionIndex = context.Agent->CurrentActionIndex; - - if (context.Entity == DebugEntity) - { - Log.Info($"GOAP: Action {currentAction.Path} deactivated"); - } - - currentAction = null; - } - - private static void StopCurrentGoal(Frame frame, GOAPEntityContext context, ref GOAPGoal currentGoal, ref GOAPAction currentAction) - { - var agent = context.Agent; - - StopCurrentAction(frame, context, ref currentAction); - - if (currentGoal != null) - { - currentGoal.Deactivate(frame, context); - - if (context.Entity == DebugEntity) - { - Log.Info($"GOAP: Goal {currentGoal.Path} deactivated"); - } - - FP disableTime = currentGoal.GetDisableTime(frame, context); - if (disableTime > 0) - { - var disableTimes = frame.ResolveList(agent->GoalDisableTimes); - - int goalIndex = Array.IndexOf(context.Root.Goals, currentGoal); - if (goalIndex >= 0) - { - disableTimes[goalIndex] = disableTime; - } - } - } - - agent->CurrentActionIndex = -1; - agent->LastProcessedActionIndex = -1; - agent->CurrentActionTime = 0; - - agent->CurrentPlanSize = 0; - agent->CurrentGoal = default; - agent->CurrentGoalTime = 0; - - currentGoal = null; - currentAction = null; - } - - private static void FindNewGoal(Frame frame, GOAPEntityContext context, ref GOAPGoal currentGoal, ref GOAPAction currentAction) - { - var agent = context.Agent; - var goals = context.Root.Goals; - - GOAPGoal bestGoal = null; - FP bestRelevancy = FP.MinValue; - - var disableTimes = frame.ResolveList(agent->GoalDisableTimes); - for (int i = 0; i < goals.Length; i++) - { - if (disableTimes[i] > 0) - continue; - - var goal = goals[i]; - - var startState = agent->CurrentState; - startState.Merge(goal.StartState); - - if (startState.Contains(goal.TargetState) == true) - continue; // Goal is satisfied - - FP relevancy = goal.GetRelevancy(frame, context); - - if (relevancy <= 0) - continue; - - if (relevancy > bestRelevancy) - { - bestRelevancy = relevancy; - bestGoal = goal; - } - } - - // Reset interruption timer - agent->InterruptionCheckCooldown = context.Root.InterruptionCheckInterval; - - if (bestGoal == null || bestGoal == currentGoal) - return; - - bool debug = context.Entity == DebugEntity; - - if (debug == true) - { - Log.Info($"GOAP: New best goal found: {bestGoal.Path}"); - } - - GOAPState currentState = agent->CurrentState; - GOAPState targetState = default; - - bestGoal.InitPlanning(frame, context, ref currentState, ref targetState); - - var aStar = Pool.Get(); - List plan = null; - - if (debug == true) - { - using (new StopwatchBlock("GOAP: Backward A* search")) - { - plan = aStar.Run(frame, context, currentState, targetState, bestGoal, context.Root.Actions, _heuristicCost, Constants.MAX_PLAN_SIZE); - } - - Log.Info($"GOAP: Search data - {aStar.Statistics.ToString()}"); - } - else - { - plan = aStar.Run(frame, context, currentState, targetState, bestGoal, context.Root.Actions, _heuristicCost, Constants.MAX_PLAN_SIZE); - } - - if (plan == null) - { - if (debug == true) - { - Log.Info($"GOAP: Failed to find plan for goal {bestGoal.Path}"); - } - - int goalIndex = Array.IndexOf(goals, bestGoal); - // Ensure there will be at least one planning without this failed goal - disableTimes[goalIndex] = FPMath.Max(FP._0_50, agent->InterruptionCheckCooldown + FP._0_10); - - Pool.Return(aStar); - - return; - } - - if (currentGoal != null) - { - StopCurrentGoal(frame, context, ref currentGoal, ref currentAction); - } - - agent->CurrentGoal = bestGoal; - agent->CurrentGoalTime = 0; - agent->CurrentState = currentState; - agent->GoalState = targetState; - - agent->CurrentActionIndex = -1; - agent->LastProcessedActionIndex = -1; - agent->CurrentActionTime = 0; - agent->CurrentPlanSize = 0; - - currentGoal = bestGoal; - currentAction = null; - - for (int i = 0; i < plan.Count; i++) - { - var action = plan[i]; - if (action == null) - break; - - *agent->Plan.GetPointer(i) = action; - agent->CurrentPlanSize++; - } - - if (debug == true) - { - var planInfo = $"GOAP: Plan FOUND. Size: {agent->CurrentPlanSize} More..."; - for (int i = 0; i < agent->CurrentPlanSize; i++) - { - planInfo += $"\nAction {i + 1}: {plan[i].Path}"; - } - - Log.Info(planInfo); - } - - currentGoal.Activate(frame, context); - - if (debug == true) - { - Log.Info($"GOAP: Goal {currentGoal.Path} activated"); - } - - // Plan object is part of pooled GOAPAStar object - // so GOAPAStar needs to be returned after plan is no longer needed - Pool.Return(aStar); - } - - private static GOAPAction GetCurrentAction(Frame frame, GOAPAgent* agent) - { - if (agent->CurrentActionIndex < 0) - return null; - - if (agent->LastProcessedActionIndex >= agent->CurrentActionIndex) - return null; - - return frame.FindAsset(agent->Plan[agent->CurrentActionIndex].Id); - } - - private static GOAPEntityContext GetContext(Frame frame, EntityRef entity) - { - var context = Pool.Get(); - - context.Entity = entity; - context.Agent = frame.Unsafe.GetPointer(entity); - context.Blackboard = frame.Has(entity) ? frame.Unsafe.GetPointer(entity) : null; - context.Root = frame.FindAsset(context.Agent->Root.Id); - context.Config = frame.FindAsset(context.Agent->Config.Id); - - return context; - } - } - - public unsafe class GOAPEntityContext - { - public EntityRef Entity; - public GOAPAgent* Agent; - public GOAPRoot Root; - public AIConfig Config; - public AIBlackboardComponent* Blackboard; - } -} \ No newline at end of file diff --git a/data/GOAPRoot.cs b/data/GOAPRoot.cs deleted file mode 100644 index f44e8def57b434ece3d0d53f4dda577cdde8ca1e..0000000000000000000000000000000000000000 --- a/data/GOAPRoot.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - public partial class GOAPRoot - { - // PUBLIC MEMBERS - - public string Label; - public FP InterruptionCheckInterval = FP._0_50; - - public AssetRefGOAPGoal[] GoalRefs; - public AssetRefGOAPAction[] ActionRefs; - - [NonSerialized] - public GOAPGoal[] Goals; - [NonSerialized] - public GOAPAction[] Actions; - - // AssetObject INTERFACE - - public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator) - { - base.Loaded(resourceManager, allocator); - - Goals = new GOAPGoal[GoalRefs == null ? 0 : GoalRefs.Length]; - for (int i = 0; i < GoalRefs.Length; i++) - { - Goals[i] = (GOAPGoal)resourceManager.GetAsset(GoalRefs[i].Id); - } - - Actions = new GOAPAction[ActionRefs == null ? 0 : ActionRefs.Length]; - for (int i = 0; i < ActionRefs.Length; i++) - { - Actions[i] = (GOAPAction)resourceManager.GetAsset(ActionRefs[i].Id); - } - } - } -} \ No newline at end of file diff --git a/data/GOAPState.cs b/data/GOAPState.cs deleted file mode 100644 index d4f70e8e25af701dbff0dc5b6ff2ac94f59a2f2f..0000000000000000000000000000000000000000 --- a/data/GOAPState.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; - -namespace Quantum -{ - [Serializable] - public partial struct GOAPState - { - // PUBLIC METHODS - - public static GOAPState Merge(GOAPState state, GOAPState mergeState) - { - var newState = state; - newState.Merge(mergeState); - return newState; - } - - public static GOAPState Remove(GOAPState state, GOAPState removeState) - { - var newState = state; - newState.Remove(removeState); - return newState; - } - - public bool HasPositiveFlag(EWorldState state) - { - return (Positive & state) == state; - } - - public bool HasNegativeFlag(EWorldState state) - { - return (Negative & state) == state; - } - - public void SetFlag(EWorldState flagState, bool value) - { - if (value == true) - { - Positive = Positive | flagState; - Negative = Negative & ~Positive; - } - else - { - Negative = Negative | flagState; - Positive = Positive & ~Negative; - } - } - - public void ClearFlag(EWorldState clearState) - { - Positive = Positive & ~clearState; - Negative = Negative & ~clearState; - } - - public void Merge(GOAPState mergeState) - { - Positive = Positive | mergeState.Positive; - Negative = Negative & ~Positive; - - Negative = Negative | mergeState.Negative; - Positive = Positive & ~Negative; - - MergeUserData(mergeState); - } - - public void Remove(GOAPState removeState) - { - Positive = Positive & ~removeState.Positive; - Negative = Negative & ~removeState.Negative; - - RemoveUserData(removeState); - } - - public bool Contains(GOAPState state) - { - bool result = (state.Positive & Positive) == state.Positive && (state.Negative & Negative) == state.Negative; - - if (result == false) - return false; - - ContainsUserData(state, ref result); - return result; - } - - public bool ContainsAny(GOAPState state) - { - bool result = (state.Positive & Positive) != EWorldState.None || (state.Negative & Negative) != EWorldState.None; - - if (result == true) - return true; - - ContainsAnyUserData(state, ref result); - return result; - } - - partial void MergeUserData(GOAPState mergeState); - partial void RemoveUserData(GOAPState removeState); - partial void ContainsUserData(GOAPState state, ref bool result); - partial void ContainsAnyUserData(GOAPState state, ref bool result); - } -} \ No newline at end of file diff --git a/data/GOAPState.qtn b/data/GOAPState.qtn deleted file mode 100644 index fa6f5f3bc1c429a31ddff235e989bc4c53f49c5a..0000000000000000000000000000000000000000 --- a/data/GOAPState.qtn +++ /dev/null @@ -1,14 +0,0 @@ -struct GOAPState -{ - EWorldState Positive; - EWorldState Negative; -} - -[Flags] -enum EWorldState : UInt32 -{ - None = 0, - Idle = 1, - CarryingTarget = 2, - TargetDelivered = 4, -} \ No newline at end of file diff --git a/data/GameConfig.cs b/data/GameConfig.cs deleted file mode 100644 index 44adf3ee5aacde00f9ce5ec9946d426a1c118b2d..0000000000000000000000000000000000000000 --- a/data/GameConfig.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum { - public partial class GameConfig { - public FP MinStrikeForce; - public FP MaxStrikeForce; - public string HoleTriggerLayer; - } -} diff --git a/data/HFSM.Agent.cs b/data/HFSM.Agent.cs deleted file mode 100644 index 97ca0901582a01ee18ee2df1c4aa2489d84332b0..0000000000000000000000000000000000000000 --- a/data/HFSM.Agent.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Quantum -{ - public partial struct HFSMAgent - { - // Used to setup info on the Unity debugger - public string GetRootAssetName(Frame frame) => frame.FindAsset(Data.Root.Id).Path; - - public AIConfig GetConfig(Frame frame) - { - return frame.FindAsset(Config.Id); - } - } -} diff --git a/data/HFSM.CheckBlackboardInt.cs b/data/HFSM.CheckBlackboardInt.cs deleted file mode 100644 index e381093f02bd829d9d642c4077e5a34e415527ed..0000000000000000000000000000000000000000 --- a/data/HFSM.CheckBlackboardInt.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace Quantum -{ - public enum EValueComparison - { - None, - LessThan, - MoreThan, - EqualTo, - } - - [Serializable] - [AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)] - public partial class CheckBlackboardInt : HFSMDecision - { - public AIBlackboardValueKey Key; - public EValueComparison Comparison = EValueComparison.MoreThan; - public AIParamInt DesiredValue = 1; - - public override unsafe bool Decide(Frame frame, EntityRef entity) - { - var blackboard = frame.Unsafe.GetPointer(entity); - - var agent = frame.Unsafe.GetPointer(entity); - var aiConfig = agent->GetConfig(frame); - - var comparisonValue = DesiredValue.Resolve(frame, entity, blackboard, aiConfig); - var currentAmount = blackboard->GetInteger(frame, Key.Key); - - switch (Comparison) - { - case EValueComparison.LessThan: return currentAmount < comparisonValue; - case EValueComparison.MoreThan: return currentAmount > comparisonValue; - case EValueComparison.EqualTo: return currentAmount == comparisonValue; - default: return false; - } - } - } -} \ No newline at end of file diff --git a/data/HFSM.Decision.cs b/data/HFSM.Decision.cs deleted file mode 100644 index 7eac18c1c1437e03f8ce8cb37a514aa27803ea38..0000000000000000000000000000000000000000 --- a/data/HFSM.Decision.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - public abstract unsafe partial class HFSMDecision - { - public string Label; - - public abstract Boolean Decide(Frame frame, EntityRef entity); - } -} diff --git a/data/HFSM.LogicalDecisions.cs b/data/HFSM.LogicalDecisions.cs deleted file mode 100644 index 9e6fc74f2072cf9de37b6f78ade490e18854b6f4..0000000000000000000000000000000000000000 --- a/data/HFSM.LogicalDecisions.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - public abstract partial class HFSMLogicalDecision : HFSMDecision - { - public AssetRefHFSMDecision[] Decisions; - - protected HFSMDecision[] _decisions; - - public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator) - { - base.Loaded(resourceManager, allocator); - - _decisions = new HFSMDecision[Decisions == null ? 0 : Decisions.Length]; - if (Decisions != null) - { - for (Int32 i = 0; i < Decisions.Length; i++) - { - _decisions[i] = (HFSMDecision)resourceManager.GetAsset(Decisions[i].Id); - } - } - } - } - - [Serializable] - [AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)] - public partial class HFSMOrDecision : HFSMLogicalDecision - { - public override unsafe bool Decide(Frame frame, EntityRef entity) - { - foreach (var decision in _decisions) - { - if (decision.Decide(frame, entity)) - return true; - } - return false; - } - } - - - [Serializable] - [AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)] - public partial class HFSMAndDecision : HFSMLogicalDecision - { - public override unsafe bool Decide(Frame frame, EntityRef entity) - { - foreach (var decision in _decisions) - { - if (!decision.Decide(frame, entity)) - return false; - } - return true; - } - } - - - [Serializable] - [AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)] - public partial class HFSMNotDecision : HFSMLogicalDecision - { - public override unsafe bool Decide(Frame frame, EntityRef entity) - { - return !_decisions[0].Decide(frame, entity); - } - } -} diff --git a/data/HFSM.Manager.Threadsafe.cs b/data/HFSM.Manager.Threadsafe.cs deleted file mode 100644 index 4b3469fb664c5a0bb834872c29b0b104a3d58ad5..0000000000000000000000000000000000000000 --- a/data/HFSM.Manager.Threadsafe.cs +++ /dev/null @@ -1,136 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - public static unsafe partial class HFSMManager - { - public static unsafe partial class ThreadSafe - { - // ========== PUBLIC METHODS ================================================================================== - - /// - /// Initializes the HFSM, making the current state to be equals the initial state - /// - public static unsafe void Init(FrameThreadSafe frame, EntityRef entity, HFSMRoot root) - { - if (frame.TryGetPointer(entity, out HFSMAgent* agent)) - { - HFSMData* hfsmData = &agent->Data; - Init(frame, hfsmData, entity, root); - } - else - { - Log.Error("[Bot SDK] Tried to initialize an entity which has no HfsmAgent component"); - } - } - - /// - /// Initializes the HFSM, making the current state to be equals the initial state - /// - public static unsafe void Init(FrameThreadSafe frame, HFSMData* hfsm, EntityRef entity, HFSMRoot root) - { - hfsm->Root = root; - if (hfsm->Root.Equals(default) == false) - { - HFSMState initialState = frame.FindAsset(root.InitialState.Id); - ChangeState(initialState, frame, hfsm, entity, ""); - } - } - - /// - /// Update the state of the HFSM. - /// - /// Usually the current deltaTime so the HFSM accumulates the time stood on the current state - public static void Update(FrameThreadSafe frame, FP deltaTime, EntityRef entity) - { - if (frame.TryGetPointer(entity, out HFSMAgent* agent)) - { - HFSMData* hfsmData = &agent->Data; - Update(frame, deltaTime, hfsmData, entity); - } - else - { - Log.Error("[Bot SDK] Tried to update an entity which has no HFSMAgent component"); - } - } - - /// - /// Update the state of the HFSM. - /// - /// Usually the current deltaTime so the HFSM accumulates the time stood on the current state - public static void Update(FrameThreadSafe frame, FP deltaTime, HFSMData* hfsmData, EntityRef entity) - { - HFSMState currentState = frame.FindAsset(hfsmData->CurrentState.Id); - currentState.UpdateState(frame, deltaTime, hfsmData, entity); - } - - /// - /// Triggers an event if the target HFSM listens to it - /// - public static unsafe void TriggerEvent(FrameThreadSafe frame, EntityRef entity, string eventName) - { - if (frame.TryGetPointer(entity, out HFSMAgent* agent)) - { - HFSMData* hfsmData = &agent->Data; - TriggerEvent(frame, hfsmData, entity, eventName); - } - else - { - Log.Error("[Bot SDK] Tried to trigger an event to an entity which has no HFSMAgent component"); - } - } - - /// - /// Triggers an event if the target HFSM listens to it - /// - public static unsafe void TriggerEvent(FrameThreadSafe frame, HFSMData* hfsmData, EntityRef entity, string eventName) - { - Int32 eventInt = 0; - - HFSMRoot hfsmRoot = frame.FindAsset(hfsmData->Root.Id); - if (hfsmRoot.RegisteredEvents.TryGetValue(eventName, out eventInt)) - { - if (hfsmData->CurrentState.Equals(default) == false) - { - HFSMState currentState = frame.FindAsset(hfsmData->CurrentState.Id); - currentState.Event(frame, hfsmData, entity, eventInt); - } - } - } - - /// - /// Triggers an event if the target HFSM listens to it - /// - public static unsafe void TriggerEventNumber(FrameThreadSafe frame, HFSMData* hfsmData, EntityRef entity, Int32 eventInt) - { - if (hfsmData->CurrentState.Equals(default) == false) - { - HFSMState currentState = frame.FindAsset(hfsmData->CurrentState.Id); - currentState.Event(frame, hfsmData, entity, eventInt); - } - } - - // ========== INTERNAL METHODS ================================================================================ - - /// - /// Executes the On Exit actions for the current state, then changes the current state - /// - internal static void ChangeState(HFSMState nextState, FrameThreadSafe frame, HFSMData* hfsmData, EntityRef entity, string transitionId) - { - Assert.Check(nextState != null, "Tried to change HFSM to a null state"); - - HFSMState currentState = frame.FindAsset(hfsmData->CurrentState.Id); - currentState?.ExitState(nextState, frame, hfsmData, entity); - hfsmData->CurrentState = nextState; - - if (frame.IsVerified == true && entity != default(EntityRef)) - { - StateChanged?.Invoke(entity, hfsmData->CurrentState.Id.Value, transitionId); - } - - nextState.EnterState(frame, hfsmData, entity); - } - } - } -} diff --git a/data/HFSM.Manager.cs b/data/HFSM.Manager.cs deleted file mode 100644 index 74fbb0c618547e8749eae3de4e1302557b10de4d..0000000000000000000000000000000000000000 --- a/data/HFSM.Manager.cs +++ /dev/null @@ -1,133 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - public static unsafe class HFSMManager - { - public static Action StateChanged; - - /// - /// Initializes the HFSM, making the current state to be equals the initial state - /// - public static unsafe void Init(Frame frame, EntityRef entity, HFSMRoot root) - { - if (frame.Unsafe.TryGetPointer(entity, out HFSMAgent* agent)) - { - HFSMData* hfsmData = &agent->Data; - Init(frame, hfsmData, entity, root); - } - else - { - Log.Error("[Bot SDK] Tried to initialize an entity which has no HfsmAgent component"); - } - } - - /// - /// Initializes the HFSM, making the current state to be equals the initial state - /// - public static unsafe void Init(Frame frame, HFSMData* hfsm, EntityRef entity, HFSMRoot root) - { - hfsm->Root = root; - if (hfsm->Root.Equals(default) == false) - { - HFSMState initialState = frame.FindAsset(root.InitialState.Id); - ChangeState(initialState, frame, hfsm, entity, ""); - } - } - - - /// - /// Update the state of the HFSM. - /// - /// Usually the current deltaTime so the HFSM accumulates the time stood on the current state - public static void Update(Frame frame, FP deltaTime, EntityRef entity) - { - if (frame.Unsafe.TryGetPointer(entity, out HFSMAgent* agent)) - { - HFSMData* hfsmData = &agent->Data; - Update(frame, deltaTime, hfsmData, entity); - } - else - { - Log.Error("[Bot SDK] Tried to update an entity which has no HFSMAgent component"); - } - } - - /// - /// Update the state of the HFSM. - /// - /// Usually the current deltaTime so the HFSM accumulates the time stood on the current state - public static void Update(Frame frame, FP deltaTime, HFSMData* hfsmData, EntityRef entity) - { - HFSMState curentState = frame.FindAsset(hfsmData->CurrentState.Id); - curentState.UpdateState(frame, deltaTime, hfsmData, entity); - } - - - /// - /// Triggers an event if the target HFSM listens to it - /// - public static unsafe void TriggerEvent(Frame frame, EntityRef entity, string eventName) - { - if (frame.Unsafe.TryGetPointer(entity, out HFSMAgent* agent)) - { - HFSMData* hfsmData = &agent->Data; - TriggerEvent(frame, hfsmData, entity, eventName); - } - else - { - Log.Error("[Bot SDK] Tried to trigger an event to an entity which has no HFSMAgent component"); - } - } - - /// - /// Triggers an event if the target HFSM listens to it - /// - public static unsafe void TriggerEvent(Frame frame, HFSMData* hfsmData, EntityRef entity, string eventName) - { - Int32 eventInt = 0; - - HFSMRoot hfsmRoot = frame.FindAsset(hfsmData->Root.Id); - if (hfsmRoot.RegisteredEvents.TryGetValue(eventName, out eventInt)) - { - if (hfsmData->CurrentState.Equals(default) == false) - { - HFSMState currentState = frame.FindAsset(hfsmData->CurrentState.Id); - currentState.Event(frame, hfsmData, entity, eventInt); - } - } - } - - /// - /// Triggers an event if the target HFSM listens to it - /// - public static unsafe void TriggerEventNumber(Frame frame, HFSMData* hfsmData, EntityRef entity, Int32 eventInt) - { - if (hfsmData->CurrentState.Equals(default) == false) - { - HFSMState currentState = frame.FindAsset(hfsmData->CurrentState.Id); - currentState.Event(frame, hfsmData, entity, eventInt); - } - } - - /// - /// Executes the On Exit actions for the current state, then changes the current state - /// - internal static void ChangeState(HFSMState nextState, Frame frame, HFSMData* hfsmData, EntityRef entity, string transitionId) - { - Assert.Check(nextState != null, "Tried to change HFSM to a null state"); - - HFSMState currentState = frame.FindAsset(hfsmData->CurrentState.Id); - currentState?.ExitState(nextState, frame, hfsmData, entity); - hfsmData->CurrentState = nextState; - - if (frame.IsVerified == true && entity != default(EntityRef)) - { - StateChanged?.Invoke(entity, hfsmData->CurrentState.Id.Value, transitionId); - } - - nextState.EnterState(frame, hfsmData, entity); - } - } -} diff --git a/data/HFSM.Root.cs b/data/HFSM.Root.cs deleted file mode 100644 index 594fd481fd36abddfcba3f4c3c6da4ecf68b0669..0000000000000000000000000000000000000000 --- a/data/HFSM.Root.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using Photon.Deterministic; - -namespace Quantum -{ - public partial class HFSMRoot : AssetObject - { - public string Label; - - public AssetRefHFSMState[] StatesLinks; - - public AssetRefHFSMState InitialState - { - get - { - if (StatesLinks != null) - { - return StatesLinks[0]; - } - return default; - } - } - - public string[] EventsNames; - - [NonSerialized] - public Dictionary RegisteredEvents = new Dictionary(); - - public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator) - { - base.Loaded(resourceManager, allocator); - - RegisteredEvents.Clear(); - for (int i = 0; i < EventsNames.Length; i++) - { - RegisteredEvents.Add(EventsNames[i], i + 1); - } - } - - public string GetEventName(int eventID) - { - foreach (var kvp in RegisteredEvents) - { - if (kvp.Value == eventID) - return kvp.Key; - } - return ""; - } - } -} \ No newline at end of file diff --git a/data/HFSM.State.cs b/data/HFSM.State.cs deleted file mode 100644 index 434ee8b28111733a6b481bbf5ab9601562da7d2a..0000000000000000000000000000000000000000 --- a/data/HFSM.State.cs +++ /dev/null @@ -1,229 +0,0 @@ -using Photon.Deterministic; -using System; -using System.Collections.Generic; - -namespace Quantum -{ - [AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)] - public unsafe partial class HFSMState : AssetObject - { - public string Label; - public AssetRefAIAction[] OnUpdateLinks; - public AssetRefAIAction[] OnEnterLinks; - public AssetRefAIAction[] OnExitLinks; - public HFSMTransition[] Transitions; - - public AssetRefHFSMState[] ChildrenLinks; - public AssetRefHFSMState ParentLink; - public int Level; - - [NonSerialized] - public AIAction[] OnUpdate; - [NonSerialized] - public AIAction[] OnEnter; - [NonSerialized] - public AIAction[] OnExit; - [NonSerialized] - public HFSMState[] Children; - [NonSerialized] - public HFSMState Parent; - - public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator) - { - base.Loaded(resourceManager, allocator); - - OnUpdate = new AIAction[OnUpdateLinks == null ? 0 : OnUpdateLinks.Length]; - if (OnUpdateLinks != null) - { - for (Int32 i = 0; i < OnUpdateLinks.Length; i++) - { - OnUpdate[i] = (AIAction)resourceManager.GetAsset(OnUpdateLinks[i].Id); - } - } - OnEnter = new AIAction[OnEnterLinks == null ? 0 : OnEnterLinks.Length]; - if (OnEnterLinks != null) - { - for (Int32 i = 0; i < OnEnterLinks.Length; i++) - { - OnEnter[i] = (AIAction)resourceManager.GetAsset(OnEnterLinks[i].Id); - } - } - OnExit = new AIAction[OnExitLinks == null ? 0 : OnExitLinks.Length]; - if (OnExitLinks != null) - { - for (Int32 i = 0; i < OnExitLinks.Length; i++) - { - OnExit[i] = (AIAction)resourceManager.GetAsset(OnExitLinks[i].Id); - } - } - - Children = new HFSMState[ChildrenLinks == null ? 0 : ChildrenLinks.Length]; - if (ChildrenLinks != null) - { - for (Int32 i = 0; i < ChildrenLinks.Length; i++) - { - Children[i] = (HFSMState)resourceManager.GetAsset(ChildrenLinks[i].Id); - } - } - - Parent = (HFSMState)resourceManager.GetAsset(ParentLink.Id); - if (Transitions != null) - { - for (int i = 0; i < Transitions.Length; i++) - { - Transitions[i].Setup(resourceManager); - } - } - } - - internal Boolean UpdateState(Frame frame, FP deltaTime, HFSMData* hfsmData, EntityRef entity) - { - HFSMState parent = Parent; - Boolean transition = false; - - if (parent != null) - { - transition = parent.UpdateState(frame, deltaTime, hfsmData, entity); - } - - if (transition == true) - return true; - - *hfsmData->Times.GetPointer(Level) += deltaTime; - - DoUpdateActions(frame, entity); - return CheckStateTransitions(frame, hfsmData, entity, 0); - } - - internal Boolean Event(Frame frame, HFSMData* hfsmData, EntityRef entity, Int32 eventInt) - { - HFSMState p = Parent; - Boolean transition = false; - if (p != null) - { - transition = p.Event(frame, hfsmData, entity, eventInt); - } - - if (transition) - { - return true; - } - - return CheckStateTransitions(frame, hfsmData, entity, eventInt); - } - - private void DoUpdateActions(Frame frame, EntityRef entity) - { - for (int i = 0; i < OnUpdate.Length; i++) - { - OnUpdate[i].Update(frame, entity); - int nextAction = OnUpdate[i].NextAction(frame, entity); - if (nextAction > i) - { - i = nextAction; - } - } - } - private void DoEnterActions(Frame frame, EntityRef entity) - { - for (int i = 0; i < OnEnter.Length; i++) - { - OnEnter[i].Update(frame, entity); - int nextAction = OnEnter[i].NextAction(frame, entity); - if (nextAction > i) - { - i = nextAction; - } - } - } - private void DoExitActions(Frame frame, EntityRef entity) - { - for (int i = 0; i < OnExit.Length; i++) - { - OnExit[i].Update(frame, entity); - int nextAction = OnExit[i].NextAction(frame, entity); - if (nextAction > i) - { - i = nextAction; - } - } - } - - private bool CheckStateTransitions(Frame frame, HFSMData* hfsmData, EntityRef entity, Int32 eventKey = 0) - { - hfsmData->Time = *hfsmData->Times.GetPointer(Level); - - return CheckTransitions(frame, Transitions, hfsmData, entity, eventKey); - } - - private static bool CheckTransitions(Frame frame, HFSMTransition[] transitions, HFSMData* hfsmData, EntityRef entity, int eventKey, int depth = 0) - { - // Just to avoid accidental loops - if (depth == 10) - return false; - - if (transitions == null) - return false; - - for (int i = 0; i < transitions.Length; i++) - { - var transition = transitions[i]; - - if (transition.State == null && transition.TransitionSet == null) - continue; - - // Only consider evaluating the event if this transition HAS a event as requisite (EventKey != 0) - if (transition.EventKey != 0 && transition.EventKey != eventKey) - continue; - - if (transition.Decision != null && transition.Decision.Decide(frame, entity) == false) - continue; - - if (transition.State != null) - { - HFSMManager.ChangeState(transition.State, frame, hfsmData, entity, transition.Id); - return true; - } - else if (CheckTransitions(frame, transition.TransitionSet.Transitions, hfsmData, entity, eventKey, depth + 1) == true) - { - return true; - } - } - - return false; - } - - internal void EnterState(Frame frame, HFSMData* hfsmData, EntityRef entity) - { - *hfsmData->Times.GetPointer(Level) = FP._0; - DoEnterActions(frame, entity); - if (Children != null && Children.Length > 0) - { - HFSMState child = Children[0]; - HFSMManager.ChangeState(child, frame, hfsmData, entity, ""); - } - } - - internal void ExitState(HFSMState nextState, Frame frame, HFSMData* hfsmData, EntityRef entity) - { - if (nextState != null && nextState.IsChildOf(this) == true) - return; - - DoExitActions(frame, entity); - Parent?.ExitState(nextState, frame, hfsmData, entity); - } - - internal bool IsChildOf(HFSMState state) - { - HFSMState parent = Parent; - - if (parent == null) - return false; - - if (parent == state) - return true; - - return parent.IsChildOf(state); - } - } -} diff --git a/data/HFSM.TimerDecision.cs b/data/HFSM.TimerDecision.cs deleted file mode 100644 index 3b02dee7f9ec17682a8f186b74068fb8f2e732c1..0000000000000000000000000000000000000000 --- a/data/HFSM.TimerDecision.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - [Serializable] - [AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)] - public partial class TimerDecision : HFSMDecision - { - public AIParamFP TimeToTrueState = FP._3; - - public override unsafe bool Decide(Frame frame, EntityRef entity) - { - var blackboard = frame.Has(entity) ? frame.Get(entity) : default; - - var agent = frame.Unsafe.GetPointer(entity); - var aiConfig = agent->GetConfig(frame); - - FP requiredTime = TimeToTrueState.Resolve(frame, entity, &blackboard, aiConfig); - - var hfsmData = &agent->Data; - return hfsmData->Time >= requiredTime; - } - } -} diff --git a/data/HFSM.Transition.cs b/data/HFSM.Transition.cs deleted file mode 100644 index 4e500061d0a4d415fd9da466f498c76e2ecdbd5d..0000000000000000000000000000000000000000 --- a/data/HFSM.Transition.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace Quantum -{ - [Serializable] - public class HFSMTransition - { - public string Id; - - public Int32 EventKey = 0; - public AssetRefHFSMDecision DecisionLink; - public AssetRefHFSMState StateLink; - public AssetRefHFSMTransitionSet TransitionSetLink; - - [NonSerialized] - public HFSMDecision Decision; - [NonSerialized] - public HFSMState State; - [NonSerialized] - public HFSMTransitionSet TransitionSet; - - public void Setup(IResourceManager resourceManager) - { - Decision = (HFSMDecision)resourceManager.GetAsset(DecisionLink.Id); - State = (HFSMState)resourceManager.GetAsset(StateLink.Id); - TransitionSet = (HFSMTransitionSet)resourceManager.GetAsset(TransitionSetLink.Id); - } - } -} diff --git a/data/HFSM.TransitionSet.cs b/data/HFSM.TransitionSet.cs deleted file mode 100644 index fac64831c35b1135e104c21f9f63a5f920bfd06a..0000000000000000000000000000000000000000 --- a/data/HFSM.TransitionSet.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - public partial class HFSMTransitionSet - { - public string Label; - public AssetRefHFSMDecision PrerequisiteLink; - - public HFSMTransition[] Transitions; - - [NonSerialized] - public HFSMDecision Prerequisite; - - public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator) - { - base.Loaded(resourceManager, allocator); - - Prerequisite = (HFSMDecision)resourceManager.GetAsset(PrerequisiteLink.Id); - - if (Transitions != null) - { - for (int i = 0; i < Transitions.Length; i++) - { - Transitions[i].Setup(resourceManager); - } - } - } - } -} - diff --git a/data/HFSM.TrueDecision.cs b/data/HFSM.TrueDecision.cs deleted file mode 100644 index 2c39a065c2db760702a0506cdff3580d394d8d73..0000000000000000000000000000000000000000 --- a/data/HFSM.TrueDecision.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - [Serializable] - [AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)] - public partial class TrueDecision : HFSMDecision - { - public override unsafe bool Decide(Frame frame, EntityRef entity) - { - return true; - } - } -} diff --git a/data/HFSM.qtn b/data/HFSM.qtn deleted file mode 100644 index d84d43f7b24deef26be6ae33b9d4d640a254ed60..0000000000000000000000000000000000000000 --- a/data/HFSM.qtn +++ /dev/null @@ -1,17 +0,0 @@ -asset HFSMRoot; -asset HFSMState; -asset HFSMDecision; -asset HFSMTransitionSet; - -component HFSMAgent{ - HFSMData Data; - AssetRefAIConfig Config; -} - -struct HFSMData -{ - asset_ref Root; - asset_ref CurrentState; - FP Time; - array[8] Times; -} \ No newline at end of file diff --git a/data/IBotDebug.cs b/data/IBotDebug.cs deleted file mode 100644 index 9c28637234a575e1f8292b6db216566646603de9..0000000000000000000000000000000000000000 --- a/data/IBotDebug.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Quantum -{ - // Interface to make the communication between the three solutions involved: quantum_unity, quantum_code and quantum.ai.editor - // Basically, these are the information that the quantum.ai.editor needs to know that are meant to be filed from Unity - public interface IBotDebug - { - EntityRef EntityRef { get; } - Frame Frame { get; } - - // The asset names on Unity - string GetHFSMRootName(); - string GetBTRootName(); - string GetUTRootName(); - } -} \ No newline at end of file diff --git a/data/IdleAction.cs b/data/IdleAction.cs deleted file mode 100644 index 3d8c9325247cf5f2a2854ce5803c4269f29419ec..0000000000000000000000000000000000000000 --- a/data/IdleAction.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Photon.Deterministic; - -namespace Quantum -{ - [Serializable] - [AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)] - public partial class IdleAction : AIAction - { - public override unsafe void Update(Frame frame, EntityRef entity) - { - } - } -} diff --git a/data/IncreaseBlackboardInt.cs b/data/IncreaseBlackboardInt.cs deleted file mode 100644 index 2814bc29cb39e62c66ecda18cc41493450887316..0000000000000000000000000000000000000000 --- a/data/IncreaseBlackboardInt.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace Quantum -{ - [Serializable] - [AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)] - public unsafe partial class IncreaseBlackboardInt : AIAction - { - public AIBlackboardValueKey Key; - public AIParamInt IncrementAmount; - - public override unsafe void Update(Frame frame, EntityRef entity) - { - var blackboard = frame.Unsafe.GetPointer(entity); - - var agent = frame.Unsafe.GetPointer(entity); - var aiConfig = agent->GetConfig(frame); - - var incrementValue = IncrementAmount.Resolve(frame, entity, blackboard, aiConfig); - - var currentAmount = blackboard->GetInteger(frame, Key.Key); - currentAmount += incrementValue; - - blackboard->Set(frame, Key.Key, currentAmount); - } - } -} \ No newline at end of file diff --git a/data/Input.User.cs b/data/Input.User.cs deleted file mode 100644 index 7a6b32ec8a636a0af8c164fb87abb1bfe260f297..0000000000000000000000000000000000000000 --- a/data/Input.User.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - unsafe partial struct Input - { - // input bandwidth trick by Erick Passos - // using a single Byte to encode a 2D direction with 2 degrees of accuracy - public FPVector2 Direction - { - get { - if (EncodedDirection == default) return default; - Int32 angle = ((Int32)EncodedDirection - 1) * 2; - return FPVector2.Rotate(FPVector2.Up, angle * FP.Deg2Rad); - } - set { - if (value == default) - { - EncodedDirection = default; - return; - } - var angle = FPVector2.RadiansSigned(FPVector2.Up, value) * FP.Rad2Deg; - angle = (((angle + 360) % 360) / 2) + 1; - EncodedDirection = (Byte)(angle.AsInt); - } - } - } -} diff --git a/data/InputHandler.cs b/data/InputHandler.cs deleted file mode 100644 index e9d02b9f27065f99126fc746346c8a81b5c3ac2c..0000000000000000000000000000000000000000 --- a/data/InputHandler.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using Quantum; -using Photon.Deterministic; - -public unsafe class InputHandler : MonoBehaviour -{ - - public int InitialIndex; - public int TargetIndex; - public bool HasInitial; - public bool DebugMoves = false; - public MovesIndicatorManager MovesIndicator; - public GameObject SelectedPieceIndicator; - - public UnityEngine.LayerMask BoardsRaycastMask; - - private void Update() - { - HandleClick(); - - if (HasInitial) - { - ChessViewUpdater.Instance.SetObjectByIndex(SelectedPieceIndicator, InitialIndex); - } - else { - SelectedPieceIndicator.SetActive(false); - } - } - - public void HandleClick() - { - if (UnityEngine.Input.GetMouseButtonDown(0)) - { - // Perform the Unity raycast - Ray ray = Camera.main.ScreenPointToRay(UnityEngine.Input.mousePosition); - RaycastHit hit; - Physics.Raycast(ray, out hit, 100, BoardsRaycastMask); - - // If the raycast hit the Board... - if (hit.collider != null) - { - var position = new FPVector2(FP.FromFloat_UNSAFE(hit.point.x), FP.FromFloat_UNSAFE(hit.point.z)); - if (HasInitial == false || DebugMoves) - { - InitialIndex = BoardHelper.GetIndexByPosition(position); - HasInitial = true; - MovesIndicator.UpdatePossibleMovements(InitialIndex); - } - else - { - TargetIndex = BoardHelper.GetIndexByPosition(position); - Frame f = QuantumRunner.Default.Game.Frames.Verified; - if (MoveValidatorHelper.IsValidMove(ref f.Global->Board, InitialIndex, TargetIndex, true) == false) - { - InitialIndex = TargetIndex; - MovesIndicator.UpdatePossibleMovements(InitialIndex); - } - else - { - HasInitial = false; - //sendcommand - var c = new MoveCommand(); - c.Data.InitialIndex = InitialIndex; - c.Data.TargetIndex = TargetIndex; - QuantumRunner.Default.Game.SendCommand(c); - MovesIndicator.ResetPrefabs(); - } - } - } - } - } -} diff --git a/data/KCCSettings.txt b/data/KCCSettings.txt deleted file mode 100644 index 43612dea2257a75c28a0130cae68a6712c9425ee..0000000000000000000000000000000000000000 --- a/data/KCCSettings.txt +++ /dev/null @@ -1,194 +0,0 @@ -using System; -using Photon.Deterministic; -using Quantum.Core; - -namespace Quantum -{ - public enum KCCMovementType - { - None, - Free, - Tangent - } - - public struct KCCMovementData - { - public KCCMovementType Type; - public FPVector2 Correction; - public FPVector2 Direction; - public FP MaxPenetration; - } - - unsafe partial class KCCSettings : AssetObject - { - // This is the KCC actual radius (non penetrable) - public FP Radius = FP._0_50; - public Int32 MaxContacts = 2; - public FP AllowedPenetration = FP._0_10; - public FP CorrectionSpeed = FP._10; - public FP BaseSpeed = FP._2; - public FP Acceleration = FP._10; - public Boolean Debug = false; - public FP Brake = 1; - - public void Init(ref KCC kcc) - { - kcc.Settings = this; - kcc.MaxSpeed = BaseSpeed; - kcc.Acceleration = Acceleration; - } - - public void SteerAndMove(FrameBase f, EntityRef entity, in KCCMovementData movementData) - { - KCC* kcc = null; - if (f.Unsafe.TryGetPointer(entity, out kcc) == false) - { - return; - } - - Transform2D* transform = null; - if (f.Unsafe.TryGetPointer(entity, out transform) == false) - { - return; - } - - if (movementData.Type != KCCMovementType.None) - { - kcc->Velocity += kcc->Acceleration * f.DeltaTime * movementData.Direction; - if (kcc->Velocity.SqrMagnitude > kcc->MaxSpeed * kcc->MaxSpeed) - { - kcc->Velocity = kcc->Velocity.Normalized * kcc->MaxSpeed; - } - //transform->Rotation = FPVector2.RadiansSigned(FPVector2.Up, movementData.Direction);// FPMath.Atan2(kcc->Velocity.Y, kcc->Velocity.X); - } - else - { - // brake instead? - kcc->Velocity = FPVector2.MoveTowards(kcc->Velocity, FPVector2.Zero, f.DeltaTime * Brake); - } - - if (movementData.MaxPenetration > AllowedPenetration) - { - if (movementData.MaxPenetration > AllowedPenetration * 2) - { - transform->Position += movementData.Correction; - } - else - { - transform->Position += movementData.Correction * f.DeltaTime * CorrectionSpeed; - } - - } - - - transform->Position += kcc->Velocity * f.DeltaTime; - - - -#if DEBUG - if (Debug) - { - Draw.Circle(transform->Position, Radius, ColorRGBA.Blue); - Draw.Ray(transform->Position, transform->Forward * Radius, ColorRGBA.Red); - } -#endif - } - - public KCCMovementData ComputeRawMovement(FrameBase f, EntityRef entity, FPVector2 direction) - { - KCC* kcc = null; - if (f.Unsafe.TryGetPointer(entity, out kcc) == false) - { - return default; - } - - Transform2D* transform = null; - if (f.Exists(entity) == false || f.Unsafe.TryGetPointer(entity, out transform) == false) - { - return default; - } - - KCCMovementData movementPack = default; - - - movementPack.Type = direction != default ? KCCMovementType.Free : KCCMovementType.None; - movementPack.Direction = direction; - Shape2D shape = Shape2D.CreateCircle(Radius); - - var layer = f.Layers.GetLayerMask("Static"); - var hits = f.Physics2D.OverlapShape(transform->Position, FP._0, shape, layer, options: QueryOptions.HitStatics | QueryOptions.ComputeDetailedInfo); - int count = Math.Min(MaxContacts, hits.Count); - - if (hits.Count > 0) - { - Boolean initialized = false; - hits.Sort(transform->Position); - for (int i = 0; i < hits.Count && count > 0; i++) - { - // ignore triggers - if (hits[i].IsTrigger) - { - // callback here... - continue; - } - - // ignoring "self" contact - if (hits[i].Entity == entity) - { - continue; - } - - var contactPoint = hits[i].Point; - var contactToCenter = transform->Position - contactPoint; - var localDiff = contactToCenter.Magnitude - Radius; - - -#if DEBUG - if (Debug) - { - Draw.Circle(contactPoint, FP._0_10, ColorRGBA.Red); - } -#endif - - var localNormal = contactToCenter.Normalized; - - count--; - - // define movement type - if (!initialized) - { - initialized = true; - - if (direction != default) - { - var angle = FPVector2.RadiansSkipNormalize(direction.Normalized, localNormal); - if (angle >= FP.Rad_90) - { - var d = FPVector2.Dot(direction, localNormal); - var tangentVelocity = direction - localNormal * d; - if (tangentVelocity.SqrMagnitude > FP.EN4) - { - movementPack.Direction = tangentVelocity.Normalized; - movementPack.Type = KCCMovementType.Tangent; - } - else - { - movementPack.Direction = default; - movementPack.Type = KCCMovementType.None; - } - - } - } - movementPack.MaxPenetration = FPMath.Abs(localDiff); - } - - // any real contact contributes to correction and average normal - var localCorrection = localNormal * -localDiff; - movementPack.Correction += localCorrection; - } - } - - return movementPack; - } - } -} \ No newline at end of file diff --git a/data/Lists.txt b/data/Lists.txt deleted file mode 100644 index 94fadd2d76270be7c3ab6587750bedb3f2d076b3..0000000000000000000000000000000000000000 --- a/data/Lists.txt +++ /dev/null @@ -1,12 +0,0 @@ - -In quantum we can to list as metioned below. -we can to list in Quantum by the following - -component Targets { - list Enemies; -} -The basic API methods for dealing with these Lists are: - -Frame.AllocateList() -Frame.FreeList(QListPtr ptr) -Frame.ResolveList(QListPtr ptr) diff --git a/data/Map.cs b/data/Map.cs deleted file mode 100644 index 5f282702bb03ef11d7184d19c80927b47f919764..0000000000000000000000000000000000000000 --- a/data/Map.cs +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/data/MovesIndicatorManager.cs b/data/MovesIndicatorManager.cs deleted file mode 100644 index 07aaddcf15ecf0d01a60aa9652888553ece48f9e..0000000000000000000000000000000000000000 --- a/data/MovesIndicatorManager.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using Quantum; - -public unsafe class MovesIndicatorManager : QuantumCallbacks -{ - - public GameObject HighlightPrefab; - - private List _prefabs = new List(); - - public void UpdatePossibleMovements(int index) - { - var f = QuantumRunner.Default.Game.Frames.Verified; - ResetPrefabs(); - for (int i = 0; i < f.Global->Board.Cells.Length; i++) - { - if (MoveValidatorHelper.IsValidMove(ref f.Global->Board, index, i, true)) - { - var FPPosition = BoardHelper.GetCordinatesByIndex(i); - Vector3 position = new Vector3((float)FPPosition.X + .5f, .5f, (float)FPPosition.Y + .5f); - var prefab = GetPrefab(); - if (prefab == null) - { - var go = Instantiate(HighlightPrefab, position, Quaternion.identity, transform); - go.SetActive(true); - _prefabs.Add(go); - } - else - { - prefab.SetActive(true); - prefab.transform.position = position; - } - } - } - } - private GameObject GetPrefab() - { - for (int i = 0; i < _prefabs.Count; i++) - { - if (_prefabs[i] != null && _prefabs[i].activeSelf == false) - { - return _prefabs[i]; - } - } - return null; - } - - public void ResetPrefabs() - { - for (int i = 0; i < _prefabs.Count; i++) - { - if (_prefabs[i] != null) - { - _prefabs[i].SetActive(false); - } - } - } -} diff --git a/data/PickupSpawnerSystem.cs b/data/PickupSpawnerSystem.cs deleted file mode 100644 index 0eeb2e2bd953ba07ba8b09415fd4d11adcec44d5..0000000000000000000000000000000000000000 --- a/data/PickupSpawnerSystem.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - public unsafe class PickupSpawnerSystem : SystemMainThread - { - public override void Update(Frame frame) - { - if (frame.Unsafe.TryGetPointerSingleton(out var spawner)) - { - int frameInterval = spawner->SpawnInterval * frame.UpdateRate; - //if ((frameInterval % f.Number) == 0 && spawner->Count < spawner->MaxCount) - if (spawner->Count < spawner->MaxCount) - { - EntityRef pickup = frame.Create(spawner->PickupPrototype); - if (frame.Unsafe.TryGetPointer(pickup, out var transform)) - { - FP angle = frame.RNG->Next(0, 360); - FPQuaternion rotation = FPQuaternion.Euler(0, angle, 0); - transform->Position = (rotation * FPVector3.Right) * frame.RNG->Next(FP._0, spawner->RandomRadius); - } - spawner->Count++; - } - - } - - ComponentIterator chainItems = frame.GetComponentIterator(); - foreach (var item in chainItems) - { - if (item.Component.Destroy) frame.Destroy(item.Entity); - } - } - } -} diff --git a/data/PieceView.cs b/data/PieceView.cs deleted file mode 100644 index 565e5b5655c656046854ca239f782a9afd9e43e3..0000000000000000000000000000000000000000 --- a/data/PieceView.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using Quantum; - -public class PieceView : MonoBehaviour { - public PieceType Type; - public PieceColor Color; - public int IndexOnBoard = -1; - public bool Initialized = false; - - [SerializeField] - private Vector3 _targetPosition; - - public void SetTargetPosition(Vector3 target) - { - Initialized = true; - _targetPosition = target; - } - - void Update() - { - if (Vector3.Distance(transform.position, _targetPosition) > 0.01f && Initialized) { - transform.position = Vector3.Lerp(transform.position, _targetPosition, Time.deltaTime * 10); - } - } - -} diff --git a/data/PiecesMapBaker.cs b/data/PiecesMapBaker.cs deleted file mode 100644 index 0edb65c8dc3e769997ab2d0bee8c2569504671e5..0000000000000000000000000000000000000000 --- a/data/PiecesMapBaker.cs +++ /dev/null @@ -1,59 +0,0 @@ -using UnityEngine; -using Quantum; -using Photon.Deterministic; -#if UNITY_EDITOR -using UnityEditor; -#endif - -//When a class implements MapDataBakerCallback, it can handle the activation of the "OnBake" event. -//This event is called when you hit the "Bake Data" at the Unity scene -//So this is useful whenever the developer needs to be into the bake process -public class PiecesMapBaker : MapDataBakerCallback -{ - public override void OnBake(MapData data) - { - PieceView[] pieces = GameObject.FindObjectsOfType(); - - var boardSpec = UnityDB.FindAsset(data.Asset.Settings.UserAsset.Id); - boardSpec.Settings.Pieces = new BoardSpec.PieceMap[pieces.Length]; - - FillBoardInformation(boardSpec.Settings, pieces); -#if UNITY_EDITOR - EditorUtility.SetDirty(boardSpec.Settings.GetUnityAsset()); -#endif - - } - - public override void OnBeforeBake(MapData data) - { - } - - private void FillBoardInformation(BoardSpec targetSpec, PieceView[] pieces) - { - for (int i = 0; i < targetSpec.Pieces.Length; i++) - { - targetSpec.Pieces[i].InitialIndex = -1; - - targetSpec.Pieces[i].Type = PieceType.None; - targetSpec.Pieces[i].Color = PieceColor.None; - } - - for (int i = 0; i < pieces.Length; i++) - { - FP positionX = FP.FromFloat_UNSAFE(pieces[i].transform.position.x); - FP positionY = FP.FromFloat_UNSAFE(pieces[i].transform.position.z); - var index = BoardHelper.GetIndexByPosition(new FPVector2(positionX, positionY)); - if (index >= 0) - { - targetSpec.Pieces[i].InitialIndex = index; - pieces[i].IndexOnBoard = index; - targetSpec.Pieces[i].Type = pieces[i].Type; - targetSpec.Pieces[i].Color = pieces[i].Color; - } - else - { - pieces[i].IndexOnBoard = -1; - } - } - } -} diff --git a/data/PlayCommand.cs b/data/PlayCommand.cs deleted file mode 100644 index 0934198a6af7da08b62cb1b167ad4712bee6f1be..0000000000000000000000000000000000000000 --- a/data/PlayCommand.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - [Serializable] - public struct PlayCommandData - { - public FP Force; - public FPVector3 Direction; - public FPVector2 Spin; - } - - public class PlayCommand : DeterministicCommand - { - public PlayCommandData Data; - - public override void Serialize(BitStream stream) - { - // serialize command data here - stream.Serialize(ref Data.Force); - stream.Serialize(ref Data.Direction); - stream.Serialize(ref Data.Spin); - } - } -} \ No newline at end of file diff --git a/data/PlayerSystem.cs b/data/PlayerSystem.cs deleted file mode 100644 index 9b1c40964537e2c45a525aaf2e530be4c0392952..0000000000000000000000000000000000000000 --- a/data/PlayerSystem.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using Photon.Deterministic; -using Quantum.Task; - -namespace Quantum -{ - public unsafe class PlayerSystem : SystemSignalsOnly, ISignalOnPlayerDataSet - { - public void OnPlayerDataSet(Frame frame, PlayerRef player) - { - RuntimePlayer data = frame.GetPlayerData(player); - EntityRef traktorEntity = frame.Create(data.TraktorPrototype); - Traktor* traktor = frame.Unsafe.GetPointer(traktorEntity); - if (frame.Unsafe.TryGetPointer(traktorEntity, out var transform) && frame.Unsafe.TryGetPointer(traktor->Sphere, out var sphereTransform)) - { - transform->Position = new FPVector3(player * 2, 0, 0); - sphereTransform->Position = transform->Position; - } - if (frame.Unsafe.TryGetPointer(traktorEntity, out var controller)) - { - controller->Player = player; - } - } - } -} diff --git a/data/Pool.cs b/data/Pool.cs deleted file mode 100644 index 597041c14c592f30d19b071e8e16085e5531fbb3..0000000000000000000000000000000000000000 --- a/data/Pool.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace Quantum -{ - public static class Pool where T : new() - { - private const int POOL_CAPACITY = 4; - - private static readonly List _pool = new List(POOL_CAPACITY); - - public static int Count => _pool.Count; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Get() - { - bool found = false; - T item = default; - - lock (_pool) - { - int index = _pool.Count - 1; - if (index >= 0) - { - found = true; - item = _pool[index]; - - _pool.RemoveAt(index); - } - } - - if (found == false) - { - item = new T(); - } - - return item; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Return(T item) - { - if (item == null) - return; - - lock (_pool) - { - _pool.Add(item); - } - } - } - - public static class Pool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Get() where T : new() - { - return Pool.Get(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Return(T item) where T : new() - { - Pool.Return(item); - } - } -} \ No newline at end of file diff --git a/data/QuantumConsoleRunner.cs b/data/QuantumConsoleRunner.cs deleted file mode 100644 index 83db4befc0106c73721883ae05efac16476f0658..0000000000000000000000000000000000000000 --- a/data/QuantumConsoleRunner.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.IO; - -namespace Quantum { - class QuantumConsoleRunner { - static void Main(string[] args) { - - Log.InitForConsole(); - - var pathToLUT = Path.GetFullPath(args[0]); - var pathToDatabaseFile = Path.GetFullPath(args[1]); - var pathToReplayFile = Path.GetFullPath(args[2]); - var pathToChecksumFile = args.Length > 3 ? Path.GetFullPath(args[3]) : null; - var maxIterations = args.Length > 4 ? long.Parse(args[4]) : 1; - - // Demonstration of a sample runner. Please duplicate the ReplayRunnerSample class to modify, because it may get overwritten in the future. - long iteration = 0; - while (iteration < maxIterations && ReplayRunnerSample.Run(pathToLUT, pathToDatabaseFile, pathToReplayFile, pathToChecksumFile)) { - if (++iteration < maxIterations) { - Console.ForegroundColor = ConsoleColor.Blue; - Console.WriteLine($"Iteration {iteration + 1}"); - Console.ForegroundColor = ConsoleColor.Gray; - } - } - - //Console.ReadKey(); - } - } -} diff --git a/data/QuantumJsonSerializer.cs b/data/QuantumJsonSerializer.cs deleted file mode 100644 index f807eaa1415a86d5aaf4c322d596145515be7d27..0000000000000000000000000000000000000000 --- a/data/QuantumJsonSerializer.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; - -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -namespace Quantum { - - public class QuantumJsonSerializer : Quantum.JsonAssetSerializerBase { - private readonly JsonSerializer _serializer = CreateSerializer(); - - public static JsonSerializer CreateSerializer() { - return JsonSerializer.Create(CreateSettings()); - } - - protected override object FromJson(string json, Type type) { - using (var reader = new StringReader(json)) { - var result = _serializer.Deserialize(reader, type); - return result; - } - } - - protected override string ToJson(object obj) { - using (var writer = new StringWriter()) { - _serializer.Serialize(writer, obj); - return writer.ToString(); - } - } - - private static JsonSerializerSettings CreateSettings() { - return new JsonSerializerSettings { - ContractResolver = new WritablePropertiesOnlyResolver(), - Formatting = Formatting.Indented, - TypeNameHandling = TypeNameHandling.Auto, - NullValueHandling = NullValueHandling.Ignore, - Converters = { new ByteArrayConverter() }, - }; - } - private class ByteArrayConverter : JsonConverter { - - public override bool CanConvert(Type objectType) { - return objectType == typeof(byte[]); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - if (reader.TokenType == JsonToken.StartArray) { - var byteList = new List(); - - while (reader.Read()) { - switch (reader.TokenType) { - case JsonToken.Integer: - byteList.Add(Convert.ToByte(reader.Value)); - break; - - case JsonToken.EndArray: - return byteList.ToArray(); - - case JsonToken.Comment: - // skip - break; - - default: - throw new Exception(string.Format("Unexpected token when reading bytes: {0}", reader.TokenType)); - } - } - - throw new Exception("Unexpected end when reading bytes."); - } else { - throw new Exception(string.Format("Unexpected token parsing binary. Expected StartArray, got {0}.", reader.TokenType)); - } - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - if (value == null) { - writer.WriteNull(); - return; - } - - byte[] data = (byte[])value; - - // compose an array - writer.WriteStartArray(); - - for (var i = 0; i < data.Length; i++) { - writer.WriteValue(data[i]); - } - - writer.WriteEndArray(); - } - } - - private class WritablePropertiesOnlyResolver : DefaultContractResolver { - - protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { - IList props = base.CreateProperties(type, memberSerialization); - return props.Where(p => p.Writable).ToList(); - } - - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { - if (member is FieldInfo) { - // just fields - return base.CreateProperty(member, memberSerialization); - } else { - return null; - } - } - } - } -} \ No newline at end of file diff --git a/data/ReplayJsonSerializerSettings.cs b/data/ReplayJsonSerializerSettings.cs deleted file mode 100644 index c0e91cc0bac2420404e66cee32d992ce2f725507..0000000000000000000000000000000000000000 --- a/data/ReplayJsonSerializerSettings.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Newtonsoft.Json; - -namespace Quantum { - public static class ReplayJsonSerializerSettings { - public static JsonSerializerSettings GetSettings() { - return new JsonSerializerSettings { - Formatting = Formatting.Indented, - TypeNameHandling = TypeNameHandling.Auto, - NullValueHandling = NullValueHandling.Ignore, - }; - } - } -} \ No newline at end of file diff --git a/data/ReplayRunnerSample.cs b/data/ReplayRunnerSample.cs deleted file mode 100644 index c1c7f3198f216b1540326fb05c85a656f789a72d..0000000000000000000000000000000000000000 --- a/data/ReplayRunnerSample.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Photon.Deterministic; -using System; -using System.IO; -using System.Threading; - -namespace Quantum { - public class ReplayRunnerSample { - - public static bool Run(string pathToLUT,string pathToDatabaseFile, string pathToReplayFile, string pathToChecksumFile) { - - FPLut.Init(pathToLUT); - - Console.WriteLine($"Loading replay from file: '{Path.GetFileName(pathToReplayFile)}' from folder '{Path.GetDirectoryName(pathToReplayFile)}'"); - - if (!File.Exists(pathToDatabaseFile)) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"File not found: '{pathToReplayFile}'"); - Console.ForegroundColor = ConsoleColor.Gray; - return false; - } - - var serializer = new QuantumJsonSerializer(); - var callbackDispatcher = new CallbackDispatcher(); - var replayFile = serializer.DeserializeReplay(File.ReadAllBytes(pathToReplayFile)); - var inputProvider = new InputProvider(replayFile.DeterministicConfig); - inputProvider.ImportFromList(replayFile.InputHistory); - - var resourceManager = new ResourceManagerStatic(serializer.DeserializeAssets(File.ReadAllBytes(pathToDatabaseFile)), SessionContainer.CreateNativeAllocator()); - - var container = new SessionContainer(replayFile); - container.StartReplay(new QuantumGame.StartParameters { - AssetSerializer = serializer, - CallbackDispatcher = callbackDispatcher, - EventDispatcher = null, - ResourceManager = resourceManager, - }, inputProvider); - - var numberOfFrames = replayFile.Length; - var checksumVerification = String.IsNullOrEmpty(pathToChecksumFile) ? null : new ChecksumVerification(pathToChecksumFile, callbackDispatcher); - - while (container.Session.FramePredicted == null || container.Session.FramePredicted.Number < numberOfFrames) { - Thread.Sleep(1); - container.Service(dt: 1.0f); - - if (Console.KeyAvailable) { - if (Console.ReadKey().Key == ConsoleKey.Escape) { - Console.WriteLine("Stopping replay"); - return false; - } - } - } - - Console.WriteLine($"Ending replay at frame {container.Session.FramePredicted.Number}"); - - checksumVerification?.Dispose(); - container.Destroy(); - - resourceManager.Dispose(); - - return true; - } - } -} diff --git a/data/ResponseCurve.cs b/data/ResponseCurve.cs deleted file mode 100644 index 64d1ebe9c1c3813c8420b5735465de9991ea661c..0000000000000000000000000000000000000000 --- a/data/ResponseCurve.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - [BotSDKHidden] - [System.Serializable] - public unsafe partial class ResponseCurve : AIFunctionFP - { - public AIParamFP Input; - - [BotSDKHidden] - public FPAnimationCurve Curve; - - public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator) - { - base.Loaded(resourceManager, allocator); - } - - public override FP Execute(Frame frame, EntityRef entity = default) - { - if (Input.FunctionRef == default) return 0; - - FP input = Input.ResolveFunction(frame, entity); - FP result = Curve.Evaluate(input); - - if (result > 1) result = 1; - else if (result < 0) result = 0; - - return result; - } - } -} diff --git a/data/RuntimeConfig.User.cs b/data/RuntimeConfig.User.cs deleted file mode 100644 index 418135fc4d703414b5e4a0477d3ec90ce1ca3988..0000000000000000000000000000000000000000 --- a/data/RuntimeConfig.User.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - partial class RuntimeConfig - { - public bool ShowIntroduction; - - public AssetRefHFSMRoot GameManagerHFSM; - - public enum GameMode { CoinGrab, BossBattle }; - public GameMode ConfigType = GameMode.CoinGrab; - - public AssetRefEntityPrototype[] RoomFillBots; - public FP RoomFillInterval = 2; - public bool FillWithBots = true; - - partial void SerializeUserData(BitStream stream) - { - stream.Serialize(ref ShowIntroduction); - stream.Serialize(ref GameManagerHFSM); - - stream.SerializeArrayLength(ref RoomFillBots); - for (var i = 0; i < RoomFillBots.Length; i++) - { - stream.Serialize(ref RoomFillBots[i]); - } - stream.Serialize(ref RoomFillInterval); - - Int32 current = (Int32)ConfigType; - stream.Serialize(ref current); - ConfigType = (GameMode)current; - - stream.Serialize(ref FillWithBots); - } - } -} \ No newline at end of file diff --git a/data/RuntimePlayer.User.cs b/data/RuntimePlayer.User.cs deleted file mode 100644 index 0a8ce8d484a9b927ba60c8c973ebc624c4a5814a..0000000000000000000000000000000000000000 --- a/data/RuntimePlayer.User.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Photon.Deterministic; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Quantum -{ - partial class RuntimePlayer - { - public AssetRefEntityPrototype SelectedCharacter; - public string PlayerName; - public bool ForceAI; - public int TeamIndex; - - partial void SerializeUserData(BitStream stream) - { - stream.Serialize(ref SelectedCharacter); - stream.Serialize(ref PlayerName); - stream.Serialize(ref ForceAI); - stream.Serialize(ref TeamIndex); - } - } -} diff --git a/data/SelfDestroy.cs b/data/SelfDestroy.cs deleted file mode 100644 index 3046e88bf91dfd3df4052476af6a954051bbb367..0000000000000000000000000000000000000000 --- a/data/SelfDestroy.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -public class SelfDestroy : MonoBehaviour -{ - public float TTL = 2; - - void Update() - { - if (TTL < 0) Destroy(gameObject); - TTL -= Time.deltaTime; - } -} \ No newline at end of file diff --git a/data/SetBlackboardInt.cs b/data/SetBlackboardInt.cs deleted file mode 100644 index 1b6c1a0c2c17fbad8b732e11f29e67b3710eff8c..0000000000000000000000000000000000000000 --- a/data/SetBlackboardInt.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace Quantum -{ - [Serializable] - [AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)] - public unsafe partial class SetBlackboardInt : AIAction - { - public AIBlackboardValueKey Key; - public AIParamInt Value; - - public override unsafe void Update(Frame frame, EntityRef entity) - { - var blackboard = frame.Unsafe.GetPointer(entity); - - var agent = frame.Unsafe.GetPointer(entity); - var aiConfig = agent->GetConfig(frame); - - var value = Value.Resolve(frame, entity, blackboard, aiConfig); - blackboard->Set(frame, Key.Key, value); - } - } -} \ No newline at end of file diff --git a/data/SimulationConfig.User.cs b/data/SimulationConfig.User.cs deleted file mode 100644 index 262679851baee19af89cc5f28594ac0e404cc7cd..0000000000000000000000000000000000000000 --- a/data/SimulationConfig.User.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Photon.Deterministic; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Quantum -{ - partial class SimulationConfig : AssetObject - { - - } -} diff --git a/data/SkipCommand.cs b/data/SkipCommand.cs deleted file mode 100644 index a27d3075c621463770d27508ea8abd7d56886acb..0000000000000000000000000000000000000000 --- a/data/SkipCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - [Serializable] - public struct SkipCommandData - { - // game-specific command data here - } - - public class SkipCommand : DeterministicCommand - { - public SkipCommandData Data; - - public override void Serialize(BitStream stream) - { - // serialize command data here - } - } -} diff --git a/data/SpawnFX.cs b/data/SpawnFX.cs deleted file mode 100644 index b39a8392bf0d1667e0449871fef8f45a8cfd2562..0000000000000000000000000000000000000000 --- a/data/SpawnFX.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -public class SpawnFX : MonoBehaviour -{ - public Transform VFXPrefab; - - private void OnDestroy() - { - Instantiate(VFXPrefab, transform.position, Quaternion.identity); - } -} \ No newline at end of file diff --git a/data/StopwatchBlock.cs b/data/StopwatchBlock.cs deleted file mode 100644 index 2f63b83a5e224fe5a060154b645652d760ef8275..0000000000000000000000000000000000000000 --- a/data/StopwatchBlock.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Diagnostics; - -namespace Quantum -{ - public class StopwatchBlock : IDisposable - { - private Stopwatch _stopwatch; - private string _blockName; - - public StopwatchBlock(string blockName) - { - _blockName = blockName; - _stopwatch = new Stopwatch(); - _stopwatch.Start(); - } - - void IDisposable.Dispose() - { - _stopwatch.Stop(); - Log.Info($"{_blockName}: {_stopwatch.Elapsed.TotalMilliseconds} ms"); - } - } -} \ No newline at end of file diff --git a/data/SystemSetup.cs b/data/SystemSetup.cs deleted file mode 100644 index 205fefabecfdca359c1ad8ce61be6134c18acf8f..0000000000000000000000000000000000000000 --- a/data/SystemSetup.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Photon.Deterministic; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Quantum -{ - public static class SystemSetup - { - public static SystemBase[] CreateSystems(RuntimeConfig gameConfig, SimulationConfig simulationConfig) - { - return new SystemBase[] { - // pre-defined core systems - new Core.CullingSystem2D(), - new Core.CullingSystem3D(), - - new Core.PhysicsSystem2D(), - new Core.PhysicsSystem3D(), - -#if DEBUG - Core.DebugCommand.CreateSystem(), -#endif - - new Core.NavigationSystem(), - new Core.EntityPrototypeSystem(), - new Core.PlayerConnectedSystem(), - - new BotSDKDebuggerSystem(), - - new GameplaySystemsGroup("Gameplay Systems", - new MatchSystem(), - new PlayerJoiningSystem(), - new CommandsSystem(), - - new GameManagerSystem(), - new MemorySystem(), - new AISystem(), - new TeamDataSystem(), - - new AttributesSystem(), - new HealthSystem(), - new ImmuneSystem(), - new VisibilitySystem(), - new InputSystem(), - new AttackSystem(), - new RespawnSystem(), - new SkillSystem() - ), - - // We don't add it to the group as it is a SignalsOnly system; no need to be enabled/disabled with the rest - new InventorySystem(), - }; - } - } -} diff --git a/data/Traktor.User.cs b/data/Traktor.User.cs deleted file mode 100644 index 9c6b4383e0994bc590b5ef340eb67b09642b5ee9..0000000000000000000000000000000000000000 --- a/data/Traktor.User.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using Photon.Deterministic; - -namespace Quantum -{ - - unsafe partial struct Traktor - { - public void Update(FrameThreadSafe frame, ref TraktorInputSystem.Filter filter, Input input) - { - TraktorConfig config = frame.FindAsset(Config.Id); - - ChainSystem.Filter sphereData = default; - var getter = frame.ComponentGetter(); - ; - - if (getter.TryGet(frame, Sphere, &sphereData)) - { - FPVector2 direction = input.Direction; - - FPVector3 forward = filter.Transform->Forward; - FPVector3 right = filter.Transform->Right; - FP forwardSpeed = FPVector3.Dot(forward, sphereData.Body->Velocity); - - // now rotate - FP lateralForce = config.LateralForce.Evaluate(forwardSpeed / config.MaxSpeed); - - if (direction.X != 0) - { - FP rotationFactor = lateralForce * config.RotationMultiplier * direction.X; - filter.Transform->Rotate(FP._0, rotationFactor * frame.DeltaTime, FP._0); - } - - FPVector3 force = default; - if (direction.Y > 0) - { - force += direction.Y * forward * config.Acceleration; - } - FP slipSpeed = FPVector3.Dot(right, sphereData.Body->Velocity); - force += right * slipSpeed * config.LateralForceMultiplier; - // swizzle from 2D to 3D - sphereData.Body->AddForce(force); - - // snap to sphere position - filter.Transform->Position = sphereData.Transform->Position; - filter.Transform->Rotation = filter.Transform->Rotation.Normalized; - } - } - } -} diff --git a/data/TraktorConfig.cs b/data/TraktorConfig.cs deleted file mode 100644 index 04867a7acc1817f19a223960709eacb4e4cd9513..0000000000000000000000000000000000000000 --- a/data/TraktorConfig.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - partial class TraktorConfig : AssetObject - { - public FP Acceleration; - public FP Drag; - public FP MaxSpeed; - public FPAnimationCurve LateralForce; - public FP LateralForceMultiplier = 1; - public FP RotationMultiplier = 1; - } -} diff --git a/data/TraktorInputSystem.cs b/data/TraktorInputSystem.cs deleted file mode 100644 index b0587e9f6705db62ca511b33be644096fba34f82..0000000000000000000000000000000000000000 --- a/data/TraktorInputSystem.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using Photon.Deterministic; -using Quantum.Task; - -namespace Quantum -{ - public unsafe class TraktorInputSystem : SystemThreadedFilter, ISignalOnComponentAdded - { - public struct Filter - { - public EntityRef Entity; - public Transform3D* Transform; - public Traktor* Traktor; - } - - public void OnAdded(Frame frame, EntityRef entity, Traktor* traktor) - { - EntityRef sphere = frame.Create(traktor->SpherePrototype); - traktor->Sphere = sphere; - - } - - public override void Update(FrameThreadSafe frame, ref Filter filter) - { - Input input = default; - if (frame.TryGetPointer(filter.Entity, out var controller)) - { - input = *((Frame)frame).GetPlayerInput(controller->Player); - } - filter.Traktor->Update(frame, ref filter, input); - } - } -} diff --git a/data/TraktorView.cs b/data/TraktorView.cs deleted file mode 100644 index 9e34c851c17ebe3faaf6384fb801549a052d19b7..0000000000000000000000000000000000000000 --- a/data/TraktorView.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Quantum; -using UnityEngine; -using Photon.Deterministic; - -public class TraktorView : MonoBehaviour -{ - Vector3 _lastPosition; - public float SpeedThreshold = 0.1f; - public float RaycastDistance = 0.51f; - public float RotationFactor = 8f; - public Transform TraktorBody; - Quaternion _desiredRotation; - private bool _wasGrounded; - private EntityView _view; - private EntityRef _sphere; - void Start() - { - _view = GetComponent(); - var frame = QuantumRunner.Default.Game.Frames.Verified; - var controller = frame.Get(_view.EntityRef); - if (QuantumRunner.Default.Session.IsLocalPlayer(controller.Player)) - { - Camera.main.GetComponent().Target = transform; - } - - if (frame.TryGet(_view.EntityRef, out var traktor)) - { - _sphere = traktor.Sphere; - } - _lastPosition = transform.position; - _desiredRotation = transform.rotation; - } - - private void LateUpdate() - { - var frame = QuantumRunner.Default.Game.Frames.Predicted; - if (frame.TryGet(_sphere, out var drivable) && drivable.Grounded && frame.TryGet(_sphere, out var body)) - { - var direction = body.Velocity.ToUnityVector3(); - _desiredRotation = Quaternion.LookRotation(direction.normalized, drivable.SurfaceNormal.ToUnityVector3()); - var euler = _desiredRotation.eulerAngles; - euler.y = 0; - _desiredRotation = Quaternion.Euler(euler); - } - TraktorBody.localRotation = Quaternion.Slerp(TraktorBody.localRotation, _desiredRotation, RotationFactor * Time.deltaTime); - _lastPosition = transform.position; - } - - -} diff --git a/data/TurnConfig.cs b/data/TurnConfig.cs deleted file mode 100644 index 9bddc135e398d6c5f8c7042cb6d1e1c2855002ee..0000000000000000000000000000000000000000 --- a/data/TurnConfig.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - public partial class TurnConfig - { - public Boolean UsesTimer; - public Int32 TurnDurationInTicks; - public Boolean IsSkippable; - } -} \ No newline at end of file diff --git a/data/TurnData.cs b/data/TurnData.cs deleted file mode 100644 index fdc381704cc1e2d94c91bf3e92f05c3ac55c0402..0000000000000000000000000000000000000000 --- a/data/TurnData.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - partial struct TurnData - { - public void Update(Frame f) - { - var config = f.FindAsset(ConfigRef.Id); - if (config == null || !config.UsesTimer || Status != TurnStatus.Active) - { - return; - } - Ticks++; - if (Ticks >= config.TurnDurationInTicks) - { - f.Signals.OnTurnEnded(this, TurnEndReason.Time); - } - } - - public void AccumulateStats(TurnData from) - { - Ticks += from.Ticks; - Number++; - } - - public void SetType(TurnType newType, Frame f = null) - { - if (Type == newType) - { - return; - } - var previousType = Type; - Type = newType; - f?.Events.TurnTypeChanged(this, previousType); - } - - public void SetStatus(TurnStatus newStatus, Frame f = null) - { - if (Status == newStatus) - { - return; - } - var previousStatus = Status; - Status = newStatus; - f?.Events.TurnStatusChanged(this, previousStatus); - if (Status == TurnStatus.Active) - { - f?.Events.TurnActivated(this); - } - } - - // frame is only necessary if caller wants to raise events - public void ResetTicks(Frame f = null) - { - ResetData(Type, Status, Entity, Player, ConfigRef, f); - } - - public void Reset(TurnConfig config, TurnType type, TurnStatus status, Frame f = null) - { - ResetData(type, status, Entity, Player, config, f); - } - - public void Reset(EntityRef entity, PlayerRef owner, Frame f = null) - { - ResetData(Type, Status, entity, owner, ConfigRef, f); - } - - public void Reset(TurnConfig config, TurnType type, TurnStatus status, EntityRef entity, PlayerRef owner, Frame f = null) - { - ResetData(type, status, entity, owner, config, f); - } - - private void ResetData(TurnType type, TurnStatus status, EntityRef entity, PlayerRef owner, AssetRefTurnConfig config, Frame f = null) - { - if (entity != EntityRef.None) - { - Entity = entity; - } - if (owner != PlayerRef.None) - { - Player = owner; - } - - if (config != null) - { - ConfigRef = config; - } - - var previousType = Type; - Type = type; - var previousStatus = Status; - Status = status; - - Ticks = 0; - - if (Type != previousType) - { - f?.Events.TurnTypeChanged(this, previousType); - } - - if (Status != previousStatus) - { - f?.Events.TurnStatusChanged(this, previousStatus); - if (Status == TurnStatus.Active) - { - f?.Events.TurnActivated(this); - } - } - - f?.Events.TurnTimerReset(this); - } - } -} diff --git a/data/TurnData.qtn b/data/TurnData.qtn deleted file mode 100644 index efb460fb320bbb7b3477713a0a6419a001f06434..0000000000000000000000000000000000000000 --- a/data/TurnData.qtn +++ /dev/null @@ -1,40 +0,0 @@ -import PlayCommandData; -import SkipCommandData; - -asset TurnConfig; - -enum TurnType { Play, Countdown } -enum TurnStatus { Inactive, Active, Resolving } -enum TurnEndReason { Time, Skip, Play, Resolved } - -struct TurnData -{ - player_ref Player; - entity_ref Entity; - asset_ref ConfigRef; - TurnType Type; - TurnStatus Status; - Int32 Number; - Int32 Ticks; -} - -global { - TurnData CurrentTurn; -} - -// ------------------ Signals ------------------ -signal OnTurnEnded (TurnData data, TurnEndReason reason); -signal OnPlayCommandReceived (PlayerRef player, PlayCommandData data); -signal OnSkipCommandReceived (PlayerRef player, SkipCommandData data); - -// ------------------ Events ------------------ -abstract event TurnEvent { TurnData Turn; } -synced event TurnTypeChanged : TurnEvent { TurnType PreviousType; } -synced event TurnStatusChanged : TurnEvent { TurnStatus PreviousStatus; } -synced event TurnEnded : TurnEvent { TurnEndReason Reason; } -synced event TurnTimerReset : TurnEvent { } -synced event TurnActivated : TurnEvent { } - -abstract event CommandEvent { player_ref Player; } -event PlayCommandReceived : CommandEvent { PlayCommandData Data; } -event SkipCommandReceived : CommandEvent { SkipCommandData Data; } \ No newline at end of file diff --git a/data/TurnData.txt b/data/TurnData.txt deleted file mode 100644 index fdc381704cc1e2d94c91bf3e92f05c3ac55c0402..0000000000000000000000000000000000000000 --- a/data/TurnData.txt +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using Photon.Deterministic; - -namespace Quantum -{ - partial struct TurnData - { - public void Update(Frame f) - { - var config = f.FindAsset(ConfigRef.Id); - if (config == null || !config.UsesTimer || Status != TurnStatus.Active) - { - return; - } - Ticks++; - if (Ticks >= config.TurnDurationInTicks) - { - f.Signals.OnTurnEnded(this, TurnEndReason.Time); - } - } - - public void AccumulateStats(TurnData from) - { - Ticks += from.Ticks; - Number++; - } - - public void SetType(TurnType newType, Frame f = null) - { - if (Type == newType) - { - return; - } - var previousType = Type; - Type = newType; - f?.Events.TurnTypeChanged(this, previousType); - } - - public void SetStatus(TurnStatus newStatus, Frame f = null) - { - if (Status == newStatus) - { - return; - } - var previousStatus = Status; - Status = newStatus; - f?.Events.TurnStatusChanged(this, previousStatus); - if (Status == TurnStatus.Active) - { - f?.Events.TurnActivated(this); - } - } - - // frame is only necessary if caller wants to raise events - public void ResetTicks(Frame f = null) - { - ResetData(Type, Status, Entity, Player, ConfigRef, f); - } - - public void Reset(TurnConfig config, TurnType type, TurnStatus status, Frame f = null) - { - ResetData(type, status, Entity, Player, config, f); - } - - public void Reset(EntityRef entity, PlayerRef owner, Frame f = null) - { - ResetData(Type, Status, entity, owner, ConfigRef, f); - } - - public void Reset(TurnConfig config, TurnType type, TurnStatus status, EntityRef entity, PlayerRef owner, Frame f = null) - { - ResetData(type, status, entity, owner, config, f); - } - - private void ResetData(TurnType type, TurnStatus status, EntityRef entity, PlayerRef owner, AssetRefTurnConfig config, Frame f = null) - { - if (entity != EntityRef.None) - { - Entity = entity; - } - if (owner != PlayerRef.None) - { - Player = owner; - } - - if (config != null) - { - ConfigRef = config; - } - - var previousType = Type; - Type = type; - var previousStatus = Status; - Status = status; - - Ticks = 0; - - if (Type != previousType) - { - f?.Events.TurnTypeChanged(this, previousType); - } - - if (Status != previousStatus) - { - f?.Events.TurnStatusChanged(this, previousStatus); - if (Status == TurnStatus.Active) - { - f?.Events.TurnActivated(this); - } - } - - f?.Events.TurnTimerReset(this); - } - } -} diff --git a/data/TurnTimerSystem.cs b/data/TurnTimerSystem.cs deleted file mode 100644 index d9b88ba7fa78778f178ad8646824f7796baa0e41..0000000000000000000000000000000000000000 --- a/data/TurnTimerSystem.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Photon.Deterministic; - -namespace Quantum -{ - public unsafe class TurnTimerSystem : SystemMainThread - { - public override void Update(Frame f) - { - f.Global->CurrentTurn.Update(f); - } - } -} \ No newline at end of file diff --git a/data/UT.qtn b/data/UT.qtn deleted file mode 100644 index f1d3d2f9a2b9b020ab3926a4cfad12c669af203c..0000000000000000000000000000000000000000 --- a/data/UT.qtn +++ /dev/null @@ -1,30 +0,0 @@ -asset UTRoot; -asset Consideration; - -component UTAgent -{ - UtilityReasoner UtilityReasoner; - AssetRefAIConfig Config; -} - -struct UtilityReasoner -{ - AssetRefUTRoot UTRoot; - [HideInInspector] list Considerations; - [HideInInspector] list MomentumList; - [HideInInspector] FP TimeToTick; - [HideInInspector] dictionary CooldownsDict; - [HideInInspector] list PreviousExecution; -} - -struct UTMomentumPack -{ - AssetRefConsideration ConsiderationRef; - UTMomentumData MomentumData; -} - -struct UTMomentumData -{ - Int32 Value; - Byte DecayAmount; -} diff --git a/data/UTAgent.User.cs b/data/UTAgent.User.cs deleted file mode 100644 index 785c1cd87c8d3d8497ed1abc901caf155e5957ad..0000000000000000000000000000000000000000 --- a/data/UTAgent.User.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Quantum -{ - public partial struct UTAgent - { - // Used to setup info on the Unity debugger - public string GetRootAssetName(Frame frame) => frame.FindAsset(UtilityReasoner.UTRoot.Id).Path; - - public AIConfig GetConfig(Frame frame) - { - return frame.FindAsset(Config.Id); - } - } -} diff --git a/data/UTManager.cs b/data/UTManager.cs deleted file mode 100644 index 765ee864da698469be46f23fdc1635561a5960e2..0000000000000000000000000000000000000000 --- a/data/UTManager.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; - -namespace Quantum -{ - public unsafe static class UTManager - { - public static Action SetupDebugger; - - public static Action ConsiderationChosen; - public static Action OnUpdate; - - /// - /// Initializes the Utility Reasoner, allocating all frame data needed. - /// If no UTRoot asset is passed by parameter, it will try to initialize with one already set on the Component, if any. - /// - public static void Init(Frame frame, UtilityReasoner* reasoner, AssetRefUTRoot utRootRef = default, EntityRef entity = default) - { - reasoner->Initialize(frame, utRootRef, entity); - } - - public static void Free(Frame frame, UtilityReasoner* reasoner) - { - reasoner->Free(frame); - } - - /// - /// Ticks the UtilityReasoner. The Considerations will be evaluated and the most useful will be executed. - /// It can be agnostic to entities, meaning that it is possible to have a UtilityReasoner as part of Global - /// - /// - /// - /// - public static void Update(Frame frame, UtilityReasoner* reasoner, EntityRef entity = default) - { - if (entity != default) - { - OnUpdate?.Invoke(entity); - } - - if (reasoner == default && entity != default) - { - reasoner = &frame.Unsafe.GetPointer(entity)->UtilityReasoner; - } - - reasoner->Update(frame, reasoner, entity); - } - } -} diff --git a/data/UTMomentumData.User.cs b/data/UTMomentumData.User.cs deleted file mode 100644 index 23fdeb20a2ce5d590c76f2934ea8bc5daeff436d..0000000000000000000000000000000000000000 --- a/data/UTMomentumData.User.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Quantum -{ - [System.Serializable] - public partial struct UTMomentumData - { - } -} diff --git a/data/UTRoot.cs b/data/UTRoot.cs deleted file mode 100644 index ea3178fea3ff513a14cb9f592a22f02d13a059c5..0000000000000000000000000000000000000000 --- a/data/UTRoot.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Quantum -{ - public partial class UTRoot - { - public AssetRefConsideration[] ConsiderationsRefs; - } -} diff --git a/data/UseCardCommand.cs b/data/UseCardCommand.cs deleted file mode 100644 index 41d87c969f94583a0da89ab76bcb5d36637fa29e..0000000000000000000000000000000000000000 --- a/data/UseCardCommand.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Quantum -{ - using Photon.Deterministic; - - public class UseCardCommand : DeterministicCommand - { - public byte CardIndex; - public FPVector2 Position; - - public override void Serialize(BitStream stream) - { - stream.Serialize(ref CardIndex); - stream.Serialize(ref Position); - } - } -} diff --git a/data/UtilityReasoner.User.cs b/data/UtilityReasoner.User.cs deleted file mode 100644 index 2a18635e3b270e0b2efc33f78fd16c242932de9c..0000000000000000000000000000000000000000 --- a/data/UtilityReasoner.User.cs +++ /dev/null @@ -1,352 +0,0 @@ -using Photon.Deterministic; -using Quantum.Collections; - -namespace Quantum -{ - public unsafe partial struct UtilityReasoner - { - public void Initialize(Frame frame, AssetRefUTRoot utRootRef = default, EntityRef entity = default) - { - // If we don't receive the UTRoot as parameter, we try to find it on the component itself - // Useful for pre-seting the UTRoot on a Prototype - UTRoot utRootInstance; - if (utRootRef == default) - { - utRootInstance = frame.FindAsset(UTRoot.Id); - } - else - { - UTRoot = utRootRef; - utRootInstance = frame.FindAsset(utRootRef.Id); - } - - // Initialize the Reasoner's considerations. - // Can be useful further when creating dynamically added Considerations to the Agent (runtime) - QList considerationsList = frame.AllocateList(); - for (int i = 0; i < utRootInstance.ConsiderationsRefs.Length; i++) - { - considerationsList.Add(utRootInstance.ConsiderationsRefs[i]); - } - Considerations = considerationsList; - - CooldownsDict = frame.AllocateDictionary(); - - QList previousExecution = frame.AllocateList(); - for (int i = 0; i < 6; i++) - { - previousExecution.Add(default); - } - PreviousExecution = previousExecution; - - MomentumList = frame.AllocateList(); - - if (entity != default) - { - UTManager.SetupDebugger?.Invoke(entity, utRootInstance.Path); - } - } - - public void Free(Frame frame) - { - UTRoot = default; - frame.FreeList(Considerations); - frame.FreeDictionary(CooldownsDict); - frame.FreeList(PreviousExecution); - frame.FreeList(MomentumList); - } - - public void Update(Frame frame, UtilityReasoner* reasoner, EntityRef entity = default) - { - Consideration[] considerations = SolveConsiderationsList(frame, Considerations, reasoner, entity); - Consideration chosenConsideration = SelectBestConsideration(frame, considerations, 1, reasoner, entity); - - if (chosenConsideration != default) - { - chosenConsideration.OnUpdate(frame, reasoner, entity); - UTManager.ConsiderationChosen?.Invoke(entity, chosenConsideration.Identifier.Guid.Value); - } - - TickMomentum(frame, entity); - } - - public Consideration[] SolveConsiderationsList(Frame frame, QListPtr considerationsRefs, UtilityReasoner* reasoner, EntityRef entity = default) - { - QList solvedRefs = frame.ResolveList(considerationsRefs); - Consideration[] considerationsArray = new Consideration[solvedRefs.Count]; - for (int i = 0; i < solvedRefs.Count; i++) - { - considerationsArray[i] = frame.FindAsset(solvedRefs[i].Id); - } - - return considerationsArray; - } - - public Consideration SelectBestConsideration(Frame frame, Consideration[] considerations, byte depth, UtilityReasoner* reasoner, EntityRef entity = default) - { - if (considerations == default) - return null; - - QList momentumList = frame.ResolveList(MomentumList); - - // We get the Rank of every Consideration Set - // This "filters" the Considerations with higher absolute utility - AssetRefConsideration[] highRankConsiderations = new AssetRefConsideration[considerations.Length]; - int highestRank = -1; - int counter = 0; - QDictionary cooldowns = frame.ResolveDictionary(CooldownsDict); - - for (int i = 0; i < considerations.Length; i++) - { - Consideration consideration = considerations[i]; - - // Force low Rank for Considerations in Cooldown - if (cooldowns.Count > 0 && cooldowns.ContainsKey(considerations[i]) == true) - { - cooldowns[considerations[i]] -= frame.DeltaTime; - if (cooldowns[considerations[i]] <= 0) - { - cooldowns.Remove(considerations[i]); - } - { - continue; - } - } - - // If the Consideration has Momentum, then it's Rank should is defined by it - // Otherwise, we calculate the Rank dynamically - int rank; - if (ContainsMomentum(momentumList, consideration, out var momentum) == true) - { - rank = momentum.Value; - } - else - { - rank = consideration.GetRank(frame, entity); - } - - if (rank > highestRank) - { - counter = 0; - - highestRank = rank; - highRankConsiderations[counter] = considerations[i]; - } - else if (highestRank == rank) - { - counter++; - highRankConsiderations[counter] = considerations[i]; - } - } - - // We clean the indices on the high rank sets that were not selected - for (int i = counter + 1; i < highRankConsiderations.Length; i++) - { - if (highRankConsiderations[i] == default) - break; - - highRankConsiderations[i] = default; - } - - // Based on the higher rank, we check which Considerations sets have greater utility - // Then we choose that set this frame - Consideration chosenConsideration = default; - FP highestScore = FP.UseableMin; - for (int i = 0; i <= counter; i++) - { - if (highRankConsiderations[i] == default) - continue; - - Consideration consideration = frame.FindAsset(highRankConsiderations[i].Id); - - FP score = consideration.Score(frame, entity); - if (highestScore < score) - { - highestScore = score; - chosenConsideration = consideration; - } - } - - if (chosenConsideration != default) - { - // If the chosen Consideration and it is not already under Momentum, - // we add add it there, replacing the previous Momentum (if any) - if (chosenConsideration.MomentumData.Value > 0 && ContainsMomentum(momentumList, chosenConsideration, out var momentum) == false) - { - InsertMomentum(frame, momentumList, chosenConsideration); - } - - // If the chosen Consideration has cooldown and it is not yet on the cooldowns dictionary, - // we add it there - if (chosenConsideration.Cooldown > 0 && cooldowns.ContainsKey(chosenConsideration) == false) - { - cooldowns.Add(chosenConsideration, chosenConsideration.Cooldown); - } - - // Add the chosen set to the choices history - OnConsiderationChosen(frame, reasoner, chosenConsideration, entity); - } - else - { - OnNoConsiderationChosen(frame, reasoner, depth, entity); - } - - // We return the chosen set so it can be executed - return chosenConsideration; - } - - #region Momentum - private bool ContainsMomentum(QList momentumList, Consideration consideration, out UTMomentumData momentum) - { - for (int i = 0; i < momentumList.Count; i++) - { - if (momentumList[i].ConsiderationRef == consideration) - { - momentum = momentumList[i].MomentumData; - return true; - } - } - - momentum = default; - return false; - } - - private void InsertMomentum(Frame frame, QList momentumList, AssetRefConsideration considerationRef) - { - Consideration newConsideration = frame.FindAsset(considerationRef.Id); - - // First, we check if this should be a replacement, which happens if: - // . The momentum list already have that same Depth added - // . Or when it have a higher Depth added - bool wasReplacedment = false; - for (int i = 0; i < momentumList.Count; i++) - { - Consideration currentConsideration = frame.FindAsset(momentumList[i].ConsiderationRef.Id); - if (currentConsideration.Depth == newConsideration.Depth || currentConsideration.Depth > newConsideration.Depth) - { - momentumList.GetPointer(i)->ConsiderationRef = considerationRef; - momentumList.GetPointer(i)->MomentumData = frame.FindAsset(considerationRef.Id).MomentumData; - - // We clear the rightmost momentum entries - if (i < momentumList.Count - 1) - { - for (int k = i + 1; k < momentumList.Count; k++) - { - momentumList.RemoveAt(k); - } - } - - wasReplacedment = true; - break; - } - } - - // If there was no replacement, we simply add it to the end of the list as this - // consideration probably has higher Depth than the others currently on the list - // which can also mean that the list was empty - if (wasReplacedment == false) - { - UTMomentumPack newMomentum = new UTMomentumPack() - { - ConsiderationRef = considerationRef, - MomentumData = frame.FindAsset(considerationRef.Id).MomentumData, - }; - momentumList.Add(newMomentum); - } - } - - private void TickMomentum(Frame frame, EntityRef entity = default) - { - QList momentumList = frame.ResolveList(MomentumList); - - // We decrease the timer and check if it is time already to decay all of the current Momentums - TimeToTick -= frame.DeltaTime; - bool decay = false; - if (TimeToTick <= 0) - { - decay = true; - TimeToTick = 1; - } - - for (int i = 0; i < momentumList.Count; i++) - { - UTMomentumPack* momentum = momentumList.GetPointer(i); - - // If we currently have a commitment, we check if it is done already - // If it is done, that Consideration's Rank shall be re-calculated - // If it is not done, then the Consideration's Rank will be kept due to the commitment - // unless some other Consideration has greater Rank and replaces the current commitment - Consideration momentumConsideration = frame.FindAsset(momentum->ConsiderationRef.Id); - - if (momentum->MomentumData.Value > 0 && momentumConsideration.MomentumData.DecayAmount > 0) - { - if (decay) - { - momentum->MomentumData.Value -= momentumConsideration.MomentumData.DecayAmount; - } - } - - - bool isDone = false; - if (momentumConsideration.Commitment != default) - { - isDone = momentumConsideration.Commitment.Execute(frame, entity); - } - if (isDone == true || momentum->MomentumData.Value <= 0) - { - momentum->MomentumData.Value = 0; - momentumList.RemoveAt(i); - } - } - } - #endregion - - #region ConsiderationsChoiceReactions - private static void OnConsiderationChosen(Frame frame, UtilityReasoner* reasoner, AssetRefConsideration chosenConsiderationRef, EntityRef entity = default) - { - Consideration chosenConsideration = frame.FindAsset(chosenConsiderationRef.Id); - - QList previousExecution = frame.ResolveList(reasoner->PreviousExecution); - - if (previousExecution[chosenConsideration.Depth - 1] != chosenConsideration) - { - // Exit the one that we're replacing - var replacedSet = frame.FindAsset(previousExecution[chosenConsideration.Depth - 1].Id); - if (replacedSet != default) - { - replacedSet.OnExit(frame, reasoner, entity); - } - - // Exit the consecutive ones - for (int i = chosenConsideration.Depth; i < previousExecution.Count; i++) - { - var cs = frame.FindAsset(previousExecution[i].Id); - if (cs == default) - break; - - cs.OnExit(frame, reasoner, entity); - previousExecution[i] = default; - } - - // Insert and Enter on the new chosen consideration - previousExecution[chosenConsideration.Depth - 1] = chosenConsideration; - chosenConsideration.OnEnter(frame, reasoner, entity); - } - } - - private static void OnNoConsiderationChosen(Frame frame, UtilityReasoner* reasoner, byte depth, EntityRef entity = default) - { - QList previousExecution = frame.ResolveList(reasoner->PreviousExecution); - - for (int i = depth - 1; i < previousExecution.Count; i++) - { - var cs = frame.FindAsset(previousExecution[i].Id); - if (cs == default) - break; - - cs.OnExit(frame, reasoner, entity); - previousExecution[i] = default; - } - } - #endregion - } -} diff --git a/data/WaitLeaf.cs b/data/WaitLeaf.cs deleted file mode 100644 index 9bbe9d60877b76b2fe5034dfac2aa937d179537e..0000000000000000000000000000000000000000 --- a/data/WaitLeaf.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Photon.Deterministic; -using System; - -namespace Quantum -{ - [Serializable] - public unsafe partial class WaitLeaf : BTLeaf - { - // How many time shall be waited - // This is measured in seconds - public FP Duration; - - // Indexer for us to store the End Time value on the BTAgent itself - public BTDataIndex EndTimeIndex; - - public override void Init(Frame frame, AIBlackboardComponent* blackboard, BTAgent* agent) - { - base.Init(frame, blackboard, agent); - - // We allocate space for the End Time on the Agent so we can change it in runtime - agent->AddFPData(frame, 0); - } - - public override void OnEnter(BTParams btParams) - { - base.OnEnter(btParams); - - FP currentTime; - FP endTime; - - // Get the current time - currentTime = btParams.Frame.BotSDKGameTime; - // Add the Duration value so we know when the Leaf will stop running - endTime = currentTime + Duration; - - // Store the final value on the Agent data - btParams.Agent->SetFPData(btParams.Frame, endTime, EndTimeIndex.Index); - } - - protected override BTStatus OnUpdate(BTParams btParams) - { - FP currentTime; - FP endTime; - - currentTime = btParams.Frame.BotSDKGameTime; - endTime = btParams.Agent->GetFPData(btParams.Frame, EndTimeIndex.Index); - - // If waiting time isn't over yet, then we need more frames executing this Leaf - // So we say that we're still Running - if (currentTime < endTime) - { - return BTStatus.Running; - } - - // If the waiting time is over, then we succeeded on waiting that amount of time - // Then we return Success - return BTStatus.Success; - } - } -} diff --git a/data/animation.txt b/data/animation.txt deleted file mode 100644 index f81ac133093ad147aa90614f576af5d113fb76df..0000000000000000000000000000000000000000 --- a/data/animation.txt +++ /dev/null @@ -1,167 +0,0 @@ -Animation -Overview -Polling Based Animation -Trigger Events -Tips -Deterministic Animation -Tips - -Overview -In Quantum there are two distinct ways to handle animation: - -poll the game state from Unity; and, -deterministic animation using the Custom Animator. -Back To Top - - -Polling Based Animation -Most games use animation to communicate the state of an object to the player. For instance, when the playable character is walking or jumping, the animations are actually In Place animations and the perceived movement is driven by code. - -In other words, the scripts managing the characters' respective (Unity) animator are stateless and simply derive values to pass on as animation parameters based on data polled from the game simulation (in Quantum). - -N.B.: If the gameplay systems rely on Root Motion or have to be aware of the animation state, then skip to the next section. - -The polling based animation concept has been implemented in the API Sample. The following snippet is extracted from the PlayerAnimations script and drives the movement animation. - -When the entity is instantiated, its Initialize() method is called which caches the entity's EntityRef and the current QuantumGame; the latter one is purely for convenience. -Every Unity Update(), the MovementAnimation() function is called. -The MovementAnimation() function polls data from the CharacterController3D using the previously cached EntityRef. -The relevant animator parameters are derived from the polled data. -The computed data is passed on to the Unity Animator. -// This snippet is extracted from the Quantum API Sample. - -public unsafe class PlayerAnimation : MonoBehaviour -{ - [SerializeField] private Animator _animator = null; - - private EntityRef _entityRef = default; - private QuantumGame _game = null; - - // Animator Parameters - private const string FLOAT_MOVEMENT_SPEED = "floatMovementSpeed"; - private const string FLOAT_MOVEMENT_VERTICAL = "floatVerticalMovement"; - private const string BOOL_IS_MOVING = "boolIsMoving"; - - // This method is called from the PlayerSetup.cs Initialize() method which is registered - // to the EntityView's OnEntityInstantiated event located on the parent GameObject - public void Initialize(PlayerRef playerRef, EntityRef entityRef){ - _playerRef = playerRef; - _entityRef = entityRef; - _game = QuantumRunner.Default.Game; - } - - // Update is called once per frame - void Update(){ - MovementAnimation(); - } - - private void MovementAnimation() { - var kcc = _game.Frames.Verified.Unsafe.GetPointer(_entityRef); - bool isMoving = kcc->Velocity.Magnitude.AsFloat > 0.2f; - - _animator.SetBool(BOOL_IS_MOVING, isMoving); - - if (isMoving) { - _animator.SetFloat(FLOAT_MOVEMENT_SPEED, kcc->Velocity.Magnitude.AsFloat); - _animator.SetFloat(FLOAT_MOVEMENT_VERTICAL, kcc->Velocity.Z.AsFloat); - } - else { - _animator.SetFloat(FLOAT_MOVEMENT_SPEED, 0.0f); - _animator.SetFloat(FLOAT_MOVEMENT_VERTICAL, 0.0f); - } - } -Back To Top - - -Trigger Events -Some animations are based on a particular events taking place in the game; e.g. a player pressing jump or getting hit by an enemy. In these cases, it is usually preferable to raise an event from the simulation and have the view listen to it. This ensures decoupling and work well in conjunction with the polling based animation approach. - -For a comprehensive explanation on events and callbacks, refer to the Quantum ECS > Game Callbacks page in the Manual. - -The API Sample uses events to communicate when punctual actions happen that should result in a visual reaction; e.g. the playable character jumping. - -The MovementSystem in Quantum reads the player input and computes the movement values before passing them on to the CharacterController3D. The system also listens to the Jump key press. If the key WasPressed, it raises a PlayerJump event and calls the CharacterController3D's Jump() method. - -using Photon.Deterministic; -namespace Quantum -{ - public unsafe struct PlayerMovementFilter - { - public EntityRef EntityRef; - public PlayerID* PlayerID; - public Transform3D* Transform; - public CharacterController3D* Kcc; - } - - unsafe class MovementSystem : SystemMainThreadFilter - { - public override void Update(Frame f, ref PlayerMovementFilter filter) - { - var input = f.GetPlayerInput(filter.PlayerID->PlayerRef); - - // Other Logic - - if (input->Jump.WasPressed) - { - f.Events.PlayerJump(filter.PlayerID->PlayerRef); - filter.Kcc->Jump(f); - } - - // Other Logic - } - } -} -On the Unity side, the PlayerAnimation script listens to the PlayerJump event and reacts to it. These steps are necessary to achieve this: - -Define a method that can receive the event - void Jump(EventPlayerJump e). -Subscribe to the event in question. -When the event is received, check it is meant for the GameObject the script is located on by comparing the PlayerRef contained in the event against the one cached earlier. -Trigger / Set the parameter/s in the Unity Animator. -// This snippet is extracted from the Quantum API Sample. - -public unsafe class PlayerAnimation : MonoBehaviour -{ - [SerializeField] private Animator _animator = null; - - private PlayerRef _playerRef = default; - private QuantumGame _game = null; - - // Animator Parameters - private const string TRIGGER_JUMP = "triggerJump"; - - public void Initialize(PlayerRef playerRef, EntityRef entityRef) - { - _playerRef = playerRef; - - // Other Logic - - QuantumEvent.Subscribe(this, Jump); - } - - private void Jump(EventPlayerJump e) - { - if (e.PlayerRef != _playerRef) return; - _animator.SetTrigger(TRIGGER_JUMP); - } -Back To Top - - -Tips -Place the model and its animator component on a child object. -Events are not part of the game state and thus are not available to late/re-joining players. It is therefore advisable to first initialize the animation state by polling the latest game state if the game has already started. -Use synchronised events for animations that need to be triggered with 100% accuracy, e.g. a victory celebration. -Use regular non-synchronised events for animations that need to be snappy, e.g. getting hit. -Use the EventCanceled callback to graciously exit from animations triggered by a cancelled non-synchronised events. This can happen when the event was raised as part of a prediction but was rolled back during a verified frame. -Back To Top - - -Deterministic Animation -The main advantage of using a deterministic animation system is tick precise animations which are 100% synchronised across all clients and will snap to the correct state in case of a rollback. While this may sounds ideal, it comes with a performance impact since animations and their state are now part of the simulated game state. In reality only few games require and benefit from a deterministic animation system; among those are Fighting games and some Sports games,. - -The Custom Animator is a tool enabling deterministic animation. It works by baking information from Unity’s Mecanim Controller and importing every configuration such as the states, the transitions between the states, the motion clips and so on. - -Development of the Custom Animator has been halted due to the dependencies it created with Unity's Mecanim. However, the code has been open sourced and is available for download on the Addons > Custom Animator page. This page also provides an overview and a quick-guide on how to import and use the Custom Animator. - -Keep in mind its features are limited and it will likely have to be adapted to your needs. - - diff --git a/data/assets simulation.txt b/data/assets simulation.txt deleted file mode 100644 index 91cb2f00b79a497e6117449281dde24db43b0c67..0000000000000000000000000000000000000000 --- a/data/assets simulation.txt +++ /dev/null @@ -1,257 +0,0 @@ -Data Asset Classes -Quantum assets are normal C# classes that will act as immutable data containers during runtime. A few rules define how these assets must be designed, implemented and used in Quantum. - -Here is a minimal definition of an asset class (for a character spec) with some simple deterministic properties: - -namespace Quantum { - partial class CharacterSpec { - public FP Speed; - public FP MaxHealth; - } -} -Notice that the asset class definition must be partial and it has to be contained specifically in the Quantum namespace. - -Creating and loading instances of asset classes into the database (editing from Unity) will be covered later in this chapter. - -Back To Top - - -Using And Linking Assets -To tell Quantum this is an asset class (adding internal meta-data by making it inherit the basic AssetObject class and preparing the database to contain instances of it): - -// this goes into a DSL file -asset CharacterSpec; -Asset instances are immutable objects that must be carried as references. Because normal C# object references are not allowed to be included into our memory aligned ECS structs, the asset_ref special type must be used inside the DSL to declare properties inside the game state (from entities, components or any other transient data structure): - -component CharacterData { - // reference to an immutable instance of CharacterSpec (from the Quantum asset database) - asset_ref Spec; - // other component data -} -To assign an asset reference when creating a Character entity, one option is to obtain the instance directly from the frame asset database and set it to the property: - -// assuming cd is a pointer to the CharacterData component -// using the SLOW string path option (fast data driven asset refs will be explained next) -cd->Spec = frame.FindAsset("path-to-spec"); -The basic use of assets is to read data in runtime and apply it to any computation inside systems. The following example uses the Speed value from the assigned CharacterSpec to compute the corresponding character velocity (physics engine): - -// consider cd a CharacterData*, and body a PhysicsBody2D* (from a component filter, for example) -var spec = frame.FindAsset(cd->Spec.Id); -body->Velocity = FPVector2.Right * spec.Speed; -Back To Top - - -A Note On Determinism -Notice that the above code only reads the Speed property to compute the desired velocity for the character during runtime, but its value (speed) is never changed. - -It is completely safe and valid to switch a game state asset reference in runtime from inside an Update (as asset_ref is a rollback-able type which hence can be part of the game state). - -However, changing the values of properties of a data asset is NOT DETERMINISTIC (as the internal data on assets is not considered part of the game state, so it is never rolled back). - -The following snippet shows examples of what is safe (switching refs) and not safe (changing internal data) during runtime: - -// cd is a CharacterData* - -// this is VALID and SAFE, as the CharacterSpec asset ref is part of the game state -cd->Spec = frame.FindAsset("anotherCharacterSpec-path"); - -// this is NOR valid NEITHER deterministic, as the internal data from an asset is NOT part of the transient game state: -var spec = frame.FindAsset("anotherCharacterSpec-path"); -// (DO NOT do this) changing a value directly in the asset object instance -spec.Speed = 10; -Back To Top - - -AssetObjectConfig Attribute -You can fine-tune the asset linking script generation with the AssetObjectConfig attribute. - -[AssetObjectConfig(GenerateLinkingScripts = false)] -partial class CharacterSpec { - // ... -} -GenerateLinkingScripts (default=true) - prevent the generation of any scripts that make the asset editable in Unity. -GenerateAssetCreateMenu (default=true) - prevent the generation of the Unity CreateAssetMenu attribute for this asset. -GenerateAssetResetMethod (default=true) - prevent the generation of the Unity Scriptable Object Reset() method (where a Guid is automatically generated when the asset is created). -CustomCreateAssetMenuName (default=null) - overwrite the CreateAssetMenu name. If set to null an menu path is generated automatically using the inheritance graph. -CustomCreateAssetMenuOrder (default=-1) - overwrite the CreateAssetMenu order. If set to -1 an alphabetical order is used. -Back To Top - - -Overwrite Asset Script Location And Disable AOT File Generation -Overwrite the default asset script destination folder and disable AOT file generation. - -See the tool section: quantum codegen unity - -Back To Top - - -Asset Inheritance -It is possible to use inheritance in data assets, which gives much more flexibility to the developer (specially when used together with polymorphic methods). - -The basic step for inheritance is to create an abstract base asset class (we'll continue with our CharacterSpec example): - -namespace Quantum { - partial abstract class CharacterSpec { - public FP Speed; - public FP MaxHealth; - } -} -Concrete sub-classes of CharacterSpec may add custom data properties of their own, and must be marked as Serializable types: - -namespace Quantum { - [Serializable] - public partial class MageSpec : CharacterSpec { - public FP HealthRegenerationFactor; - } - - [Serializable] - public partial class WarriorSpec : CharacterSpec { - public FP Armour; - } -} -Back To Top - - -In The DSL -Once you have declared an asset in the DSL, you can use the asset_ref type in the DSL to hold references to the base class and any of its subclasses. - -component CharacterData { - // Accepts asset references to CharacterSpec base class and its sub-classes(MageSpec and WarriorSpec). - asset_ref ClassDefinition; - FP CooldownTimer; -} -Should you want to keep a reference specific to a sub-class, the derived asset needs to be declared in the DSL first using asset import: - -asset CharacterSpec; -asset import MageSpec; -If the derived asset has already been declared in the DSL, simply use asset_ref as for the base class. For instance, to use the MageSpec directly in the DSL instead of CharacterSpec, we would have to write the following: - -component MageData { - // Only accepts asset references to MageSpec class. - asset_ref ClassDefinition; - FP CooldownTimer; -} -Back To Top - - -Data-Driven Polymorphism -Having gameplay logic to direct evaluate (in if or switch statements) the concrete CharacterSpec class would be very bad design, so asset inheritance makes more sense when coupled with polymorphic methods. - -Notice that adding logic to data assets means implementing logic in the quantum.state project and this logic still have to consider the following restrictions: - -Operate on transient game state data: that means logic methods in data assets must receive transient data as parameters (either entity pointers or the Frame object itself); -Only read, never modify data on the assets themselves: assets must still be treated as immutable read-only instances; -The following example adds a virtual method to the base class, and a custom implementation to one of the subclasses (notice we use the Health field defined for the Character entity more to the top of this document): - -namespace Quantum { - partial unsafe abstract class CharacterSpec { - public FP Speed; - public FP MaxHealth; - public virtual void Update(Frame f, EntityRef e, CharacterData* cd) { - if (cd->Health < 0) - f.Destroy(e); - } - } - - [Serializable] - public partial unsafe class MageSpec : CharacterSpec { - public FP HealthRegenerationFactor; - // reads data from own instance and uses it to update transient health of Character pointer passed as param - public override void Update(Frame f, EntityRef e, CharacterData* cd) { - cd->Health += HealthRegenerationFactor * f.DeltaTime; - base.Update(f, e, cd); - } - } -} -To use this flexible method implementation independently of the concrete asset assigned to each CharacterData, this could be executed from any System: - -// Assuming cd is the pointer to a specific entity's CharacterData component, and entity is the corresponding EntityRef: - -var spec = frame.FindAsset(cd->Spec.Id); -// Updating Health using data-driven polymorphism (behavior depends on the data asset type and instance assigned to character -spec.Update(frame, entity, cd); -Back To Top - - -Using DSL Generated Structs In Assets -Structs defined in the DSL can also be used on assets. The DSL struct must be annotated with the [Serializable] attribute, otherwise the data is not inspectable in Unity. - -[Serializable] -struct Foo { - int Bar; -} - -asset FooUser; -Using the DSL struct in a Quantum asset. - -namespace Quantum { - public partial class FooUser { - public Foo F; - } -} -If a struct is not [Serializable]-friendly (e.g. because it is an union or contains a Quantum collection), prototype can be used instead: - -using Quantum.Prototypes; -namespace Quantum { - public partial class FooUser { - public Foo_Prototype F; - } -} -The prototype can be materialized into the simulation struct when needed: - -Foo f = new Foo(); -fooUser.F.Materialize(frame, ref f, default); -Back To Top - - -Dynamic Assets -Assets can be created at runtime, by the simulation. This feature is called DynamicAssetDB. - -var assetGuid = frame.AddAsset(new MageSpec() { - Speed = 1, - MaxHealth = 100, - HealthRegenerationFactor = 1 -}); -Such asset can be loaded and disposed of just like any other asset: - -MageSpec asset = frame.FindAsset(assetGuid); -frame.DisposeAsset(assetGuid); -Dynamic assets are not synced between peers. Instead, the code that creates new assets needs to be deterministic and ensure that each peer will an asset using the same values. - -The only exception to the rule above is when there is a late-join - the new client will receive a snapshot of the DynamicAssetDB along with the latest frame data. Unlike serialization of the frame, serialization and deserialization of dynamic assets is delegated outside of the simulation, to IAssetSerializer interface. When run in Unity, QuantumUnityJsonSerializer is used by default: it is able to serialize/deserialize any Unity-serializable type. - -Back To Top - - -Initializing DynamicAssetDB -Simulation can be initialized with preexisting dynamic assets. Similar to adding assets during the simulation, these need to be deterministic across clients. - -First, an instance of DynamicAssetDB needs to be created and filled with assets: - -var initialAssets = new DynamicAssetDB(); -initialAssets.AddAsset(new MageSpec() { - HealthRegenerationFactor = 10 -}); -initialAssets.AddAsset(new WarriorSpec() { - Armour = 100 -}); -... -Second, QuantumGame.StartParameters.InitialDynamicAssets needs to be used to pass the instance to a new simulation. In Unity, since it is the QuantumRunner behaviour that manages a QuantumGame, QuantumRunner.StartParamters.InitialDynamicAssets is used instead. - -Back To Top - - -Built In Assets -Quantum also comes shipped with several built-in assets, such as: - -SimulationConfig - defines many specifications for a Quantum simulation, from scene management setup, heap configuration, thread count and Physics/Navigation settings; -DeterministicConfig - specifies details on the game session, such as it's simulation rate, the checksum interval and lots of configuration regarding Input related to both the client and the server; -QuantumEditorSettings - has the definition for many editor-only details, like the folder the DB should be based in, the color of the Gizmos and auto build options for automatically baking maps, nav meshes, etc; -BinaryDataAsset - an asset that allows the user to reference arbitrary binary information (in the form of a byte[]). For example, by default the Physics and the Navigation engines uses binary data assets to store information like the static triangles data. This asset also has built in utilities to compress and decompress the data using gzip. -CharacterController3DConfig - config asset for the built in 3D KCC. -CharacterController2DConfig - config asset for the built in 2D KCC. -PhysicsMaterial - defines a Physics Material for Quantum's 3D physics engine. -PolygonCollider - defines a Polygon Collider for Quantum's 2D physics engine. -NavMeshAsset - defines a NavMesh used by Quantum's navigation system. -NavMeshAgentConfig - defines a NavMesh Agent Config for Quantum's navigation system. -MapAsset - stores many static per-scene information such as Physics settings, colliders, NavMesh settings, links, regions and also the Scene Entity Prototypes on that Map. Every Map is correlated with a single Unity scene. \ No newline at end of file diff --git a/data/assets unity.txt b/data/assets unity.txt deleted file mode 100644 index 3f1cd1c172f354939065f1a828dd4b95e7ee9516..0000000000000000000000000000000000000000 --- a/data/assets unity.txt +++ /dev/null @@ -1,243 +0,0 @@ -Assets in Unity -Overview -Finding Quantum Assets In Unity Scripts -Resources, Addressables And Asset Bundles -Drag-And-Dropping Assets In Unity -Map Asset Baking Pipeline -Preloading Addressable Assets -V1.16 Or Older -V1.17 Or Newer -Baking AssetBase Load Information -Updating Quantum Assets In Build -Updating Existing Assets -Adding New Assets -Example Implementation -Adding New Assets With DynamicAssetDB - -Overview -Quantum generates a ScriptableObject-based wrapper partial class for each each asset type available in Unity. The base class for such wrappers is AssetBase. The main class managing AssetBase instances is called UnityDB. - -Editing a data asset -Editing properties of a data asset from Unity. -The Quantum SDK will generate the unique GUIDs for each asset. -AssetGuids of all the available assets have to be known to the simulation when it starts. - -The process for this to happen is: - -In the Editor: -All AssetBase assets collected from the locations defined in QuantumEditorSettings.AssetSearchPaths (by default Assets/Resources/DB). -Each AssetBase has a generated entry containing the AssetGuid and the information needed to load the AssetBase at runtime. -Entries are saved into the AssetResourceContainer asset at defined in the QuantumEditorSettings.AssetResourcePath (by default Assets/Resources/AssetResources.asset). -At runtime: -The first time any of UnityDB members is used AssetResourceContainer is loaded using Resources.Load (based on QuantumEditorSettings.AssetResourcePath). -The list of entries is used to initialize the simulation's IResourceManager along with the information needed to load each asset dynamically. -To browse the list of Asset Objects currently part of the database, use the AssetDB Inspector window accessible via Quantum/Show AssetDB Inspector and Window/Quantum/AssetDB Inspector menu items. - - -Back To Top - - -Finding Quantum Assets In Unity Scripts -Every concrete asset class created by the user gets a corresponding class generated on the Unity side to enable their instantiation as actual Unity Scriptable Objects. - -Just to exemplify: a Quantum asset named CharacterData gets, in Unity, a class named CharacterDataAsset, where the word Asset is always the suffix added. The Unity class always contains a Property named AssetObject which can be cast to the Quantum class in order to access the simulation specific fields. - -Use the UnityDB class in order to find assets in the Unity side. Here is a complete snippet of a Quantum asset declaration, and how to access it's fields on Unity: - -In the Quantum side: - -// in any .qtn file: -asset CharacterData; - -// in a .cs file: -public unsafe partial class CharacterData -{ - public FP MaximumHealth; -} -In the Unity side: - -var characterDataAsset = UnityDB.FindAsset(myAssetRef.Id); -var characterData = characterDataAsset.Settings; -FP maximumHealth = characterData.MaximumHealth; -Back To Top - - -Resources, Addressables And Asset Bundles -Quantum never forms hard-references to AssetBase assets. This enables the use of any dynamic content delivery. The following methods of loading assets are supported out of the box: - -Resources -Addressables (needs to be explicitly enabled) -Asset Bundles (as a proof-of-concept, due to Asset Bundles demanding highly custom, per-project approach) -Enabling Addressables -Enabling Addressables in QuantumEditorSettings. Alternatively, define `QUANTUM_ADDRESSABLES` and `QUANTUM_ADDRESSABLES_WAIT_FOR_COMPLETION` (for Addressables 1.17 or newer). -There are not any extra steps needed for AssetBase to be loadable dynamically using any of the methods above. The details on how to load each asset are stored in AssetResourceContainer. This information is accessed when a simulation calls Frame.FindAsset or when UnityDB.FindAsset is called and leads to an appropriate method of loading being used. - -If an asset is in a Resource folder, it will be loaded using the Resources API. -If an asset has an address (explicit or implicit), it will be loaded using the Addressables API. -If an asset belongs to an Asset Bundle (explicitly or implicitly), there will be an attempt to load it using the AssetBundle API. -To make the list of the assets (AssetResourceContainer) dynamic itself some extra code is needed; pleasr refer to the Updating Quantum Assets At Runtime section for more information. - -User scripts can avoid hard references by using AssetRef types (e.g. AssetRefSimulationConfig) instead of AssetBase references (e.g. SimulationConfig) to reference Quantum assets. - -public class TestScript : MonoBehaviour { - // hard reference - public SimulationConfigAsset HardRef; - // soft reference - public AssetRefSimulationConfig SoftRef; - - void Start() { - // depending on the target asset's settings, this call may result in - // any of the supported loading methods being used - SimulationConfigAsset config = UnityDB.FindAsset(SoftRef.Id); - } -} -Back To Top - - -Drag-And-Dropping Assets In Unity -Adding asset instances and searching them through the Frame class from inside simulation Systems can only go so far. At convenient solution arises from the ability to have asset instances point to database references and being able to drag-and-drop these references inside Unity Editor. - -One common use is to extend the pre-build RuntimePlayer class to include an AssetRef to a particular CharacterSpec asset chosen by a player. The generated and type-safe asset_ref type is used for linking references between assets or other configuration objects. - -// this is added to the RuntimePlayer.User.cs file -namespace Quantum { - partial class RuntimePlayer { - public AssetRefCharacterSpec CharacterSpec; - - partial void SerializeUserData(BitStream stream) { - stream.Serialize(ref CharacterSpec); - } - } -} -This snippet will allow for an asset_ref field to generated which will only accept a link to an asset of type CharacterSpec. This field will show up in the Unity inspector and can be populated by drag-and-dropping an asset into the slot. - -Drag & Drop Asset -Asset ref properties are shown as type-safe slots for Quantum scriptable objects. -Back To Top - - -Map Asset Baking Pipeline -Another entry point for generating custom data in Quantum is the map baking pipeline. The MapAsset is the AssetBase-based wrapper for Map asset. - -The Map asset is required by a Quantum simulation and contains basic information such as Navmeshes and static colliders; additional custom data can be saved as part of the asset placed in its custom asset slot - this can be an instance of any custom data asset. The custom asset can be used to store any static data meant to be used during initialization or at runtime. A typical example would be an array of spawn point data such as position, spawned type, etc. - -In order for a Unity scene to be associated with a MapAsset, the MapData MonoBehaviour component needs to be present on a GameObject in the scene. Once MapData.Asset points to a valid MapAsset, the baking process can take place. By default, Quantum bakes navmeshes, static colldiers and scene prototypes automatically as a scene is saved or when entering play mode; this behaviour can be changed in QuantumEditorSettings. - -To assign a custom piece of code to be called every time the a bake happens, create a class inheriting from the abstract MapDataBakerCallback class. - -public abstract class MapDataBakerCallback { - public abstract void OnBake(MapData data); - public abstract void OnBeforeBake(MapData data); - public virtual void OnBakeNavMesh(MapData data) { } - public virtual void OnBeforeBakeNavMesh(MapData data) { } -} -Then override the mandatory OnBake(MapData data) and OnBakeBefore(MapData data) methods. - -public class MyCustomDataBaker: MapDataBakerCallback { - public void OnBake(MapData data) { - // any custom code to live-load data from scene to be baked into a custom asset - // generated custom asset can then be assigned to data.Asset.Settings.UserAsset - } - public void OnBeforeBake(MapData data) { - - } -} -Back To Top - - -Preloading Addressable Assets -Quantum needs assets to be loadable synchronously. - - -V1.16 Or Older -In the Addressables version prior to 1.17, there were no means to load Addressable assets synchronously other than preloading before the simulation started or using Unity's SyncAddressables sample. - -Back To Top - - -V1.17 Or Newer -WaitForCompletion was addded in Addressables 1.17 which added the ability to load assets synchronously. To enable it for Quantum, define QUANTUM_ADDRESSABLES_USE_WAIT_FOR_COMPLETION or use the toggles in the QuantumEditorSettings asset's Build Features section. - -Although synchronous loading is possible, there are situations in which preloading assets might still be preferable; the QuantumRunnerLocalDebug.cs script demonstrates how to achieve this. - -Back To Top - - -Baking AssetBase Load Information -AssetResourceContainer is a ScriptableObject containing information on how to load each AssetBase and maps them to AssetGuids. - -N.B.: `AssetBase` is not a Quantum asset itself. -Every time the menu option Quantum > Generate Asset Resources is used or an asset in one of QuantumEditorSettings.AssetSearchPaths is imported, the AssetResourceContainer is recreated in full at the location specified by the QuantumEditorSettings.AssetResourcePath. - -During the creation of the AssetResourceContainer, each AssetBase located in any QuantumEditorSettings.AssetSearchPaths is assigned to a group. By default, three groups exist: - -ResourcesGroup; -AssetBundlesGroup; and, -AddressablesGroup. -The process of deciding which group an asset is assigned to is shown in the diagram below. - -AssetResourceContainer generation -The flow of assigning an asset a group. -An asset is considered Addressable if: - -it has an address assigned; -any of its parent folders is Addressable; or, -it is nested in another Addressable asset. -The same logic applies to deciding whether an asset is a part of an Asset Bundle. - -To disable baking AssetBase when assets are imported, untick QuantumEditorSettings.UseAssetBasePostprocessor. - -Back To Top - - -Updating Quantum Assets In Build -It is possible for an external CMS to provide data assets; this is particularly useful for providing balancing updates to an already released game without making create a new build to which players would have to update. - -This approach allows balancing sheets containing information about data-driven aspects such as character specs, maps, NPC specs, etc... to be updated independently from the game build itself. In this case, game clients would always try to connect to the CMS service, check for whether there is an update and (if necessary) upgrade their game data to the most recent version before starting or joining online matches. - -Back To Top - - -Updating Existing Assets -The use of Addressables or Asset Bundles is recommended as these are supported out of the box. Any AssetBase that is an Addressable or part of an Asset Bundle will get loaded at runtime using the appropriate methods. - -To avoid unpredictable lag spikes resulting from downloading assets during the game simulation, consider downloading and preloading your assets as discussed here: Preloading Addressable Assets. - -Back To Top - - -Adding New Assets -The AssetResourceContainer generated in the editor will contain the list of all the assets present at its creation. If a project's dynamic content includes adding new Quantum assets during without creating a new build, a way to update the list needs to be implemented. - -The recommended approach to achieve this is with an extension of the partial UnityDB.LoadAssetResourceContainerUser method. When the first simulation starts or any UnityDB method is called, Quantum will make an attempt to load the AssetResourceContainer. By default it is assumed the AssetResourcesContainer is a Resource located at the QuantumEditorSettings.AssetResourcePath. To override this behaviour, UnityDB.LoadAssetResourceContainerUser needs to be implemented. - -Back To Top - - -Example Implementation -First, the AssetResourceContainer needs to be moved out of the Resources folder. This is done by setting the QuantumEditorSettings.AssetResourcePath: - -AssetResourceContainer Path Override -Second, the new AssetResourceContainer needs to be made into an Addressable: - -Addressable AssetResourceContainer -Finally, the snippet implementing the partial method: - -partial class UnityDB { - static partial void LoadAssetResourceContainerUser(ref AssetResourceContainer container) { - var path = QuantumEditorSettings.Instance.AssetResourcesPath; - -#if UNITY_EDITOR - if (!UnityEditor.EditorApplication.isPlaying) { - container = UnityEditor.AssetDatabase.LoadAssetAtPath(path); - Debug.Assert(container != null); - return; - } -#endif - - var op = Addressables.LoadAssetAsync(path); - container = op.WaitForCompletion(); - Debug.Assert(container != null); - } -} -This is a simplified implementation and, depending on project's needs, some management of the `AsyncOperationHandle` returned by `Addressables.LoadAssetAsync` may need to be added. \ No newline at end of file diff --git a/data/bt.qtn b/data/bt.qtn deleted file mode 100644 index cbc31bc65340e950ee65a887bf072b0c97b6a1dd..0000000000000000000000000000000000000000 --- a/data/bt.qtn +++ /dev/null @@ -1,27 +0,0 @@ -asset BTNode; -asset BTService; - -component BTAgent { - asset_ref Tree; - asset_ref Current; - list NodesStatus; - list ServicesEndTimes; - list BTDataValues; - list ActiveServices; - list DynamicComposites; - AssetRefAIConfig Config; - Int32 AbortNodeId; -} - -struct BTDataIndex{ - Int32 Index; -} - -union BTDataValue{ - FP FPValue; - Int32 IntValue; -} - -asset import BTComposite; -asset import BTDecorator; -asset import BTRoot; \ No newline at end of file diff --git a/data/callbacks.txt b/data/callbacks.txt deleted file mode 100644 index 5c84e7439f330fd7e84cad400ac764a4655ce7ac..0000000000000000000000000000000000000000 --- a/data/callbacks.txt +++ /dev/null @@ -1,201 +0,0 @@ -Introduction -Collision and Trigger callbacks in Quantum are handled through system signals. There are two steps required to have callbacks executed for any specific entity: - -enabling the specific type(s) of callback to the entity(ies). -implementing the corresponding signal(s). -Before learning how to do enable callbacks and how to write code, it is important to first understand the different callback types and what causes them to be executed. - -Back To Top - - -Callback Types -Collisions and triggers start as either a two-entity or an entity-static pair generated by the collision detection step in the physics engine. Depending on the combination of physics components attached to entities, and the value of the trigger property (true/false), the tables below illustrate the types of callbacks that will be executed. - - -Entity Vs Entity -According to their component composition, physics-enabled entities can be classified as either: - -Non-Trigger Collider: Entity with a non-trigger Physics Collider and, optionally, a kinematic Physics Body. -Trigger Collider: Entity with a trigger Physics Collider and, optionally, a kinematic Physics Body. -Dynamic Body: Entity with a non-trigger Physics Collider and a dynamic (non-kinematic) Physics Body. -When a collision pair is composed of two entities A and B, these are the possibly executed callbacks (depending on the group each entity belongs to): - -Entities A x B Non-Trigger Collider Trigger Collider Dynamic Body -Non-Trigger Collider None OnTrigger OnCollision -Trigger Collider OnTrigger None OnTrigger -Dynamic Body OnCollision OnTrigger OnCollision -Back To Top - - -Entity Vs Static Collider -Static colliders, on the other hand, can be either Trigger or Non-Trigger, according to their IsTrigger property. These are the possible combinations when the collision pair is composed of an entity and a static collider: - -Components (Entity and Static) Non-Trigger Static Collider Trigger Static Collider -Non-Trigger Collider None OnTrigger -Trigger Collider OnTrigger None -Dynamic Body OnCollision OnTrigger -Back To Top - - -Enabling Callbacks On Entities -It is possible to control which callback types (and against which kinds of other collider) are enabled for each individual entity. This is done via the Entity Prototype in Unity or in code via the SetCallbacks function in the physics engine API, which takes the entity and a collision callbacks flag. - - -Via Entity Prototypes -The physics callbacks can be set on any Entity Prototype with a PhysicsCollider (2D/3D). - -Setting Physics Callbacks via the Entity Prototype's Physics Properties in the Unity Editor -Setting Physics Callbacks via the Entity Prototype's Physics Properties in the Unity Editor. -Each entity can have several callbacks. - -N.B.: Enabling the callbacks on an Entity Prototype only sets the callbacks for that particular entity. You still have to implement the corresponding signals in code. See the section on Callback Signals below for more information. - -Back To Top - - -Via Code -The callbacks flag is a bitmask and can specify multiple callback types by the use of bitwise operations as exemplified next. - -The following snippet enables the full set of OnTrigger callbacks against another dynamic entity (OnDynamicTrigger, OnDynamicTrigger OnDynamicTriggerEnter and OnDynamicTriggerExit). - -CallbackFlags flags = CallbackFlags.OnDynamicTrigger; -flags |= CallbackFlags.OnDynamicTriggerEnter; -flags |= CallbackFlags.OnDynamicTriggerExit; - -// for 2D -f.Physics2D.SetCallbacks(entity, flags); - -// for 3D -f.Physics3D.SetCallbacks(entity, flags); -These are the basic callback flags (the corresponding signals are called every tick until the collision is not being detected anymore by the physics engine): - -CallbackFlags.OnDynamicCollision -CallbackFlags.OnDynamicTrigger -CallbackFlags.OnStaticTrigger -And these are the corresponding Enter/Exit callbacks (that can be enabled independently from the above): - -CallbackFlags.OnDynamicCollisionEnter -CallbackFlags.OnDynamicCollisionExit -CallbackFlags.OnDynamicTriggerEnter -CallbackFlags.OnDynamicTriggerExit -CallbackFlags.OnStaticCollisionEnter -CallbackFlags.OnStaticCollisionExit -CallbackFlags.OnStaticTriggerEnter -CallbackFlags.OnStaticTriggerExit -Having to enable callbacks on a per-entity basis is an intentional design choice to make the default simulation as fast as possible. Also notice that Enter/Exit callbacks incur in a bit more memory and more CPU usage (compared to basic callbacks), so for the leanest possible simulation you should avoid these whenever possible. - -Back To Top - - -Callback Signals -N.B.: Collision and Trigger callbacks for both Entity vs Entity and Entity vs Static pairs are grouped into a unified signals API. - -These are the 2D Physics signals: - -namespace Quantum { - public interface ISignalOnCollision2D : ISignal { - void OnCollision2D(Frame f, CollisionInfo2D info); - } - public interface ISignalOnCollisionEnter2D : ISignal { - void OnCollisionEnter2D(Frame f, CollisionInfo2D info); - } - public interface ISignalOnCollisionExit2D : ISignal { - void OnCollisionExit2D(Frame f, ExitInfo2D info); - } - public interface ISignalOnTrigger2D : ISignal { - void OnTrigger2D(Frame f, TriggerInfo2D info); - } - public interface ISignalOnTriggerEnter2D : ISignal { - void OnTriggerEnter2D(Frame f, TriggerInfo2D info); - } - public interface ISignalOnTriggerExit2D : ISignal { - void OnTriggerExit2D(Frame f, ExitInfo2D info); - } -} -And the 3D Physics signals: - -namespace Quantum { - public interface ISignalOnCollision3D : ISignal { - void OnCollision3D(Frame f, CollisionInfo3D info); - } - public interface ISignalOnCollisionEnter3D : ISignal { - void OnCollisionEnter3D(Frame f, CollisionInfo3D info); - } - public interface ISignalOnCollisionExit3D : ISignal { - void OnCollisionExit3D(Frame f, ExitInfo3D info); - } - public interface ISignalOnTrigger3D : ISignal { - void OnTrigger3D(Frame f, TriggerInfo3D info); - } - public interface ISignalOnTriggerEnter3D : ISignal { - void OnTriggerEnter3D(Frame f, TriggerInfo3D info); - } - public interface ISignalOnTriggerExit3D : ISignal { - void OnTriggerExit3D(Frame f, ExitInfo3D info); - } -} -To receive the callback, you need to implement the corresponding signal interface in at least one active system (disabled systems do not get any signal executed in them): - -public class PickUpSystem : SystemSignalsOnly, ISignalOnTriggerEnter3D -{ - public void OnTriggerEnter3D(Frame f, TriggerInfo3D info) - { - if (!f.Has(info.Entity)) return; - if (!f.Has(info.Other)) return; - - var item = f.Get(info.Entity).Item; - var itemAsset = f.FindAsset(item.Id); - itemAsset.OnPickUp(f, info.Other, itemAsset); - - f.Destroy(info.Entity); - } -} -The code above exemplifies yet another optimization one can use when implementing signals. Inheriting from SystemSignalsOnly lets the system handle signals while not needing to schedule an empty update function (that would unecessarily incur into task system overhead). - -Back To Top - - -CollisionInfo -The signals for OnCollisionEnter and OnCollision offer up additional information on the colliding entities via the CollisionInfo struct. - - -Contact Points -The information on the contact points can be accessed through the ContactPoints API. - -Average : the average of all contact points -Count : the number of contact points -Length : a buffer with all contact points -First : returns the first contact point of the first Triangle -First can help saving computations if only one/any contact point is needed, and it does not have to be an averaged one. - -ContactPoints is also an iterator. When colliding with a mesh, it can be used to iterate through all triangle collisions contact points. - -while(info.ContactPoints.Next(out var cp)) { - Draw.Sphere(cp, radius); -} -Sphere-Triangle collisions have a single contact point; therefore, both Average and ContactPoints[0] will return the same contact. Other types of collision can have more contact points. - -Back To Top - - -Mesh Collision -When an entity is colliding with a mesh, Count and Average take all triangle collisions belonging to that specific mesh into account. Instead of receiving a callback for each triangle an entity collides with, these colliding triangles are grouped into a single CollisionInfo struct. - -In the case of a mesh collision, info.ContactNormal and info.Penetration will return the average value of that mesh's triangle collisions; the same data available through info.MeshTriangleCollisions.AverageNormal and info.MeshTriangleCollisions.AveragePenetration. - -In addition to the average normal an penetration, you can iterate through each triangle collision and access specific info such as the triangle data itself. MeshTriangleCollisions is also an iterator; it can be used to iterate through each triangle's collision data and retrieve mesh-specific collision data. - -if (info.IsMeshCollision) { - while(info.MeshTriangleCollisions.Next(out var triCollision)) { - Draw.Ray(triCollision.Triangle->Center, triCollision.ContactNormal * triCollision.Penetration); - } -} -Back To Top - - -Misc And FAQ -Useful information when working with collision callbacks: - -If 2 entities have the same callback set, the callback will be called twice - once per entity it is associated with. The only difference between the two calls are the values for Entity and Other which will be swapped. -Collisions with triggers and static colliders do not compute the Normal/Point/Penetration data. -Static Colliders on Unity can have a Quantum DB asset attached to their "Asset" field. Casting it to your expected custom asset types is a good way to add arbitrary data to your static collision callbacks. \ No newline at end of file diff --git a/data/cheaters.txt b/data/cheaters.txt deleted file mode 100644 index 1487bf1dab49236308e3a0610cf1b80980721bf6..0000000000000000000000000000000000000000 --- a/data/cheaters.txt +++ /dev/null @@ -1,124 +0,0 @@ -Cheat Protection -Introduction -Cheat Protection By Determinism -Trustable Match Results -How To Access The Replay Data -Self-Hosted Spectators Referee -Custom Quantum Server -Protect Client-Controlled Game Configs -Custom Authentication -Determinism As A Drawback -Perfect Information Problem -Detecting Cheaters Using Checksums - -Introduction -Security and cheat-protection are important aspects of online multiplayer games. Quantum's determinism offers unique features to address them. This page dives into the details on the built-in protection and offers best-practices to create production-ready online games with Photon Quantum. - -It is crucial for developers to be aware of all security related issues and the steps to mitigated those as well as at which time these should be taken. Although it is possible to run the full simulation on the server, it extremely rare this is sensible from a practical and cost perspective. - -Running servers is costly especially when the game does not generate revenue, yet. -In a most cases cheaters only make up a very small portion of the user base. -Making a game 100% cheat proof is an utopian idea, even keeping just one step ahead of them is a huge task. -There are game genres which rely on as much security as possible. -The single most important advice is: write the game now and add these more complex safety checks incrementally. It is perfectly viable to go live without a custom server and be successful. -In the documentation the following terms are used: - -a game backend refers to online services created and hosted by the customer; -a custom server (/plugin) refers to a customer-customized Quantum plugin hosted by Photon; and, -Quantum Public Cloud refers to non-customized Quantum servers hosted by Photon. -Back To Top - - -Cheat Protection By Determinism -The huge upside of a deterministic game, even without the server running the simulation, is its cheat-robustness; if one player modifies their client, for example by changing their character's speed, it will not affect any other player. They might notice the cheating player behaving strangely (e.g. continuously bumping into a wall) but otherwise their experience remains untainted. - -The reason for this is simple: each client deterministically runs the complete simulation locally and only shares input with other clients. - -Back To Top - - -Trustable Match Results -A match result is used to update the player progression in a game backend. In the most secure scenarios this is done from the server where the game logic ran. - -However, there are a few iterations possible to secure the results in a cost-efficient way before going towards an authoritative game server. - -Prototyping Clients push their individual results to the developers game backend. This is good for prototyping and games can also launch with this setup. Having a data structure which can be filled with the results to be sent to a backend is the first thing to have. -Majority Vote Clients push the match result individually but the backend choses the result based on a majority vote. Outliers that send (potentially) tampered data can be identified. There is no need to wait for a confirmation on the clients, just display them their result right away and save the progression only _after_ having had it validated on the backend. -Resimulation Replays If there is no agreement via the majority vote or a statistical evaluation smell, flag the match for a revalidation and collect the input history of the players. With the input history, the config files and the asset database the Quantum simulation can run outside Unity to recheck the result (replay). See the quantum-console-runner project in the Quantum SDK and read the following section on how to access the replay data. -Self-Hosted Spectators Referee Use a referee spectator, a .Net application, which can connect to live matches and run the simulation simultaneously, then submit the final results from it rather than letting the players send it. -Custom Quantum Server Run the simulation on the server and submit its result (see the quantum-custom-plugin SDK). -Back To Top - - -How To Access The Replay Data -Quantum 2.1 and earlier - -Request to be send by a client -Send from the Self-Hosted Spectators Referee -Send from the Custom Quantum Server -Quantum 3.0. (planned) - -Configurable webhook for Quantum Public Cloud users to stream the verified input history to another backend -Back To Top - - -Self-Hosted Spectators Referee -The Quantum simulation can be joined as a spectator; a spectator is a client which can connect to the server and run the full game without the ability of sending input. This can be used to start and initialize the game from a trusted source. - -Create a spectator application (runs outside Unity). -Run it anywhere to communicate securely with the game backend to start and prepare a Photon room. -Start a Quantum game. -Let other clients late-join the simulation (see the quantum-console-spectator project in the Quantum SDK). -Adding another, artificial client to the Photon Room for spectating purposes will increase the CCU count, which is the basis for the server cost. This might be a problem for games with low player counts (1vs1) as increases the CCU count perceptibly. -Back To Top - - -Custom Quantum Server -Running custom code on the Quantum server requires renting Photon Enterprise servers. It will enable authority over the following aspects: - -Option to run the game simulation on the server to: -Have a trusting source for game results -Enable server snapshots (are send by clients when using the Public Quantum Cloud) -Option to inject server controlled secrets into the game (server command) -Option to intercept and replace DeterministicConfig, RuntimeConfig and RuntimePlayer -Option to save validated replays -Back To Top - - -Protect Client-Controlled Game Configs -Two important shared configuration files are sent by the clients in the beginning of a match: DeterministicConfig (Quantum settings) and RuntimeConfig (game settings). The server will accept the first ones (DeterministicConfig, RuntimeConfig) it receives which is more or less random in the most cases. - -Clients also sending another config which is describing the player loadout: RuntimePlayer. To verify it against the player progression saved on a backend a custom Quantum server plugin is required. - -Public Quantum Cloud Chooses a random source for DeterministicConfig and RuntimeConfig -Custom Quantum Server All configs can be intercepted and replaced after retrieving it from the developers backend -Dashboard (coming in Quantum 3.0) DeterministicConfig can be forced on Public Cloud server by associating it with an AppId inside the Photon dashboard. -Webhooks (coming in Quantum 3.0) RuntimeConfig and RuntimePlayer can be verified by the developers backend calling a webrequest configured on the Photon dashboard. -Back To Top - - -Custom Authentication -We do not offer an authentication service nor a player database ourselves but we absolutely recommend to add a proprietary or third-party authentication service. - -photon realtime custom authentication - -Back To Top - - -Determinism As A Drawback -While Determinism has a lot of advantages, it comes with a few notable drawbacks inherent to this type of technology. - - -Perfect Information Problem -With Quantum every client has access to all information required to simulation the game locally (apart from other players' input). This means client controlled secrets used in a card game and Fog Of War-like features are easily hackable. - -There are also fringe cases which let clients "guess" a next random number or the ability to create bots. - -Back To Top - - -Detecting Cheaters Using Checksums -It is not recommended to use Quantum checksum detection for live games as a way to detect cheaters. - -Checksum calculation is expensive and could lead to hiccups; and, -The build-in mechanism stops the simulation for every client in the game session immediately. diff --git a/data/collider.txt b/data/collider.txt deleted file mode 100644 index 98672509323b0c682ca4a9f64a8b6a6ad789076c..0000000000000000000000000000000000000000 --- a/data/collider.txt +++ /dev/null @@ -1,205 +0,0 @@ -Introduction -The collision and physics behaviour each have their own component in Quantum 2. - -Adding a PhysicsCollider2D/PhysicsCollider3D to an entity, turns entity into a dynamic obstacle or trigger which can be moved via its transform. -Adding a PhysicsBody2D/PhysicsBody3D allows the entity to be controlled by the physics solver. -Back To Top - - -Requirements -The Transform2D/Transform3D, PhysicsCollider2D/PhysicsCollider3D and PhysicsBody2D/PhysicsBody3D components are tightly intertwined. As such some of them are requirements for others to function. The complete dependency list can be found below: - -Requirement Transform PhysicsCollider PhysicsBody -Component -Transform ✓ ✗ ✗ -PhysicsCollider ✓ ✓ ✗ -PhysicsBody ✓ ✓ ✓ -These dependencies build on one another, thus you have to add the components to an entity in the following order if you wish to enable a PhysicsBody: - -Transform -PhysicsCollider -PhysicsBody -Back To Top - - -The PhysicsBody Component -Adding the PhysicsBody ECS component to an entity enables this entity to be taken into account by the physics engine. N.B.: the use of a PhysicsBody requires the entity to already have a Transform and a PhysicsCollider . - -You can create and initialize the components either manually in code, or via the EntityPrototype component in Unity. - -var entity = f.Create(); -var transform = new Transform2D(); -var collider = PhysicsCollider2D.Create(f, Shape2D.CreateCircle(1)); -var body = PhysicsBody2D.CreateDynamic(1); - -f.Set(entity, transform); -f.Set(entity, collider); -f.Set(entity, body); -The same rule applies to the 3D Physics: - -var entity = f.Create(); -var transform = Transform3D.Create(); - -var shape = Shape3D.CreateSphere(FP._1); - -var collider = PhysicsCollider3D.Create(shape); -var body = PhysicsBody3D.CreateDynamic(FP._1); - -f.Set(entity, transform); -f.Set(entity, collider); -f.Set(entity, body); -In case of the EntityPrototype method, the components will be initialized with their saved values. - -Adjusting an Entity Prototype's Physics Properties via the Unity Editor -Adjusting an Entity Prototype's Physics Properties via the Unity Editor. -The PhysicsCollider3D supports only supports the following Shape3D for dynamic entities: - -Sphere -Box -Back To Top - - -Center Of Mass -The Center of Mass, simply referred to as CoM from here on out, can be set on the PhysicsBody component. The CoM represents an offset relative to the position specified in the Transform component. Changing the position of the CoM allows to affect how forces are applied to the PhysicsBody. - -Animated examples of how various CoM affect the same PhysicsBody -Animated examples showcasing how various CoM affect the same PhysicsBody. -By default, the CoM is set to the centroid of the PhysicsCollider's shape. This is enforced by the Reset Center of Mass On Added in the PhysicsBody Config drawer. - -N.B.: To customize the CoM position, you HAVE TO uncheck the Reset Center of Mass On Added flag; otherwise the CoM will be reset to the Collider's centroid when the PhysicsBody component gets added to the entity. - -Defaults Flags in the PhysicsBody Config -Defaults Flags in the PhysicsBody Config viewed in the Unity Editor. -The above configuration is the commonly used for an entity behaving like a uniformly dense body, a.k.a. body with a uniform density. However, the CoM and collider offset are configured separately. The combinations are explained in the table below. - -PhysicsCollider Offset PhysicsBody CoM Reset Center of Mass On Added flag Resulting positions -Default Position = 0, 0, 0 -Custom Value = any position differing from the default position -Default Position Default Position On / Off Collider Centroid and the CoM positions are both equal to the transform position. -Custom Value Default Position On Collider Centroid is offset from the transform, and the CoM is equal to the Collider Centroid position. -Custom Value Default Position Off Collider Centroid is offset from the transform position. -The CoM is equal to the transform position. -Custom Value Custom Position On Collider Centroid is offset from the transform position. -The CoM is equal to the Collider Centroid position. -Custom Value Custom Position Off Collider Centroid is offset from the transform position. -The CoM is offset from the transform position. -Back To Top - - -Compound Collider CoM -A compound shape's CoM is a combination of all the shape's elements' centroids based on the weighted average of their areas (2D) or volumes (3D). - -Back To Top - - -Key Points -In summary, these are the main points you need to takeaway regarding the CoM configuration. - -The PhysicsCollider offset and PhysicsBody CoM positions are distinct from one another. -By default the PhysicsBody Config has the flags Reset Center of Mass On Added and Reset Inertia on Added. -To set a custom CoM, uncheck the Reset Center of Mass On Added flag in the PhysicsBody Config. -If the Reset Center of Mass On Added flag is checked on the PhysicsBody Config, the CoM will be automatically set to the PhysicsCollider centroid upon being added to the entity - regardless of the CoM position specified in the Editor. -Back To Top - - -Applying External Forces -The PhysicsBody API allows for the manual application of external forces to a body. - -// This is the 3D API, the 2D one is identical. - -public void AddTorque(FPVector3 amount) -public void AddAngularImpulse(FPVector3 amount) - -public void AddForce(FPVector3 amount, FPVector3? relativePoint = null) -public void AddLinearImpulse(FPVector3 amount, FPVector3? relativePoint = null) -// relativePoint is a vector from the body's center of mass to the point where the force is being applied, both in world space. -// If a relativePoint is provided, the resulting Torque is computed and applied. - -public void AddForceAtPosition(FPVector3 force, FPVector3 position, Transform3D* transform) -public void AddImpulseAtPosition(FPVector3 force, FPVector3 position, Transform3D* transform) -// Applies the force/impulse at the position specified while taking into account the CoM. -As you can gather from the API, angular and linear momentum of the PhysicsBody can be affected by applying: - -forces; or -impulses. -Although they are similar, there is a key different; forces are applying over a period of time, while impulses are immediate. You can think of them as: - -Force = Force per deltatime -Impulse = Force per frame -Note: In Quantum deltatime is fixed and depended on the simulation rate set in Simulation Config asset. - -An impulse will produce the same effect, regardless of the simulation rate. However, a force depends on the simulation rate - this means applying a force vector of 1 to a body at a simulation rate of 30, if you increase the simulation rate to 60 the deltatime with be half and thus the integrated force will be halved as well. - -Generally speaking, it is advisable to use an impulse when a punctual and immediate change is meant to take place; while a force should be used for something that is either constantly, gradually, or applied over a longer period of time. - -Back To Top - - -Initializing The Components -To initialize a PhysicsBody as either a Dynamic or Kinematic body, you can use their respective Create functions. These methods are accessible via the PhysicsBody2D and PhysicsBody3D classes, e.g.: - -PhysicsBody3D.CreateDynamic -PhysicsBody3D.CreateKinematic -Back To Top - - -ShapeConfigs -To initialize PhysicsCollider and PhysicsBody via data-driven design, you can use the ShapeConfig types (Shape2DConfig, and Shape3DConfig). These structs can be added as a property to any Quantum data-asset, editable from Unity (for shape, size, etc). - -// data asset containing a shape config property -partial class CharacterSpec { - // this will be edited from Unity - public Shape2DConfig Shape2D; - public Shape3DConfig Shape3D; - public FP Mass; -} -When initializing the body, we use the shape config instead of the shape directly: - -// instantiating a player entity from the Frame object -var playerPrototype = f.FindAsset(PLAYER_PROTOTYPE_PATH); -var playerEntity = playerPrototype.Container.CreateEntity(f); - -var playerSpec = f.FindAsset("PlayerSpec"); - -var transform = Transform2D.Create(); -var collider = PhysicsCollider2D.Create(playerSpec.Shape2D.CreateShape(f)); -var body = PhysicsBody2D.CreateKinematic(playerSpec.Mass); - -// or the 3D equivalent: -var transform = Transform3D.Create(); -var collider = PhysicsCollider3D.Create(playerSpec.Shape3D.CreateShape()) -var body = PhysicsBody3D.CreateKinematic(playerSpec.Mass); - -// Set the component data -f.Set(playerEntity, transform); -f.Set(playerEntity, collider); -f.Set(playerEntity, body); -Back To Top - - -Enabling Physics Callbacks -An entity can have a set of physics callbacks associated with it. These can be enabled either via code or in the Entity Prototype's PhysicsCollider component. - -Setting Physics Callbacks via the Entity Prototype's Physics Properties in the Unity Editor -Setting Physics Callbacks via the Entity Prototype's Physics Properties in the Unity Editor. -For information on how to set the physics callbacks in code and implement their respective signals, please refer to the Callbacks entry in the Physics manual. - -Back To Top - - -Kinematic -In Quantum v2 there are 4 different ways for a physics entity to have kinematic-like behaviour: - -By having only a PhysicsCollider component. In this case the entity does not have a PhysicsBody component; i.e. no mass, drag, force/torque integrations, etc... . You can manipulate the entity transform at will, however, when colliding with dynamic bodies, the collision impulses are solved as if the entity was stationary (zeroed linear and angular velocities). -By disabling the PhysicsBody component. If you set the IsEnabled property on a PhysicsBody to false, the physics engine will treat the entity in same fashion as presented in Point 1 - i.e as having only a collider component. No forces or velocities are integrated. This is suitable if you want the body to behave like a stationary entity temporarily and its config (mass, drag coefficients, etc) for when you re-enable it at a later point. -By setting the IsKinematic property on a PhysicsBody component to true. In this case the physics engine will not move affect the PhysicsBody itself, but the body's linear and angular velocities will still have affect other bodies when resolving collisions. Use this if you want to control the entity movement instead of letting the physics engine do it, and know that you have the responsibility to move an entity and control a body's velocity manually, while still having other dynamic bodies react to it. -By initializing the PhysicsBody with CreateKinematic. If the body is expected to behave as kinematic during its entire lifetime, you can simply create it as a kinematic body. This will have the PhysicsBody behave like in 3 from the very beginning. If the body needs to eventually become dynamic one, you simply create a new one with the CreateDynamic method and set IsKinematic = true. Setting IsKinematic to true/false and re-initializing the PhysiscBody component as dynamic/kinematic can be done seamlessly at any time. -Back To Top - - -The PhysicsCollider Component - -Disabling / Enabling The Component -Since Quantum 2.1, the PhysicsCollider component is equipped with an Enabled property. When setting this property to false, the entity with the PhysicsCollider will be ignored in the PhysicsSystem. - -As the PhysicsBody requires an active PhysicsCollider, it will be effectively disabled as well. \ No newline at end of file diff --git a/data/command.txt b/data/command.txt deleted file mode 100644 index e3f028bd461d38015f508844c36e9bf0da1483f0..0000000000000000000000000000000000000000 --- a/data/command.txt +++ /dev/null @@ -1,308 +0,0 @@ -Commands -Introduction -Command System Setup In The Simulation -DeterministicCommandSetup -CommandSetup (Only For 2.0!) -Sending Commands From The View -Sending CompoundCommands From The View -Overloads -Polling Commands From The Simulation -Note -Examples For Collections -List -Array -Compound Command Example - -Introduction -Quantum Commands are an input data paths to Quantum standard APIs. They are similar to Quantum Inputs but are not required to be sent every tick. - -Quantum Commands are fully reliable. The server will always accept them and confirm it, regardless of the time at which they are sent. This comes with a trade-off; namely local clients, including the one who sent the command, cannot predict the tick in which the command is received by the simulation. While visual prediction can be shown if needed, the simulation will only receive the Command after the server has confirmed it as part of a tick. - -Commands are implemented as regular C# classes that inherit from Photon.Deterministic.DeterministicCommand. They can contain any serializable data. - -using Photon.Deterministic; -namespace Quantum -{ - public class CommandSpawnEnemy : DeterministicCommand - { - public long enemyPrototypeGUID; - - public override void Serialize(BitStream stream) - { - stream.Serialize(ref enemyPrototypeGUID); - } - - public void Execute(Frame f) - { - var enemyPrototype = f.FindAsset(enemyPrototypeGUID); - enemyPrototype.Container.CreateEntity(f); - } - } -} -Back To Top - - -Command System Setup In The Simulation -In order for commands to be sent by Quantum, a system needs to be created for them and be made aware of the available commands. - -In Quantum 2.1 and onwards, Commands need to be registered in CommandSetup.User.cs. -In Quantum 2.0, Commands need to be registered in CommandSetup.cs. -Back To Top - - -DeterministicCommandSetup -Commands need to be added to the command factories found in CommandSetup.User.cs to be available at runtime. - -N.B.: CommandSetup.Legacy.cs is not directly used in this setup; however, it needs to be kept in 2.1 for compatibility reasons. - -// CommandSetup.User.cs - -namespace Quantum { - public static partial class DeterministicCommandSetup { - static partial void AddCommandFactoriesUser(ICollection factories, RuntimeConfig gameConfig, SimulationConfig simulationConfig) { - // user commands go here - // new instances will be created when a FooCommand is received (de-serialized) - factories.Add(new FooCommand()); - - // BazCommand instances will be acquired from/disposed back to a pool automatically - factories.Add(new DeterministicCommandPool()); - } - } -} - ----------- - -// CommandSetup.Legacy.cs - -using Photon.Deterministic; -namespace Quantum { - public static class CommandSetup { - public static DeterministicCommand[] CreateCommands(RuntimeConfig gameConfig, SimulationConfig simulationConfig) { - return new null; - } - } -} -Back To Top - - -CommandSetup (Only For 2.0!) -In Quantum 2.0 Commands need to be registered in CommandSetup.cs to be available at runtime. - -N.B.: This system is obsolete in newer versions of Quantum; refer to DeterministicCommandSetup for more information. - -using Photon.Deterministic; -namespace Quantum { - public static class CommandSetup { - public static DeterministicCommand[] CreateCommands(RuntimeConfig gameConfig, SimulationConfig simulationConfig) { - return new DeterministicCommand[] { - - // user commands go here - new CommandSpawnEnemy(), - }; - } - } -} -Back To Top - - -Sending Commands From The View -Commands can be send from anywhere inside Unity. - -using Quantum; -using UnityEngine; - -public class UISpawnEnemy : MonoBehaviour -{ - [SerializeField] private EntityPrototypeAsset enemyPrototype = null; - private PlayerRef _playerRef; - - public void Initialize(PlayerRef playerRef) { - _playerRef = playerRef; - } - - public void SpawnEnemy() { - CommandSpawnEnemy command = new CommandSpawnEnemy() - { - enemyPrototypeGUID = enemyPrototype.Settings.Guid.Value, - }; - QuantumRunner.Default.Game.SendCommand(command); - } -} -Back To Top - - -Sending CompoundCommands From The View -N.B.: This is only available from Quantum 2.1 onwards. - -To send multiple commands at once, simply create a CompoundCommand and add each individual DeterministicCommand to it before sending it. - -var compound = new Quantum.Core.CompoundCommand(); -compound.Commands.Add(new FooCommand()); -compound.Commands.Add(new BazCommand()); - -QuantumRunner.Default.Game.SendCommand(compound); -Back To Top - - -Overloads -SendCommand() has two overloads. - -void SendCommand(DeterministicCommand command); -void SendCommand(Int32 player, DeterministicCommand command); -Specify the player index (PlayerRef) if multiple players are controlled from the same machine. Games with only one local player can ignore the player index field. - -Back To Top - - -Polling Commands From The Simulation -To receive and handle Commands inside the simulation poll the frame for a specific player: - -using Photon.Deterministic; -namespace Quantum -{ - public class PlayerCommandsSystem : SystemMainThread - { - public override void Update(Frame f) - { - for (int i = 0; i < f.PlayerCount; i++) - { - var command = f.GetPlayerCommand(i) as CommandSpawnEnemy; - command?.Execute(f); - } - } - } -} -Like any other sytem, the system handling the command polling and consumption needs to be included in SystemSetup.cs - -namespace Quantum { - public static class SystemSetup { - public static SystemBase[] CreateSystems(RuntimeConfig gameConfig, SimulationConfig simulationConfig) { - return new SystemBase[] { - // pre-defined core systems - [...] - - // user systems go here - new PlayerCommandsSystem(), - - }; - } - } -} -Back To Top - - -Note -The API does neither enforce, nor implement, a specific callback mechanism or design pattern for Commands. It is up to the developer to chose how to consume, interpret and execute Commands; for example by encoding them into signals, using a Chain of Responsibility, or implementing the command execution as a method in them. - -Back To Top - - -Examples For Collections - -List -using System.Collections.Generic; -using Photon.Deterministic; - -namespace Quantum -{ - public class ExampleCommand : DeterministicCommand - { - public List Entities = new List(); - - public override void Serialize(BitStream stream) - { - var count = Entities.Count; - stream.Serialize(ref count); - if (stream.Writing) - { - foreach (var e in Entities) - { - var copy = e; - stream.Serialize(ref copy.Index); - stream.Serialize(ref copy.Version); - } - } - else - { - for (int i = 0; i < count; i++) - { - EntityRef readEntity = default; - stream.Serialize(ref readEntity.Index); - stream.Serialize(ref readEntity.Version); - Entities.Add(readEntity); - } - } - } - } -} -Back To Top - - -Array -using Photon.Deterministic; - -namespace Quantum -{ - public class ExampleCommand : DeterministicCommand - { - public EntityRef[] Entities; - - public override void Serialize(BitStream stream) - { - stream.SerializeArrayLength(ref Entities); - for (int i = 0; i < Cars.Length; i++) - { - EntityRef e = Entities[i]; - stream.Serialize(ref e.Index); - stream.Serialize(ref e.Version); - Entities[i] = e; - } - } - } -} -Back To Top - - -Compound Command Example -Only one command can be attached to an input stream per tick. Even though a client can send multiple Deterministic Commands per tick, the commands will not reach the simulation at the same tick, rather they will arrive separately on consecutive ticks. To go around this limitation, you can pack multiple Deterministic Commands into a single CompoundCommand. - -Quantum 2.1 already offers this class. And for previous versions it can be added like this: - -public class CompoundCommand : DeterministicCommand { - public static DeterministicCommandSerializer CommandSerializer; - public List Commands = new List(); - - public override void Serialize(BitStream stream) { - if (CommandSerializer == null) { - CommandSerializer = new DeterministicCommandSerializer(); - CommandSerializer.RegisterPrototypes(CommandSetup.CreateCommands(null, null)); - } - - var count = Commands.Count; - stream.Serialize(ref count); - - if (stream.Reading) { - Commands.Clear(); - } - - for (var i = 0; i < count; i++) { - if (stream.Reading) { - CommandSerializer.ReadNext(stream, out var cmd); - Commands.Add(cmd); - } else { - CommandSerializer.PackNext(stream, Commands[i]); - } - } - } -} -To then dispatch the compounded commands: - -public override void Update(Frame f) { - for (var i = 0; i < f.PlayerCount; i++) { - var compoundCommand = f.GetPlayerCommand(i) as CompoundCommand; - if (compoundCommand != null) { - foreach (var cmd in compoundCommand.Commands) { - } - } - } -} diff --git a/data/components.txt b/data/components.txt deleted file mode 100644 index f11e177176c840dbe85cffa70c4ed0fc719cdd0d..0000000000000000000000000000000000000000 --- a/data/components.txt +++ /dev/null @@ -1,326 +0,0 @@ -Introduction -Components are special structs that can be attached to entities, and used for filtering them (iterating only a subset of the active entities based on its attached components). - -Aside from custom components, Quantum comes with several pre-built ones: - -Transform2D/Transform3D: position and rotation using Fixed Point (FP) values; -PhysicsCollider, PhysicsBody, PhysicsCallbacks, PhysicsJoints (2D/3D): used by Quantum's stateless physics engines; -PathFinderAgent, SteeringAgent, AvoidanceAgent, AvoidanceOBstacle: navmesh-based path finding and movement. -Back To Top - - -Component -This is a basic example definition of a component in the DSL: - -component Action -{ - FP Cooldown; - FP Power; -} -Labeling them as components (like above), instead of structs, will generate the appropriate code structure (marker interface, id property, etc). Once compiled, these will also be available in the Unity Editor for use with the Entity Prototype. In the editor, custom components are named Entity Component ComponentName. - -The API to work on components is presented via the Frame class. You have the option of working on copies on the components, or on them components via pointers. To distinguish between the access type, the API for working on copies is accessible directly viaFrame and the API for accessing pointers is available under Frame.Unsafe - as the latter modifies the memory. - -The most basic functions you will require to add, get and set components are the functions of the same name. - -Add is used to add a component to an entity. Each entity can only carry one copy of a certain component. To aid you in debugging, Add returns an AddResult enum. - -public enum AddResult { - EntityDoesNotExist = 0, // The EntityRef passed in is invalid. - ComponentAlreadyExists = 1, // The Entity in question already has this component attached to it. - ComponentAdded = 2 // The component was successfully added to the entity. -} -Once an entity has a component, you can retrieve it with Get. This will return a copy of the component value. Since you are working on a copy, you will need to save the modified values on the component using Set. Similarly to the Add method, it returns a SetResult which can be used to verify the operation's result or react to it. - -public enum SetResult { - EntityDoesNotExist = 0, // The EntityRef passed in is invalid. - ComponentUpdated = 1, // The component values were successfully updated. - ComponentAdded = 2 // The Entity did not have a component of this type yet, so it was added with the new values. -} -For example if you were to set the starting value of a health component, you would do the following: - -private void SetHealth(Frame f, EntityRef entity, FP value){ - var health = f.Get(entity); - health.Value = value; - f.Set(entity, health); -} -This table recaps the methods already presented and the others offered to you to manipulate components and their values are: - -Method Return Additional Info -Add(EntityRef entityRef) AddResult enum, see above. allows an invalid entity ref. -Get(EntityRef entityRef) T -a copy of T with the current values. does NOT allow an invalid entity ref. -Throws an exception if the component T is not present on the entity. -Set(EntityRef entityRef) SetResult enum, see above. allows an invalid entity ref. -Has(EntityRef entityRef) bool -true = entity exists and the component is attached -false = entity does not exist, or component is not attached. allows invalid entity ref, -and component to not exist. -TryGet(EntityRef entityRef, out T value) bool -true = entity exists and component is attached to it. -false = entity does not exist, or component not attached to it. allows an invalid entity ref. -TryGetComponentSet(EntityRef entityRef, -out ComponentSet componentSet) bool -true = entity exists and all components of the components are attached -false = entity does not exist, or one or more components of the set are -not attached. allows an invalid entity ref. -Remove(EntityRef entityRef) No return value. -Will remove component if the entity exists and carries the component. -Otherwise does nothing. allows an invalid entity ref. -To facilitate working on components directly and avoid the -small- overhead from using Get/Set, Frame.Unsafe offers unsafe versions of Get and TryGet (see table below). - -Method Return Additional Info -GetPointer(EntityRef entityRef) T* does NOT allow invalid entity ref. -Throws an exception if the component T is not present on the entity. -TryGetPointer(EntityRef entityRef -out T* value) bool -true = entity exists and component is attached to it. -false = entity does not exist, or component not attached to it. allows an invalid entity ref. -Back To Top - - -Singleton Component -A Singleton Component is a special type of component of which only one can exist at any given time. There can ever only be one instance of a specific T singleton component, on any entity in the entire game state - this is enforced deep in the core of the ECS data buffers. This is strictly enforced by Quantum. - -A custom Singleton Component can be defined in the DSL using singleton component. - -singleton component MySingleton{ - FP Foo; -} -Singletons inherit an interface called IComponentSingleton which itself inherits from IComponent. It can therefore do all the common things you would expect from regular components: - -It can be attached to any entity. -It can be managed with all the regular safe & unsafe methods (e.g. Get, Set, TryGetPointer, etc...). -It can be put on entity prototypes via the Unity Editor, or instantiated in code on an entity. -In addition to the regular component related methods, there are several special methods dedicated to singletons. Just like for regular components, the methods are separated in Safe and Unsafe based on whether they return a value type or a pointer. - -Method Return Additional Info -API - Frame -SetSingleton (T component, -EntityRef optionalAddTarget = default) void Sets a singleton IF the singleton does not exist. -------- -EntityRef (optional), specifies which entity to add it to. -IF none is given, a new entity will be created to add the singleton to. -GetSingleton() T Throws exception if singleton does not exist. -No entity ref is needed, it will find that automatically. -TryGetSingleton(out T component) bool -true = singleton exists -false = singleton does NOT exist Does NOT throw an exception if singleton does not exist. -No entity ref is needed, it will find that automatically. -GetOrAddSingleton(EntityRef optionalAddTarget = default) T Gets a singleton and returns it. -IF the singleton does not exist, it will be created like in SetSingleton. ------ -EntityRef (optional), specifies which entity to add it to if it has to be created. -A new entity will be created to add the singleton to if no EntityRef is passed in. -GetSingletonEntityRef() EntityRef Returns the entity which currently holds the singleton. -Throws if the singleton does not exist. -TryGetSingletonEntityRef(out EntityRef entityRef) bool -true = singleton exists. -false = singleton does NOT exist. Get the entity which currently holds the singleton.Does NOT throw if the single does not exist. -API - Frame.Unsafe -Unsafe.GetPointerSingleton() T* Gets a singleton pointer. -Throws exception if it does not exist. -TryGetPointerSingleton(out T* component) bool -true = singleton exists. -false = singleton does NOT exist. Gets a singleton pointer. -GetOrAddSingletonPointer(EntityRef optionalAddTarget = default) T* Gets or Adds a singleton and returns it. -IF the singleton does not exist, it will be created. ------ -EntityRef (optional), specifies which entity to add it to if it has to be created. -A new entity will be created to add the singleton to if no EntityRef is passed in. -Back To Top - - -Adding Functionality -Since components are special structs, you can extend them with custom methods by writing a partial struct definition in a C# file. For example, if we could extend our Action component from before as follows: - -namespace Quantum -{ - public partial struct Action - { - public void UpdateCooldown(FP deltaTime){ - Cooldown -= deltaTime; - } - } -} -Back To Top - - -Reactive Callbacks -There are two component specific reactive callbacks: - -ISignalOnAdd: called when a component type T is added to an entity. -ISignalOnRemove: called when a component type T is removed from an entity. -These are particularly useful in case you need to manipulate part of the component when it is added/removed - for instance allocate and deallocate a list in a custom component. - -To receive these signals, simply implement them in a system. - -Back To Top - - -Components Iterators -If you were to require a single component only, ComponentIterator (safe) and ComponentBlockIterator (unsafe) are best suited. - -foreach (var pair in frame.GetComponentIterator()) -{ - var component = pair.Component; - component.Position += FPVector3.Forward * frame.DeltaTime; - frame.Set(pair.Entity, component); -} -Component block iterators give you the fastest possible access via pointers. - -// This syntax returns an EntityComponentPointerPair struct -// which holds the EntityRef of the entity and the requested Component of type T. -foreach (var pair in frame.Unsafe.GetComponentBlockIterator()) -{ - pair.Component->Position += FPVector3.Forward * frame.DeltaTime; -} - -// Alternatively, it is possible to use the following syntax to deconstruct the struct -// and get direct access to the EntityRef and the component -foreach (var (entityRef, transform) in frame.Unsafe.GetComponentBlockIterator()) -{ - transform->Position += FPVector3.Forward * frame.DeltaTime; -} -Back To Top - - -Filters -Filters are a convenient way to filter entities based on a set of components, as well as grabbing only the necessary components required by the system. Filters can be used for both Safe (Get/Set) and Unsafe (pointer) code. - - -Generic -To create a filter simply use the Filter() API provided by the frame. - -var filtered = frame.Filter(); -The generic filter can contain up to 8 components. If you need to more specific by creating without and any ComponentSet filters. - -var without = ComponentSet.Create(); -var any = ComponentSet.Create(); -var filtered = frame.Filter(without, any); -A ComponentSet can hold up to 8 components. The ComponentSet passed as the without parameter will exclude all entities carrying at least one of the components specified in the set. The any set ensures entities have at least one or more of the specified components; if an entity has none of the components specified, it will be excluded by the filter. - -Iterating through the filter is as simple as using a while loop with filter.Next(). This will fill in all copies of the components, and the EntityRef of the entity they are attached to. - -while (filtered.Next(out var e, out var t, out var b)) { - t.Position += FPVector3.Forward * frame.DeltaTime; - frame.Set(e, t); -} -N.B.: You are iterating through and working on copies of the components. So you need to set the new data back on their respective entity. - -The generic filter also offers the possibility to work with component pointers. - -while (filtered.UnsafeNext(out var e, out var t, out var b)) { - t->Position += FPVector3.Forward * frame.DeltaTime; -} -In this instance you are modifying the components' data directly. - -Back To Top - - -FilterStruct -In addition to regular filters, you may use the FilterStruct approach. For this you need to first define a struct with public properties for each component type you would like to receive. - -struct PlayerFilter -{ - public EntityRef Entity; - public CharacterController3D* KCC; - public Health* Health; - public FP AccumulatedDamage; -} -Just like a ComponentSet, a FilterStruct can filter up to 8 different component pointers. - -N.B.: A struct used as a FilterStruct is required to have an EntityRef field! - -The component type members in a FilterStruct HAVE TO BE pointers; only those will be filled by the filter. In addition to component pointers, you can also define other variables, however, these will be ignored by the filter and are left to you to manage. - -var players = f.Unsafe.FilterStruct(); -var playerStruct = default(PlayerFilter); - -while (players.Next(&playerStruct)) -{ - // Do stuff -} -Frame.Unsafe.FilterStruct() has an overload utilizing the optional ComponentSets any and without to further specify the filter. - -Back To Top - - -Note On Count -A filter does not know in advance how many entities it will touch and iterate over. This is due to the way filters work in Sparse-Set ECS: - -the filter finds which among the components provided to it has the least entities associated with it (smaller set to check for intersection); and then, -it goes through the set and discards any entity that does not have the other queried components. -Knowing the exact number in advance would require traversing the filter once; as this is an (O(n) operation, it would not be efficient. - -Back To Top - - -Components Getter -Should you want to get a specific set of components from a known entity, use a filter struct in combination with the Frame.Unsafe.ComponentGetter. N.B.: This is only available in an unsafe context! - -public unsafe class MySpecificEntitySystem : SystemMainThread - - struct MyFilter { - public EntityRef Entity; // Mandatory member! - public Transform2D* Transform2D; - public PhysicsBody2D* Body; - } - - public override void Update(Frame f) { - MyFilter result = default; - - if (f.Unsafe.ComponentGetter().TryGet(f, f.Global->MyEntity, &result)) { - // Do Stuff - } - } -If this operation has to performed often, you can cache the look-up struct in the system as shown below (100% safe). - -public unsafe class MySpecificEntitySystem : SystemMainThread - - struct MyFilter { - public EntityRef Entity; // Mandatory member! - public Transform2D* Transform2D; - public PhysicsBody2D* Body; - } - - ComponentGetter _myFilterGetter; - - public override void OnInit(Frame f) { - _myFilterGetter = f.Unsafe.ComponentGetter(); - } - - public override void Update(Frame f) { - MyFilter result = default; - - if (_myFilterGetter.TryGet(f, f.Global->MyEntity, &result)) { - // Do Stuff - } - } -Back To Top - - -Filtering Strategies -Often times you will be running into a situation where you will have many entities, but you only want a subset of them. Previously we introduced the components and tools available in Quantum to filter them; in this section, we will present some strategies that utilize these. N.B.: The best approach will depend on your own game and its systems. We recommend taking the strategies below as a jumping off point to create a fitting one to your unique situation. - -Note: All terminology used below has been created in-house to encapsulate otherwise wordy concepts. - -Back To Top - - -Micro-component -Although many entities may be using the same component types, few entities use the same component composition. One way to further specialize their composition is by the use of micro-components . Micro-components are highly specialized components with data for a specific system or behaviour. Their uniqueness will allow you to create filters that can quickly identify the entities carrying it. - -Back To Top - - -Flag-component -One common way to identify entities is by adding a flag-component to them. In ECS the concept of flags does not exist per-se, nor does Quantum support entity types; so what exactly are flag-components ? They are components holding little to no data and created for the exclusive purpose of identifying entities. - -For instance, in a team based game you could have: - -a "Team" component with an enum for TeamA and TeamB; or -a "TeamA" and "TeamB" component. -Option 1. is helpful when the main purpose is polling the data from the View, while option 2. will enable you to benefit from the filtering performance in the relevant simulation systems. - -Note: Sometimes a flag-component are also referred to as tag-component because tagging and flagging entities is used interchangeably. \ No newline at end of file diff --git a/data/config-class ref.txt b/data/config-class ref.txt deleted file mode 100644 index fc5d2340ee21563638371d90281ad2f93ec63ded..0000000000000000000000000000000000000000 --- a/data/config-class ref.txt +++ /dev/null @@ -1,165 +0,0 @@ -Photon.Deterministic.DeterministicSessionConfig Class Reference -Parameterize internals of the Deterministic simulation and plugin (the Quantum server component). More... - -Public Attributes -Boolean AggressiveSendMode = false - If the server should skip buffering and perform aggressive input sends, only suitable for games with less or equal 4 players. More... - -Boolean ChecksumCrossPlatformDeterminism = false - If Quantum should skip performing rollbacks and re-predict when it's not needed to retain determinism. Not used in lockstep mode. Mutually exclusive with the _BW_COMPAT_ExposeVerifiedStatusInsideSimulation setting. More... - -Int32 ChecksumInterval = 60 - How often we should send checksums of the frame state to the server for verification (useful during development, set to zero for release). Defined in frames. More... - -Int32 InputDelayMax = 60 - The maximum input offset a player can have. More... - -Int32 InputDelayMin = 0 - The minimum input offset a player can have. More... - -Int32 InputDelayPingStart = 100 - At what ping value that Quantum starts applying input offset. Defined in milliseconds. More... - -Int32 InputFixedSize - Fixed input size. More... - -Boolean InputFixedSizeEnabled - If the input data has a fixed byte length, enabling this saves bandwidth. More... - -Int32 InputHardTolerance = 8 - How many frames the server will wait until it expires a frame and replaces all non-received inputs with repeated inputs or null's and sends it out to all players. More... - -Int32 InputRedundancy = 3 - How much staggering the Quantum client should apply to redundant input resends. 1 = Wait one frame, 2 = Wait two frames, etc. More... - -Int32 InputRepeatMaxDistance = 10 - How many frames Quantum will scan for repeatable inputs. 5 = Scan five frames forward and backwards, 10 = Scan ten frames, etc. More... - -Boolean LockstepSimulation = false - Runs the quantum simulation in lockstep mode, where no rollbacks are performed. s recommended to set input InputDelayMin to at least 10 and _BW_COMPAT_InputPacking to 1. More... - -Int32 MinOffsetCorrectionDiff = 1 - How many frames the current local input delay must diff to the current requested offset for Quantum to update the local input offset. Defined in frames. More... - -Int32 MinTimeCorrectionFrames = 1 - How much the local client time must differ with the server time when a time correction package is received for the client to adjust it's local clock. Defined in frames. More... - -Int32 PlayerCount - Player count the simulation is initialized for. More... - -Int32 RollbackWindow = 60 - How many frames are kept in the local ring buffer on each client. Controls how much Quantum can predict into the future. Not used in lockstep mode. More... - -Int32 SessionStartTimeout = 1 - How long the Quantum server will wait for the room to become full until it forces a start of the Quantum session. Defined in seconds. More... - -Int32 TimeCorrectionRate = 4 - How many times per second the server will send out time correction packages to make sure every clients time is synchronized. More... - -Int32 TimeScaleMin = 100 - The smallest timescale that can be applied by the server. Defined in percent. More... - -Int32 TimeScalePingMax = 300 - The ping value that the server will reach the 'Time Scale Minimum' value at, i.e. be at its slowest setting. Defined in milliseconds. More... - -Int32 TimeScalePingMin = 100 - The ping value that the server will start lowering the time scale towards 'Time Scale Minimum'. Defined in milliseconds. More... - -Int32 UpdateFPS = 60 - How many ticks per second Quantum should execute. More... - -Detailed Description -Parameterize internals of the Deterministic simulation and plugin (the Quantum server component). - -This config file will be synchronized between all clients of one session. Though each player starts its own simulation locally with his own version of the DeterministicConfig the server will distribute the config file instance of the first player that joined the plugin. - -Member Data Documentation -◆ PlayerCount -Int32 Photon.Deterministic.DeterministicSessionConfig.PlayerCount -Player count the simulation is initialized for. - -◆ ChecksumCrossPlatformDeterminism -Boolean Photon.Deterministic.DeterministicSessionConfig.ChecksumCrossPlatformDeterminism = false -If Quantum should skip performing rollbacks and re-predict when it's not needed to retain determinism. Not used in lockstep mode. Mutually exclusive with the _BW_COMPAT_ExposeVerifiedStatusInsideSimulation setting. - -This allows Quantum frame checksumming to be deterministic across different runtime platforms, however it comes with quite a cost and should only be used during debugging. - -◆ LockstepSimulation -Boolean Photon.Deterministic.DeterministicSessionConfig.LockstepSimulation = false -Runs the quantum simulation in lockstep mode, where no rollbacks are performed. s recommended to set input InputDelayMin to at least 10 and _BW_COMPAT_InputPacking to 1. - -◆ AggressiveSendMode -Boolean Photon.Deterministic.DeterministicSessionConfig.AggressiveSendMode = false -If the server should skip buffering and perform aggressive input sends, only suitable for games with less or equal 4 players. - -◆ UpdateFPS -Int32 Photon.Deterministic.DeterministicSessionConfig.UpdateFPS = 60 -How many ticks per second Quantum should execute. - -◆ ChecksumInterval -Int32 Photon.Deterministic.DeterministicSessionConfig.ChecksumInterval = 60 -How often we should send checksums of the frame state to the server for verification (useful during development, set to zero for release). Defined in frames. - -◆ RollbackWindow -Int32 Photon.Deterministic.DeterministicSessionConfig.RollbackWindow = 60 -How many frames are kept in the local ring buffer on each client. Controls how much Quantum can predict into the future. Not used in lockstep mode. - -◆ InputHardTolerance -Int32 Photon.Deterministic.DeterministicSessionConfig.InputHardTolerance = 8 -How many frames the server will wait until it expires a frame and replaces all non-received inputs with repeated inputs or null's and sends it out to all players. - -◆ InputRedundancy -Int32 Photon.Deterministic.DeterministicSessionConfig.InputRedundancy = 3 -How much staggering the Quantum client should apply to redundant input resends. 1 = Wait one frame, 2 = Wait two frames, etc. - -◆ InputRepeatMaxDistance -Int32 Photon.Deterministic.DeterministicSessionConfig.InputRepeatMaxDistance = 10 -How many frames Quantum will scan for repeatable inputs. 5 = Scan five frames forward and backwards, 10 = Scan ten frames, etc. - -◆ SessionStartTimeout -Int32 Photon.Deterministic.DeterministicSessionConfig.SessionStartTimeout = 1 -How long the Quantum server will wait for the room to become full until it forces a start of the Quantum session. Defined in seconds. - -◆ TimeCorrectionRate -Int32 Photon.Deterministic.DeterministicSessionConfig.TimeCorrectionRate = 4 -How many times per second the server will send out time correction packages to make sure every clients time is synchronized. - -◆ MinTimeCorrectionFrames -Int32 Photon.Deterministic.DeterministicSessionConfig.MinTimeCorrectionFrames = 1 -How much the local client time must differ with the server time when a time correction package is received for the client to adjust it's local clock. Defined in frames. - -◆ MinOffsetCorrectionDiff -Int32 Photon.Deterministic.DeterministicSessionConfig.MinOffsetCorrectionDiff = 1 -How many frames the current local input delay must diff to the current requested offset for Quantum to update the local input offset. Defined in frames. - -◆ TimeScaleMin -Int32 Photon.Deterministic.DeterministicSessionConfig.TimeScaleMin = 100 -The smallest timescale that can be applied by the server. Defined in percent. - -◆ TimeScalePingMin -Int32 Photon.Deterministic.DeterministicSessionConfig.TimeScalePingMin = 100 -The ping value that the server will start lowering the time scale towards 'Time Scale Minimum'. Defined in milliseconds. - -◆ TimeScalePingMax -Int32 Photon.Deterministic.DeterministicSessionConfig.TimeScalePingMax = 300 -The ping value that the server will reach the 'Time Scale Minimum' value at, i.e. be at its slowest setting. Defined in milliseconds. - -◆ InputDelayMin -Int32 Photon.Deterministic.DeterministicSessionConfig.InputDelayMin = 0 -The minimum input offset a player can have. - -◆ InputDelayMax -Int32 Photon.Deterministic.DeterministicSessionConfig.InputDelayMax = 60 -The maximum input offset a player can have. - -◆ InputDelayPingStart -Int32 Photon.Deterministic.DeterministicSessionConfig.InputDelayPingStart = 100 -At what ping value that Quantum starts applying input offset. Defined in milliseconds. - -◆ InputFixedSizeEnabled -Boolean Photon.Deterministic.DeterministicSessionConfig.InputFixedSizeEnabled -If the input data has a fixed byte length, enabling this saves bandwidth. - -◆ InputFixedSize -Int32 Photon.Deterministic.DeterministicSessionConfig.InputFixedSize -Fixed input size. \ No newline at end of file diff --git a/data/config.txt b/data/config.txt deleted file mode 100644 index b81433fba824d8aeb8d22bc1755fa34b3203cdb6..0000000000000000000000000000000000000000 --- a/data/config.txt +++ /dev/null @@ -1,88 +0,0 @@ -Quantum.SimulationConfig Class Reference -The SimulationConfig holds parameters used in the ECS layer and inside core systems like physics and navigation. More... - -Inherits Quantum.AssetObject, and Quantum.AssetObject. - -Public Attributes -AutoLoadSceneFromMapMode AutoLoadSceneFromMap = AutoLoadSceneFromMapMode.UnloadPreviousSceneThenLoad - This option will trigger a Unity scene load during the Quantum start sequence. -This might be convenient to start with but once the starting sequence is customized disable it and implement the scene loading by yourself. "Previous Scene" refers to a scene name in Quantum Map. More... - -SimulationConfigChecksumErrorDumpOptions ChecksumErrorDumpOptions - Additional options for checksum dumps, if the default settings don't provide a clear picture. More... - -FP ChecksumSnapshotHistoryLengthSeconds = 3 - How long to store checksumed verified frames. The are used to generate a frame dump in case of a checksum error happening. Not used in Replay and Local mode. More... - -SimulationUpdateTime DeltaTimeType = SimulationUpdateTime.Default - Configure how the client tracks the time to progress the Quantum simulation from the QuantumRunner class. More... - -FrameBase.EntitiesConfig Entities - Global entities configuration More... - -int HeapExtraCount = 0 - Sets extra heaps to allocate for a session in case you need to create 'auxiliary' frames than actually required for the simulation itself More... - -int HeapPageCount = 256 - Define the max heap page count for memory the frame class uses for custom allocations like QList<> for example. More... - -int HeapPageShift = 15 - Define the max heap size for one page of memory the frame class uses for custom allocations like QList<> for example. More... - -Navigation.Config Navigation - Global navmesh configurations. More... - -PhysicsCommon.Config Physics - Global physics configurations. More... - -int ThreadCount = 2 - Override the number of threads used internally. More... - -Detailed Description -The SimulationConfig holds parameters used in the ECS layer and inside core systems like physics and navigation. - -Member Data Documentation -◆ Navigation -Navigation.Config Quantum.SimulationConfig.Navigation -Global navmesh configurations. - -◆ Physics -PhysicsCommon.Config Quantum.SimulationConfig.Physics -Global physics configurations. - -◆ Entities -FrameBase.EntitiesConfig Quantum.SimulationConfig.Entities -Global entities configuration - -◆ AutoLoadSceneFromMap -AutoLoadSceneFromMapMode Quantum.SimulationConfig.AutoLoadSceneFromMap = AutoLoadSceneFromMapMode.UnloadPreviousSceneThenLoad -This option will trigger a Unity scene load during the Quantum start sequence. -This might be convenient to start with but once the starting sequence is customized disable it and implement the scene loading by yourself. "Previous Scene" refers to a scene name in Quantum Map. - -◆ DeltaTimeType -SimulationUpdateTime Quantum.SimulationConfig.DeltaTimeType = SimulationUpdateTime.Default -Configure how the client tracks the time to progress the Quantum simulation from the QuantumRunner class. - -◆ HeapPageShift -int Quantum.SimulationConfig.HeapPageShift = 15 -Define the max heap size for one page of memory the frame class uses for custom allocations like QList<> for example. - -2^15 = 32.768 bytes - -TotalHeapSizeInBytes = (1 << HeapPageShift) * HeapPageCount -◆ HeapPageCount -int Quantum.SimulationConfig.HeapPageCount = 256 -Define the max heap page count for memory the frame class uses for custom allocations like QList<> for example. - -TotalHeapSizeInBytes = (1 << HeapPageShift) * HeapPageCount -◆ HeapExtraCount -int Quantum.SimulationConfig.HeapExtraCount = 0 -Sets extra heaps to allocate for a session in case you need to create 'auxiliary' frames than actually required for the simulation itself - -◆ ThreadCount -int Quantum.SimulationConfig.ThreadCount = 2 -Override the number of threads used internally. - -◆ ChecksumSnapshotHistoryLengthSeconds -FP Quantum.SimulationConfig.ChecksumSnapshotHistoryLengthSeconds = 3 -How long to store checksumed verified frames. The are used to generate a frame dump in case of a checksum error happening. Not used in Replay and Local mode. \ No newline at end of file diff --git a/data/configuration.txt b/data/configuration.txt deleted file mode 100644 index e2c44c2e1979712435d679c8c70638bb7b0434a3..0000000000000000000000000000000000000000 --- a/data/configuration.txt +++ /dev/null @@ -1,190 +0,0 @@ -Introduction -Quantum Start Sequence -Config Files -PhotonServerSettings -DeterministicConfig -SimulationConfig -Delta Time Type -RuntimeConfig -RuntimePlayer -Using DSL Generated Code With RuntimePlayer And RuntimeConfig Serialization - -Introduction -There are a few Quantum config files that have specific roles and purposes. - -These config files are placed in different folders in the Unity project. Finding them quickly is made easy with the shortcuts (unity) editor window found in "Menu/Quantum/Show Shortcuts". - -Most of default config instances reside as Scriptable Objects inside the "Resources" folder at the root level of the Unity project Assets, and will end up in your app build from there (see DeterministicSessionConfigAsset.Instance for example) while others (RuntimeConfig, RuntimePlayer) can be assembled during run-time. - -Back To Top - - -Quantum Start Sequence -Which config is used by whom and send when is shown in the diagram below. - -Config Sequence Diagram -Config Sequence Diagram -Back To Top - - -Config Files - -PhotonServerSettings -Assets/Resources/PhotonServerSettings.asset -Quantum, from version 2.0, uses Photon Realtime to connect and communicate to the Photon Cloud. This config describes where the client connects to (cloud + region, local ip, ..). - -photon realtime introduction - -Also a valid AppId (referring to an active Quantum plugin) is set here. - -Only one instance of this config file is allowed. The loading is tightly integrated into the PhotonNetwork class. See PhotonNetwork.PhotonServerSettings. - -Photon Server Settings -Photon Server Settings -Back To Top - - -DeterministicConfig -Assets/Resouces/DeterministicConfig.asset -Via the DeterministicConfig developers can parametrize internals of the deterministic simulation and plugin (the Quantum server component). Toggle Show Help Info in the inspector of this config for details of each parameter. - -The default way only allows one instance of this asset but as long as it is passed into QuantumRunner.StartParameters it does not matter how the file is retrieved. - -This config file will be synchronized between all clients of one session. Although each player starts their own simulation locally with their own version of the DeterministicConfig, the server will distribute the config file instance of the first player who joined the plugin. - -The data on this config is included in the checksum generation. - -Deterministic Config -Deterministic Config -Back To Top - - -SimulationConfig -Assets/Resources/DB/Configs/SimulationConfig.asset -This config file holds parameters used in the ECS layer and inside core systems like physics and navigation. See the related system sections in the manual for more details of each value. - -The SimulationConfig is part of the Quantum DB and multiple instances of this config are supported. Add the config asset GUID to the RuntimeConfig to select which SimualtionConfig should be used. - -Developers can "extend" (create a partial class) the quantum_code/quantum.state/Core/SimulationConfig.cs class and add more data to it. - -Simulation Config -Simulation Config -Back To Top - - -Delta Time Type -You can customize how the QuantumRunner will accumulate elapsed time to update the Quantum simulation (see the QuantumRunner.DeltaTime property). - -The Default setting will use an internal stopwatch and is the recommended setting for production. -EngineDeltaTime will use, for example Unity.deltaTime, to track when to trigger simulation updates. This is very handy when debugging the project using break points, because upon resuming the simulation with not fast-forward but continue from the exact time the simulation was paused. Alas, this setting can cause issues with time synchronization when initializing online matches: the time tracking can be inaccurate under load (e.g. level loading) and result in a lot of large extra time syncs request and cancelled inputs for a client when starting an online game. -Back To Top - - -RuntimeConfig -In contrast to the SimulationConfig, which has only static configuration data, the RuntimeConfig holds information that can be different from game to game. By default is defines for example what map to load and the random start seed. It is assembled from scratch each time starting a game. - -Developers can add custom data to quantum_code/quantum.state/RuntimeConfig.User.cs (don't forget to fill out the serialization methods). - -Like the DeterministicConfig this "file" is distributed to every other client after the first player connected and joined the Quantum plugin. - -A convenient way of using this config is by creating a MonoBehaviour that stores an instance of RuntimeConfig (and RuntimePlayer) with default values and asset links (GUIDs) for example pointing to other asset files containing specific balancing data. When the player is inside a game lobby parts of the Runtime configs can be overwritten with his custom load-out before connecting and starting the game. See QuantumRunnerLocalDebug.cs or the sample below: - -Runtime Setup -Runtime Setup -using Quantum; -using UnityEngine; - -public sealed class RuntimeSetup : MonoBehaviour -{ - public static RuntimeSetup Instance { get; private set; } - - public RuntimeConfig GameConfig { get { return _gameConfig; } } - public RuntimePlayer PlayerConfig { get { return _playerConfig; } } - - [SerializeField] private RuntimeConfig _gameConfig; - [SerializeField] private RuntimePlayer _playerConfig; - - private void Awake() { - Instance = this; - } -} -Back To Top - - -RuntimePlayer -Similar to the RuntimeConfig the RuntimePlayer describes dynamic properties for one player (quantum_code/quantum.state/RuntimePlayer.User.cs). - -The data for a player behaves differently to the other configs, because it is send by each player individually after the actual game has been started. See the Player document in the manual for more information. - -Back To Top - - -Using DSL Generated Code With RuntimePlayer And RuntimeConfig Serialization -RuntimeConfig and RuntimePlayer require to write manual serialization code. When using DSL generated structs of component prototypes the serialization code can be simplified. - -Caveat: Never use objects that are actually pointers that require a frame to be resolved (e.g. Quantum collections). - -The following struct Foo43 and components prototype Component43 will be used in the RuntimePlayer. - -struct Foo43 { - int Integer; - array[8] Bytes; - asset_ref MapAssetReference; - Bar43 Bar43; -} - -struct Bar43 { - FPVector3 Vector3; -} - -component Component43 { - int Integer; - OtherComponent43 OtherComponent; -} - -component OtherComponent43 { - int Integer; - FP FP; -} -The partial RuntimePlayer.User implementation looks like this. - -partial class RuntimePlayer { - // A) Use a DSL generated struct on RuntimePlayer - public Foo43 Foo; - - // B) Piggyback on a component prototype to set data - public Component43_Prototype Component43 = new Component43_Prototype { OtherComponent = new OtherComponent43_Prototype() }; - - partial void SerializeUserData(BitStream stream) { - // A) Because the struct is memory alined we can pin the memory and serialize it as a byte array which will work platform indenpentently. - unsafe { - fixed (Foo43* p = &Foo) { - stream.SerializeBuffer((byte*)p, sizeof(Foo43)); - } - } - - // B) Initialized the references in the field declaration with new and serialize all fields here. - stream.Serialize(ref Component43.Integer); - stream.Serialize(ref Component43.OtherComponent.Integer); - stream.Serialize(ref Component43.OtherComponent.FP); - } -} -Send the RuntimePlayer from the client: - -var runtimePlayer = new Quantum.RuntimePlayer { - Component43 = new Quantum.Prototypes.Component43_Prototype { - Integer = 1, - OtherComponent = new Quantum.Prototypes.OtherComponent43_Prototype { FP = 2, Integer = 3 } }, - Foo = new Foo43 { - Bar43 = new Bar43 { Vector3 = FPVector3.One }, - Integer = 4, - MapAssetReference = new AssetRefMap() { Id = 66 } - } -}; - -unsafe { - runtimePlayer.Foo.Bytes[0] = 7; - runtimePlayer.Foo.Bytes[1] = 6; -} - -game.SendPlayerData(lp, runtimePlayer); \ No newline at end of file diff --git a/data/continuous collison.txt b/data/continuous collison.txt deleted file mode 100644 index 41b63ec5627460dc601b4e81147a6b2dc1a502d6..0000000000000000000000000000000000000000 --- a/data/continuous collison.txt +++ /dev/null @@ -1,60 +0,0 @@ -Overview -Continous Collision Detection is used to fast moving physics entities collide with other physics colliders instead of tunnelling through them. - -There are two common approaches for CCD algorithms, speculative and sweep based. Quantum implements a speculative Continuous Collision Detection due to the performance considerations tied to its stateless physics engine. The speculative CCD approach is better suited for parallelism while also handling angular motion well; the former is needed for performance and the latter is necessary in many gameplay scenarios. - -The speculative CCD algorithm increases the minimum bounding box used during the broad-phase for an entity based on its PhyiscsBody component linear Velocity and AngularVelocity. It is called speculative because it "speculates" the entity may collide with any of the other physics objects in that area and feeds all these candidates into the solver. This speculation ensures all contact contrains are taken into account when solving the collision thus preventing tunnelling. - -Back To Top - - -Set-up -Two simple steps are required to set up the CCD; both of which can done at edit-time and / or runtime. - -N.B.: Given the performance impact CCD has on the simulation, the CCD functionality is enabled on a per-entity basis and NOT globally! - -Back To Top - - -Edit-Time -Step 1: Check the Allow CCD boolean in the Physics section of the Simulation Config asset. - -Allow CCD in the Simulation Config -Enable the CCD in the Simulation Config. -Step 2: Enable the Use Continuous Collision Detected flag in the Config found on the PhysicsBody component of the Entity Prototype$. - -CCD Flag in the PhysicsBody Config -Select the CCD Flag in the PhysicsBody Config. -Back To Top - - -Runtime -Should the CCD only be necessary in particular situation or moments of the game, it is possible to dynamically toggle the CCD and entities using it on and off. - -Step 1: Toggle the AllowCCD property in the current game state's PhysicsSceneSettings. The PhysicsSceneSettings are part of the frame and initialized with the Physics values found in the SimulationConfig asset. IMPORTANT: Do NOT modify the SimulationConfig asset at runtime, this is undeterministic and will result in desynchronization! - -frame.PhysicsSceneSettings->CCDSettings.AllowCCD = true; -Step 2: Toggle the UseContinuousCollisionDetection property on the PhysicsBody component for the entity which should be using CCD. - -var physicsBody = f.Get(myEntityRef); -physicsBody.UseContinuousCollisionDetection = true; -Back To Top - - -Config -The SimulationConfig assets hold the default values for initializing the physics engine; including the aspects related to the CCD. The default values found in the Continuous Collision Detection (CCD) section are optimal for most games and should only be tweaked with care if edge cases were to arise. - -AllowCCD: Allows CCD to be performed if the Physics Body has CCD enabled on its Config flags. -CCDLinearVelocityThreshold: If CCD is allowed, it will be performed on all Physics Bodies that have it enabled and have a linear velocity magnitude above this threshold. -CCDAngularVelocityThreshold: If CCD is allowed, it will be performed on all Physics Bodies that have it enabled and have a angular velocity magnitude above this threshold. -CCDDistanceTolerance: The absolute distance value below which the Physics Bodies under CCD check can be considered as touching. -MaxTimeOfImpactIterations: The maximum number of iterations performed by the CCD algorithm when computing the time of impact between two Physics Bodies. -MaxRootFindingIterations: The maximum number of iterations performed when computing the point in time when the distance between two Physics Bodies in a given separation axis is below the tolerance. -Back To Top - - -Known Limitations -Although the speculative CCD is feature complete, one needs to be aware of the know limitations of the speculative approach. - -The current algorithm runs a single CCD iteration alongside the regular physics collision resolution. In other words, after a CCD collision is detected and resolved, the remaining delta-time for that entity is integrated regardless of CCD. Thus there is a chance of tunnelling occurring in highly dense environments with extremely fast moving entities. - diff --git a/data/custom-plug-over.txt b/data/custom-plug-over.txt deleted file mode 100644 index 5954af1c4dea45dc97ad17b12bc13194adaac511..0000000000000000000000000000000000000000 --- a/data/custom-plug-over.txt +++ /dev/null @@ -1,40 +0,0 @@ -Overview -You can get Quantum Custom Server Plugin SDK from our quantum sdk page. - -It contains Quantum server libraries, the Photon-Server and a sample project to enable you to run your own Photon-Server Quantum plugin. - -Common features: - -Run your game simulation on the server -Send server snapshots to late-joining or reconnecting clients -Forward the input or replays to another backend -Safely retrieve player configurations/load-outs/inventories from another backend -Forward game results to another backend -Add additional user authentication -Start by watching the intro video below. - -The set up tutorial demonstrates how the sample project runs on a local Photon-Server. - -Use the API docs to gather more information about the features you want to implement. - -Back To Top - - -Quantum Custom Server Plugin Intro Video (YouTube) - -Back To Top - - -API -The PhotonDeterministic.Plugin and PhotonDeterministic.Server classes have been added to the Quantum offline API documentation that can be found in the Quantum SDK folder (unblock the zip before extracting please): - -PhotonQuantum-Documentation.chm -Additional the API information is available as Visual Studio xml documentation alongside the dlls in the Custom Plugin SDK folder: - -assemblies\PhotonDeterministic.Plugin.xml -assemblies\PhotonDeterministic.Server.xml -The comments are visible when hovering over classes and methods: - - -And when opening the classes in your IDE: - diff --git a/data/custom-setup tutorial.txt b/data/custom-setup tutorial.txt deleted file mode 100644 index e59949c165c931c28c59de341c514bf3cf289f92..0000000000000000000000000000000000000000 --- a/data/custom-setup tutorial.txt +++ /dev/null @@ -1,148 +0,0 @@ -Set Up Tutorial -Download The Plugin SDK -Run The Quantum Plugin Locally -License -Start And Debug The Plugin -Connect Clients To The Photon-Server Plugin -1) Export Quantum Asset Database -2) Copy Math Look-Up Tables -3) Configure Asset Paths -4) Configure Unity -Finally -The Quantum custom plugin is based of Photon-Server V5 and follows the workflow described in the Photon Server docs. Dive into these docs for further reading: photon-server v5 step by step guide - -Here we present a tutorial like introduction with all essential steps to get started with the Quantum custom plugin and finally run and debug it locally including the server side game simulation. - -Watch Erick from Exit Games go through the installation and set up process: Quantum Custom Server Plugin Intro (YouTube) - - -Download The Plugin SDK -Download the Plugin SDK package here: quantum sdk & release notes. If a matching Quantum CustomPlugin SDK version is not available please ask us. - -On Windows it's better to unblock the zip file before extracting it (right-click the zip file, select Properties, check the unblock toggle and press OK). If you have problems getting the Photon Server to run locally this might be the cause. - -Extract the zip file to a folder inside your Quantum project to have this setup: - -Quantum project - assemblies - quantum_code - quantum_custom (or any other name) - assemblies - Photon-Server - quantum.custom.plugin - quantum_unity - ... -If you chose another folder location adjust the following paths in quantum.custom.plugin\quantum.custom.plugin.csproj to point to the release versions of the Quantum SDK library files (PhotonDeterministic.dll, quantum.core.dll) and your quantum.code.dll. - - - ..\..\assemblies\release\PhotonDeterministic.dll - - - ..\..\assemblies\release\quantum.core.dll - - - ..\..\quantum_unity\Assets\Photon\Quantum\Assemblies\quantum.code.dll - -SDK Content - -assemblies folder includes the Quantum server plugin (PhotonDeterministic*.dll) and Photon Server libs (PhotonHivePlugin.dll) -Photon-Server folder structure that include necessary tools and programs to run the Photon-Server locally -deploy -bin_Win64 contains PhotonControl.exe, a tray-based tool to control the local Photon Server, PhotonSocketServer.exe which is the Photon Server main application and this folder is also where the license file should go -LoadBalancing\GameServer\bin the location of the plugin.config that supplies settings to our local plugin -log server log files -Plugins\DeterministicPlugin\bin the location of the compiled plugin project and its assets -quantum.custom.plugin source code that runs a Quantum Photon Server plugin -Back To Top - - -Run The Quantum Plugin Locally - -License -If you have not already, create an Exit Games account on our website SignUp. -Visit the website Your Photon Server Licenses log into your account and download your free license file. -If you require licenses to publish a game please contact us -Place the .license file inside the Photon-Server\deploy\bin_Win64 folder -Back To Top - - -Start And Debug The Plugin -Open the quantum_custom.sln and make sure that the references to PhotonDeterministic.dll, quantum.core.dll and quantum.code.dll (your game project) are valid (see section above). Select dlls that are build with release configuration because debug will running be considerably slower. -(Only Until Quantum 2.0 / PhotonServer 4) Run Photon-Server\deploy\bin_Win64\PhotonControl.exe once (it is started inside the Windows tray) and select Game Server IP Config > Set Local IP: 192.168.XXX.XXX - -Press F5 to start and debug the Quantum plugin running inside the Photon-Server -You can set breakpoints but it may disconnect clients due to timeouts when resuming -Common Errors: no license file, Quantum library references not found or not compatible, zip file was not unblocked before extracting, check the Photon-Server\deploy\log for more information.. - -Back To Top - - -Connect Clients To The Photon-Server Plugin -The plugin project is set up in a way that it runs your quantum simulation as well. This can be interesting to add authority over the final game results or being able to send game snapshots to reconnecting players directly from the server. - -The plugin requires the identical game libraries and data: - -Back To Top - - -1) Export Quantum Asset Database -Export the asset db from Unity by selecting Quantum > Export > Asset DB. Chose the folder Photon-Server\deploy\Plugins\DeterministicPlugin\bin\assets as destination. - -Quantum 2.0: - -Rename the file from assetDb.json to db.json (or change the path inside the plugin.config file) -additional .bytes files are created alongside the asset db - -Quantum 2.1: - -No additional files are created. All binary data is included in the json. - -Optionally the export asset db can be embedded into the quantum.code.dll (only Quantum 2.1). There is already an empty placeholder file inside the Quantum code project ( quantum_code\quantum.code\db.json ). If not create it and add to the quantum.code.csproj: - - - - ... - -Export the asset db via the Unity menu to quantum_code\quantum.code\db.json. Recompile the solution and make sure the plugin the just created dll. - -The plugin will always try to load the db from file first. If non was found it will try to load the db from the assembly. - -Back To Top - - -2) Copy Math Look-Up Tables -Copy the LUT folder (quantum_unity\Assets\Photon\Quantum\Resources\LUT) from your Quantum Unity project to Photon-Server\deploy\Plugins\DeterministicPlugin\bin\assets. You can delete the .meta files. - -Back To Top - - -3) Configure Asset Paths -The location of the assets that you just used can be changed in this file: Photon-Server\deploy\LoadBalancing\GameServer\bin\plugin.config (PathToDBFile and PathToLUTFolder). The paths are relative to quantum_custom\Photon-Server\deploy\Plugins\DeterministicPlugin\bin. - -EmbeddedDBFile is the name of the asset db that is optionally embedded into the quantum.code assembly. The file name is db.json but requires the Quantum. prefix. - - - - - - -When you publish the plugin online you need to configure these paths in the Photon dashboard. - -Back To Top - - -4) Configure Unity -The Photon Server Settings file needs the information to connect to a local URL instead the Photon cloud. Open your Unity project select the Photon Server Settings file and press the Configure App Settings - Local Master Server button. - -Settings required: UseNameserver=off, Host=your local IP address, Port=5055 - - - -Finally -Start the plugin (open the custom plugin project and press F5) -Start the Menu scene in Unity, connect and play diff --git a/data/data.txt b/data/data.txt deleted file mode 100644 index e87dd5fe9c4f2d0ffd0238d055be181e80cea9c1..0000000000000000000000000000000000000000 --- a/data/data.txt +++ /dev/null @@ -1,162 +0,0 @@ -Overview -Photon Quantum is a high-performance deterministic ECS (Entity Component System) framework for online multiplayer games made with Unity. - -It is based on the predict/rollback approach which is ideal for latency-sensitive online games such as action RPGs, sports games, fighting games, FPS and more. - -Quantum also helps the developer to write clean code, fully decoupling simulation logic (Quantum ECS) from view/presentation (Unity), while also taking care of the network implementations specifics (internal predict/rollback + transport layer + game agnostic server logic): - -Quantum implements a state-of-the-art tech stack composed of the following pieces: - -Server-managed predict/rollback simulation core. -Sparse-set ECS memory model and API. -Complete set of stateless deterministic libraries (math, 2D and 3D physics, navigation, etc.). -Rich Unity editor integration and tooling. -All built on top of mature and industry-proven existing Photon products and infrastructure (photon realtime transport layer, photon server plugin to host server logic, etc.); - -Determinism Without Lockstep -In deterministic systems, game clients only exchange player input with the simulation running locally on all clients. In the past, this has used a lockstep approach, in which game clients would wait for the input from all other players before updating each tick/frame of the simulation. - -In Quantum, however, game clients are free to advance the simulation locally using input prediction, and an advanced rollback system takes care of restoring game state and re-simulating any mispredictions. - -Because Quantum also relies on a game-agnostic authoritative server component (photon server plugin) to manage input latency and clock synchronization, clients never need to wait for the slowest one to rollback/confirm the simulation forward: - -Quantum Server-Managed predict/Rollback -In Quantum, deterministic input exchange is managed via game-agnostic server logic. This prevents game clients with bad network from interfering with the experience of players on good networks. -These are the basic building blocks of a Quantum game: -Quantum Server Plugin: manages input timing and delivery between game clients, acts as clock synchronization source. Can be extended to integrate with customer-hosted back-end systems (matchmaking, player services, etc.). -Game Client Simulator: communicates with Quantum server, runs local simulation, performing all input prediction and rollbacks. -Custom Gameplay Code: developed by the customer as an isolated pure C# simulation (decoupled from Unity) using the Quantum ECS. Besides providing a framework for how to organize high-performance code, Quantum's API offers a great range of pre-built components (data) and systems (logic) that can be reused in any game such as deterministic 3D vector math, 2D and 3D physics engines, navmesh pathfinder, etc. - -Old School Coding -Starting from the assumption that all simulation code must be high-performance out of the box, Quantum internal systems are all designed with that in mind from the ground up. - -The key to Quantum's high performance is the use of pointer-based C# code combined with its sparse-set ECS memory model (all based memory aligned data-structs and a custom heap allocator - no garbage collection at runtime from simulation code). - -The goal is to leave most of the CPU budget for view/rendering code (Unity), including here the re-simulations induced by input mispredictions, inherent to the predict/rollback approach: - -Quantum hyper-fast predict/rollback -Quantum is designed to make your simulation code run as fast as possible, leaving most of the CPU budget for rendering updates. -Although the use of pointer-based C# is exposed (for performance), most of the complexity is hidden away from the developer by the clever use of a custom DSL and automatic code generation. - -Back To Top - - -Code Generation -In Quantum, all gameplay data (game state) is kept either in the sparse-set ECS data structures (entities and components) or in our custom heap-allocator (dynamic collections and custom data), always as blittable memory-aligned C# structs. - -To define all data structures that go into that, the developer uses a custom DSL (domain specific language) that lets him concentrate on the game concepts instead of performance-oriented restrictions: - -// components define reusable game state data groups - -component Resources -{ - Int32 Mana; - FP Health; -} - -// structs, c-style unions, enums, flags, etc, can be defined directly from the DSL as well -struct CustomData -{ - FP Resources; - Boolean Active; -} -The code-snippet above would generate the corresponding types (with explicit memory alignment), serialization code, and a all boiler plate control logic for special types (like components). - -The auto-generated API lets you both query and modify the game state with comprehensive functions to iterate, modify, create or destroy entities (based on composition): - -var es = frame.Filter(); -// Next fills in copies of each of the components + the EntityRef -while (es.NextUnsafe(out var entity, out var transform, out var resources)) { - transform->Position += FPVector3.Forward * frame.DeltaTime; -} -Back To Top - - -Stateless Systems -While Quantum's DSL covers game state data definition with concepts such as entities, components and auxiliary structures (structs, enums, unions, bitsets, collections, etc.), there needs to be a way to organize the custom game logic that will update this game state. - -You write custom logic by implementing Systems, which are stateless pieces of logic that will be executed every tick update by Quantum's client simulation loop: - -public unsafe class LogicSystem : SystemMainThread -{ - public override void Update(Frame f) - { - // your game logic here (f is a reference for the generated game state container). - } -} -The Systems API game loop call order, signals for system intercommunication (both custom and pre-built, such as the physics engine collision callbacks), events and several other extension hooks. - -Back To Top - - -Events -While the simulation is implemented in pure C#, without referencing Unity's API directly, there are two important features to let gameplay code communicate with the rendering engine: events and the asset linking system. - -Events are a way for the game code to inform the rendering engine that important things happened during the simulation. One good example is when something results in damage to a character. - -Using the state from the previous section as a basis, imagine that damage reduces the health value from the resources component of a character entity. From the Unity rendering scripts, the only noticeable data will be the new health value itself, without any way to know what caused the damage, and also what was the previous health value, etc. - -Event definition in a file DSL: - -event Damage -{ - entity_ref Character; - FP Amount; -} -Gameplay code raises events as a simple API call (generated): - -public void ApplyDamage(Frame f, EntityRef c, FP amount) -{ - // missing here, the logic to apply damage to the character itself - - // this sends an event to the "view" (Unity) - f.Events.Damage(amount, c); -} -Quantum's event processor will handle all generated events after the tick update is done, taking care of events that require server-confirmed input, event repetitions, and also cancelation/confirmation when simulation rollbacks do occur. - -Events raised from the simulation code can then be consumed in runtime from callbacks created in Unity scripts: - -public void OnDamage(DamageEvent dmg) -{ - // instantiate and show floating damage number above the target character, etc -} -Back To Top - - -Asset Linking -Unity is known for its flexible editor and smooth asset pipeline. The Asset Linking system allows game and level designers to create and edit data-driven simulation objects from the Unity Editor, which are then fed into the simulation. This is essential to prototype and to add final balancing touches to the gameplay. - -From the C# simulation project, the developer creates a data-driven class exposing the desired attributes: - -public partial class CharacterClass -{ - public Int32 MaxMana; - public FP MaxHealth; -} -Then from Unity, level designers can create as many instances of this asset as needed, each one being automatically assigned with a Unique GUID: - -Character Classes - Asset Linking -Example of the Asset Linking system: data-driven character-class asset containers being created/modified directly from the Unity Editor. -Then, programmers can use data from these assets directly from inside the simulation: - -var data = frame.FindAsset("character_class_id"); -var mana = data.MaxMana; -It's also possible to reference these assets directly in components from the state definition DSL: - -component CharacterAbilities -{ - asset_ref CharacterData; -} -Back To Top - - -Deterministic Library -In Quantum, the simulation needs to compute the same results on all clients, given they use the same input values. This means it has to be deterministic, which implies neither using any float or doubles variables, nor anything from the Unity API such as their vectors, physics engine, etc. - -To help game developers to implement this style of gameplay, Quantum comes bundled with a flexible and extensible deterministic library, both with a set of classes/struct/functions and also some components that can be used directly when defining entities with the DSL. - -The following modules are available: - -Deterministic math library: FP (Fixed Point) type (Q48.16) to replace floats/doubles, FPVector2, FPVector3, FPMatrix, FPQuaternion, RNGSession, FPBounds2, and all extra math utils including safe casts, and parsers from native types. The math library is implemented with performance as a primary goal, so we make intense use of inlining, lookup tables and fast operators whenever possible. -2D and 3D Physics Engines: high performance stateless 2D/3D physics engines with support for static and dynamic objects, callbacks, joints, etc. -NavMesh/PathFinder/Agents: includes both an exporter from an existing Unity navmesh or an editor to manipulate the mesh directly. Also includes industry standard HRVO collision avoidance, funneled paths, and many more features. diff --git a/data/db.json b/data/db.json deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/data/entity.txt b/data/entity.txt deleted file mode 100644 index ad421faf580899020f73b92cd7ddbf033989a1b3..0000000000000000000000000000000000000000 --- a/data/entity.txt +++ /dev/null @@ -1,149 +0,0 @@ -Entity Prototypes -Introduction -Setting Up A Prototype -Basic -Custom Components -Note On Collections -Hierarchy -Creating/Instantiating A Prototype -Baked In The Scene/Map -In Code -Note -Entity View -Self -Separate From Prototype -Important - -Introduction -To facilitate data driven design, Quantum 2.0 introduced Entity Prototypes. - -An Entity Prototype is a serialized version of an entity that includes: - -composition (i.e. which components it is made of); and, -data (i.e. the components' properties and their initial value). -This allows for a clean separation of data and behaviour, while enabling designers to tweak the former without programmers having to constantly edit the latter. - -Back To Top - - -Setting Up A Prototype -Entity prototypes can be set up in Unity Editor. - - -Basic -To create an Entity Prototype simply add the Entity Prototype script to any GameObject. - -Entity Prototype Script on an empty GameObjet -Basic Entity Prototype (Empty GameObject + Entity Prototype Script). -The Entity Prototype script allows you to set up and define the parameters for the most commonly used components for both 2D and 3D. - -Transform (including Transform2DVertical for 2D) -PhysicsCollider -PhysicsBody -NavMeshPathFinder -NavMeshSteeringAgent -NavMeshAvoidanceAgent -The dependencies for the Physics and NavMesh related agents are respected. For more information, please read their respective documentation. - -Back To Top - - -Custom Components -Additional components can be added to an Entity Prototype via either: - -the Add Entity Component drop-down; or, -the regular Unity Add Component button by searching for the right Entity Component. -Back To Top - - -Note On Collections -Dynamic collections in components are only automatically allocated IF there is at least one item in them. Otherwise, the collection will have to be allocated manually. For more information on the subject, refer to the dynamics collection entry on the dsl page. - -Back To Top - - -Hierarchy -In ECS the concept of entity/GameObject hierarchy does not exist. As such entity prototypes do not support hierarchies or nesting. - -Although child prototypes are not supported directly, you can: - -Create separate prototypes in the scene and bake them. -Link them by keeping a reference in a component. -Update the position of the "child" manually. -Note: Prototypes that are not baked in scene will have to follow a different workflow where the entities are created and linked in code. - -You can have hierarchies in objects (View), however hierarchies in entities (Simulation) will have to be handled by you. - -Back To Top - - -Creating/Instantiating A Prototype -Once a Entity Prototype has been defined in Unity, there are various ways to include it in the simulation. - - -Baked In The Scene/Map -If the Entity Prototype is created as part of a Unity Scene, it will be baked into the corresponding Map Asset. The baked Entity Prototype will be loaded when the Map is and initialized with the values it was baked with. - -N.B.: If a Scene's Entity Prototype is edited or has its values changed, the Map Data has to be re-baked. - -Back To Top - - -In Code -To create a new entity from an Entity Prototype, you need to follow these steps: - -Create a Unity Prefab of the GameObject carrying the EntityPrototype script. -Place the Prefab in Resources\DB. -Entity Prototype Asset -Entity Prototype Prefab + Nested Entity Prototype Asset. -=> This will generate a nested *Entity Prototy* **Asset**. -Refresh the Quantum Database Quantum -> Generate Asset Resources. -Make the Entity Prototy Asset Path or GUID available to your simulation. -Entity Prototype Asset GUID & Path -Entity Prototype Asset Window. -=> To copy the Path or GUID, click on their corresponding `Edit` button. -Call Create() via the frame and pass, for example, the EntityPrototype reference, or an instance of it: -void CreateExampleEntity(Frame f){ - // using a reference - var exampleEntity = f.Create(myPrototypeReference); - - // OR, getting an instance before, using the asset's path as a parameter, and then creating the entity - var entityPrototype = f.FindAsset("Resources/DB/Prefabs/Example|EntityPrototype"); - var exampleEntity = f.Create(entityPrototype); -} -Back To Top - - -Note -Entity Prototypes present in the Scene are baked into the Map Asset, while prefabed Entity Prototypes are individual Assets that are part of the Quantum Asset DataBase. - -Back To Top - - -Entity View -The Entity View corresponds to the visual representation of an entity in Unity. In the spirit of data driven design, an Entity Prototype can either incorporate its View component or point to a separate EntityView Asset. - -Back To Top - - -Self -To set an Entity Prototype's view to itself, simply add the Entity View component to it. - -Entity Prototype with Entity View -Entity Prototype with "Self" View. -Once the component has been added, the *Entity Prototype* script will list **Self** as the value for the *View* parameter. This will also create a nested *Entity View* **Asset** in the same prefab. -Entity Prototype Asset and -Entity Prototype Asset and "Self" View Asset. -Back To Top - - -Separate From Prototype -To set up and link a view separate from the Entity Prototype asset: - -Add the Entity View to the GameObject you would like to represent the view. -Prefab the GameObject carrying the Entity View. -Place the prefab in Resources\DB, this will create an Entity View Asset nested in the prefab. -Entity Prototype with Entity View -Entity Prototype Asset and separate Entity View Asset. -Refresh the database Quantum -> Generate Asset Resources. -Link the View field from the Entity Prototype with the newly created Entity View Asset. This can be done via drag-and-drop or the Unity context search menu. \ No newline at end of file diff --git a/data/events callback.txt b/data/events callback.txt deleted file mode 100644 index bbd8f118c3eae8902546cd6bb75ac090dce0fb74..0000000000000000000000000000000000000000 --- a/data/events callback.txt +++ /dev/null @@ -1,366 +0,0 @@ -Introduction -The split between simulation (Quantum) and view (Unity) allows for great modularity during the development of the game state and the visuals. However, the view requires information from the game state to update itself. Quantum offers two ways: - -Polling the game state -Events/Callbacks -Although both are valid approaches, their use-cases are slightly different. Generally speaking, polling Quantum information from Unity is preferable for on-going visuals while events are used for punctual occurrences where the game simulation triggers a reaction in the view. This document will focus on Frame Events & Callbacks. - -Back To Top - - -Frame Events -Events are a fire-and-forget mechanism to transfer information from the simulation to the view. They should never be used to modify or updates parts of the games state (Signals are used for that). Events have a couple of important aspects to understand that help manage them during prediction and rollbacks. - -Events do not synchronize anything between clients and they are fired by each client's own simulation. -Since the same Frame can be simulated more than once (prediction, rollback), it is possible to have events being triggered multiple times. To avoid undesired duplicated Events Quantum identifies duplicates using a hash code function over the Event data members, the Event id and the tick. See nothashed keyword for further information. -Regular, non-synced, Events will be either cancelled or confirmed once the predicted Frame from which they were fired has been verified. See Canceled And Confirmed Events for further information. -Events are dispatched after all Frames have been simulated right after the OnUpdateView callback. Events are called in the same order they were invoked with the exception of non-synced Events which can be skipped when identified as duplicated. Due to this timing, the targeted EntityView may already have been destroyed. -The simplest Event and its usage looks like this: - -Define an Event using the Quantum DSL -event MyEvent { - int Foo; -} -Trigger the Event from the simulation -f.Events.MyEvent(2022); -And subscribe and consume the Event in Unity, where we generate a class for the event, with the prefix Event -QuantumEvent.Subscribe(listener: this, handler: (EventMyEvent e) => Debug.Log($"MyEvent {e.Foo}")); -Back To Top - - -DSL Structure -Events and their data are defined using the Quantum DSL inside a qtn-file. Compile the project to make them become available via the Frame.Events API in the simulation. - -event MyEvent { - FPVector3 Position; - FPVector3 Direction; - FP Length -} -Class inheritance allows to share base Events classes and members. - -event MyBaseEvent {} -event SpecializedEventFoo : MyBaseEvent {} -event SpecializedEventBar : MyBaseEvent {} -The synced keyword cannot be inherited. -Use abstract classes to prevent base-Events to be triggered directly. - -abstract event MyBaseEvent {} -event MyConcreteEvent : MyBaseEvent {} -Reuse DSL generated structs inside the Event. - -struct FooEventData { - FP Bar; - FP Par; - FP Rap; -} - -event FooEvent { - FooEventData EventData; -} -Back To Top - - -Keywords -Synced -Nothashed -Local, Remote -Client, Server - -Synced -To avoid rollback-induced false positive Events, they can be marked with the synced keyword. This will guarantee the events will only be dispatched (to Unity) when the input for the Frame has been confirmed by the server. - -Synced Events will add a delay between the time it is issued in the simulation (during a predicted Frame) and its manifestation in the view which can be used to inform players. - -synced event MyEvent {} -Synced Events never create false positives or false negatives -Non-synced Events are never called twice on Unity -Back To Top -Back To "Keywords" - - -Nothashed -To prevent an Event which has already been consumed by the view in an earlier predicted Frame to be dispatched again a hash-code is calculated for each Event instance. Before dispatching an Event, the hash-code is used to check if the event is a duplicate. - -This can lead to the following situation: Minimal rollback-induced position changes of one Event are wrongly interpreted as two different Events. - -The nothashed keyword can be used to control what key-candidate data is used to test the Event uniqueness by ignoring parts of the Event data. - -abstract event MyEvent { - nothashed FPVector2 Position; - Int32 Foo; -} -Back To Top -Back To "Keywords" - - -Local, Remote -If an event has a player_ref member special keywords are available : remote and local - -Before the Event is dispatched in Unity on a client the keywords will cause the player_ref to be checked if assigned to a local or remote player respectively. If all conditions match, the event is dispatched on this client. - -event LocalPlayerOnly { - local player_ref player; -} -event RemotePlayerOnly { - remote player_ref player; -} -To recap: the simulation itself is agnostic to the concept of remote and local. The keywords only alter if a particular event is raised in the view of an individual client. - -Should an Event have multiple player_ref parameters, local and remote can be combined. This event will only trigger on the client who controls the LocalPlayer and when the RemotePlayer is assigned to different player. - -event MyEvent { - local player_ref LocalPlayer; - remote player_ref RemotePlayer; - player_ref AnyPlayer; -} -If a client controls several players (e.g. split-screen), all their player_ref will be considered local. - -Back To Top -Back To "Keywords" - - -Client, Server -Since Quantum 2.1 - -This is only relevant when running server side simulation on a custom Quantum plugin. -Events can be qualified using client and server keywords to scope where they will executed. By default all Events will be dispatched on the client and server. - -server synced event MyServerEvent {} -client event MyClientEvent {} -Back To Top - - -Using Events -Trigger Events -Choosing Event Data -Event Subscriptions In Unity -Unsubscribing From Events -Event Subscriptions In CSharp -Canceled And Confirmed Events - -Trigger Events -Events types and signatures are code-generated into the Frame.FrameEvents struct which is accessible over Frame.Events. - -public override void Update(Frame f) { - f.Events.MyEvent(2022); -} -Back To Top -Back To "Using Events" - - -Choosing Event Data -Ideally, the Event data should be self-contained and carry all information the subscriber will need to handle it on the view. - -The Frame at which the Event was raised in the simulation might no longer be available when the Event is actually called on the view. Meaning that information to be retrieved from the Frame needed to handle the Event could be lost. - -A QCollection or QList on an Event is actually only passed as a Ptr to memory on the Frame heap. Resolving the pointer may fail because the buffer is no longer available. The same can be true for EntityRefs, when accessing Components from the most current Frame at the time the Event is dispatched the data may not be the same as when the Event was originally invoked. - -Ways to enrich the Event data with an array or a List: - -If the collection data payload is of a known and reasonable max size a fixed array can be wrapped inside a struct and added to the Event. Unlike QCollections the arrays do not store the data on the Frame heap but carry it on the value itself. -struct FooEventData { - array[4] ArrayOfValues; -} -event FooEvent { - FooEventData EventData; -} -The DSL currently does not allow to declare an Event with a regular C# List type in it. But the Event can be extended using partial classes. See the Extend Event Implementation section for more details. -Back To Top -Back To "Using Events" - - -Event Subscriptions In Unity -Quantum supports a flexible Event subscription API in Unity via QuantumEvent. - -QuantumEvent.Subscribe(listener: this, handler: (EventPlayerHit e) => Debug.Log($"Player hit in Frame {e.Tick}")); -In the example above, the listener is simply the current MonoBehaviour and the handler an anonymous function. Alternatively, a delegate function can be passed in. - -QuantumEvent.Subscribe(listener: this, handler: OnEventPlayerHit); - -private void OnEventPlayerHit(EventPlayerHit e){ - Debug.Log($"Player hit in Frame {e.Tick}"); -} -QuantumEvent.Subscribe offers a few optional QoL arguments that allow to qualify the subscription in various ways. - -// only invoked once, then removed -QuantumEvent.Subscribe(this, (EventPlayerHit e) => {}, once: true); - -// not invoked if the listener is not active -// and enabled (Behaviour.isActiveAndEnabled or GameObject.activeInHierarchy is checked) -QuantumEvent.Subscribe(this, (EventPlayerHit e) => {}, onlyIfActiveAndEnabled: true); - -// only called for runner with specified id -QuantumEvent.Subscribe(this, (EventPlayerHit e) => {}, runnerId: "SomeRunnerId"); - -// only called for a specific -QuantumEvent.Subscribe(this, (EventPlayerHit e) => {}, runner: runnerReference); - -// custom filter, invoked only if player 4 is local -QuantumEvent.Subscribe(this, (EventPlayerHit e) => {}, filter: (QuantumGame game) => game.PlayerIsLocal(4)); - -// only for replays -QuantumEvent.Subscribe(this, (EventPlayerHit e) => {}, gameMode: DeterministicGameMode.Replay); - -// not for replays (Quantum SDK v2.0) -QuantumEvent.Subscribe(this, (EventPlayerHit e) => {}, excludeGameMode: DeterministicGameMode.Replay); - -// for all types except replays (Quantum SDK 2.1+) -QuantumEvent.Subscribe(this, (EventPlayerHit e) => {}, gameMode: DeterministicGameMode.Replay, exclude: true); -//=> The gameMode parameter accepts and array of DeterministicGameMode -Back To Top -Back To "Using Events" - - -Unsubscribing From Events -Unity manages the lifetime of MonoBehaviours, so there is no need to be unregistered as listeners get cleaned up automatically. - -If tighter control is required unsubscribing can be handled manually. - -var subscription = QuantumEvent.Subscribe(); - -// cancels this specific subscription -QuantumEvent.Unsubscribe(subscription); - -// cancels all subscriptions for this listener -QuantumEvent.UnsubscribeListener(this); - -// cancels all listeners to EventPlayerHit for this listener -QuantumEvent.UnsubscribeListener(this); -Back To Top -Back To "Using Events" - - -Event Subscriptions In CSharp -If an Event is subscribed outside of a MonoBehaviour the subscription has to be handled manually. - -var disposable = QuantumEvent.SubscribeManual((EventPlayerHit e) => {}); // subscribes to the event -// ... -disposable.Dispose(); // disposes the event subscription -Back To Top -Back To "Using Events" - - -Canceled And Confirmed Events -Non-synced Events are either cancelled or confirmed once the verified Frame has been simulation. Quantum offers the callbacks CallbackEventCanceled and CallbackEventConfirmed to react to them. - -QuantumCallback.Subscribe(this, (Quantum.CallbackEventCanceled c) => Debug.Log($"Cancelled event {c.EventKey}")); -QuantumCallback.Subscribe(this, (Quantum.CallbackEventConfirmed c) => Debug.Log($"Confirmed event {c.EventKey}")); -Event instances are identified by the EventKey struct. The previously received Event can be added into a dictionary for example by creating the EventKey like this. - -public void OnEvent(MyEvent e) { - EventKey eventKey = (EventKey)e; - // ... -} -Back To Top - - -Extend Event Implementation -Although Events support using a QList. When resolving the list the corresponding Frame might not be available anymore. Additional data types can be added using partial class declarations. - -event ListEvent { - Int32 Foo; -} -public partial class EventListEvent { - public List ListOfFoo; -} -To be able to raise the customized Event via the Frame.Event API extend the FrameEvents struct. - -f.Events.TestListEvent(f, 1, new List() {2, 3, 4});. -namespace Quantum { - public partial class Frame { - public partial struct FrameEvents { - public EventListEvent ListEvent(Frame f, Int32 foo, List listOfFoo) { - var ev = f.Events.ListEvent(foo); - ev.ListOfFoo = listOfFoo; - return ev; - } - } - } -} -Back To Top - - -Callbacks -Callbacks are a special type of event triggered internally by the Quantum Core. The ones made available to the user are: - -Callback Description -CallbackPollInput Is called when the simulation queries local input. -CallbackInputConfirmed Is called when local input was confirmed. -CallbackGameStarted Is called when the game has been started. -CallbackGameResynced Is called when the game has been re-synchronized from a snapshot. -CallbackGameDestroyed Is called when the game was destroyed. -CallbackUpdateView Is guaranteed to be called every rendered frame. -CallbackSimulateFinished Is called when frame simulation has completed. -CallbackEventCanceled Is called when an event raised in a predicted frame was cancelled in a verified frame due to a roll-back / missed prediction. Synchronized events are only raised on verified frames and thus will never be cancelled; this is useful to graciously discard non-synced events in the view. -CallbackEventConfirmed Is called when an event was confirmed by a verified frame. -CallbackChecksumError Is called on a checksum error. -CallbackChecksumErrorFrameDump Is called when due to a checksum error a frame is dumped. -CallbackChecksumComputed Is called when a checksum has been computed. -CallbackPluginDisconnect Is called when the plugin disconnects the client with an error. The reason parameter is filled with an error discription (e.g. "Error #15: Snapshot request timed out"). The client state is unrecoverable after that and needs to reconnect and restart the simulation. The current QuantumRunner should be shutdown immediately. -Back To Top - - -Unity-side Callbacks -By tweaking the value of Auto Load Scene From Map in the SimulationConfig asset, it is possible to determine if the game scene will be loaded automatically or not and it is also possible to determine whether the preview scene unloading will happen before or after the game scene is loaded. - -There are four callbacks that are called when the scenes are being loaded and unloaded: CallbackUnitySceneLoadBegin, CallbackUnitySceneLoadDone, CallbackUnitySceneUnloadBegin, CallbackUnitySceneUnloadDone. - -Back To Top - - -MonoBehaviour -Callbacks are subscribed to and unsubscribe from in the same way one as Frame Events presented earlier, albeit through QuantumCallback instead of QuantumEvent. - -var subscription = QuantumCallback.Subscribe(...); -QuantumCallback.Unsubscribe(subscription); // cancels this specific subscription -QuantumCallback.UnsubscribeListener(this); // cancels all subscriptions for this listener -QuantumCallback.UnsubscribeListener(this); // cancels all listeners to CallbackPollInput for this listener -Unity manages the lifetime of its objects. Therefore, Quantum can detect whether the listener is alive or not. "Dead" listeners are removed with each LateUpdate and with each event invocation for specific event type. - -For example, to subscribe to the PollInput method and set up the player input, the following steps are necessary: - -public class LocalInput : MonoBehaviour { - private DispatcherSubscription _pollInputDispatcher; - private void OnEnable() { - _pollInputDispatcher = QuantumCallback.Subscribe(this, (CallbackPollInput callback) => PollInput(callback)); - } - - public void PollInput(CallbackPollInput callback) { - Quantum.Input i = new Quantum.Input(); - callback.SetInput(i, DeterministicInputFlags.Repeatable); - } - - private void OnDisable(){ - QuantumCallback.Unsubscribe(_pollInputDispatcher); - } -} -Back To Top - - -Pure CSharp -If a callback is subscribed outside of a MonoBehaviour the subscription has to be handled manually. - -var disposable = QuantumCallback.SubscribeManual((CallbackPollInput pollInput) => {}); // subscribes to the callback -// ... -disposable.Dispose(); // disposes the callback subscription -Back To Top - - -Entity Instantiation Order -When creating entities using Frame.Create() and the Frame simulation is finished, the following callbacks will be executed in order: - -OnUpdateView, the view for the newly created entities are instantiated. -Monobehaviour.Awake -Monobehaviour.OnEnabled -EntityView.OnEntityInstantiated -Frame.Events are called. -Event and Callback subscription can be done in either Monobehaviour.OnEnabled or EntityView.OnEntityInstantiated. - -MonoBehaviour.OnEnabled, it is possible to subscribe to events in code here; however, the EntityView's EntityRef and Asset GUID will not have been set yet. -EntityView.OnEntityInstantiated is a UnityEvent part of the EntityView component. It can be subscribed to via the in-editor menu. When OnEntityInstantiated is called, the EntityRef and Asset GUID of the EntityView are guaranteed to be set. If the event subscription or custom logic requires either of those parameters, this is where it should be executed. -OnEntityInstantiated subscription menu in Editor -OnEntityInstantiated subscription menu as seen in the Editor. -To unsubscribe from an event or callback, simply use the complementary functions: - -Unsubscribe in OnDisabled for any subscription made in OnEnabled. -Unsubscribe in OnEntityDestroyed for any subscription made in OnEntityInstantiated. \ No newline at end of file diff --git a/data/extended assets.txt b/data/extended assets.txt deleted file mode 100644 index 33f49758189ec1fc42af632abd1c22b353cfdf15..0000000000000000000000000000000000000000 --- a/data/extended assets.txt +++ /dev/null @@ -1,56 +0,0 @@ -Extending Assets for Unity -Overview -Example -Access At Runtime -Access At Edit-time - -Overview -Quantum assets can be extended with Unity-specific data not relevant for the simulation like data for the UI (colors, texts, icons...). This is done with the use of partial classes. - -Back To Top - - -Example -Let's take the CharacterSpec asset as an example. Its ScriptableObject-based wrapper in Unity is called CharacterSpecAsset and is the type which needs to extended. - -public partial class CharacterSpecAsset { - [Header("Unity")] - public Sprtie Icon; - public Color Color; - public string DisplayName; -} -These fields can only be accessed in the View (Unity) and cannot be accessed or used in the simulation (Quantum). -The newly created partial class needs to be added to the same assembly as the original definition of CharacterSpecAsset. By default, all Unity-side Quantum code belongs to the PhotonQuantum assembly. - -To ensure the partial class belongs to the correct assembly use one of the following approaches: - -Save the class in Assets/Photon/Quantum/User directory. -Save the class in any directory that has an AssemblyDefinitionReference asset pointing to the PhotonQuantum assembly. -Delete Assets/Photon/Quantum/PhotonQuantum.asmdef. This will make Quantum a part of the main assembly. Note that this step needs to be repeated after each Quantum SDK update. -Back To Top - - -Access At Runtime -To access the extra fields at runtime, use the UnityDB.FindAsset() method. - -CharacterSpecAsset characterSpecAsset = UnityDB.FindAsset(guid); -Debug.Log(characterSpecAsset.DisplayName); -Alternatively, the code-generated GetUnityAsset() extension methods can be used: - -CharacterSpec characterSpec = frame.FindAsset(guid); -CharacterSpecAsset characterSpecAsset = characterSpec.GetUnityAsset(); -Debug.Log(characterSpecAsset.DisplayName); -Both of the approaches will result in the asset being loaded into Quantum's AssetDB using the appropriate method, as discussed here: resources, addressables and asset bundles. - -Back To Top - - -Access At Edit-time -To load an asset using its path while in the Unity Editor, the UnityEditor.AssetDataBase.LoadAssetAtPath() method can be used. - -CharacterSpecAsset characterSpecAsset = UnityEditor.AssetDatabase.LoadAssetAtPath(path); -Debug.Log(characterSpecAsset.DisplayName); -Alternatively, the asset can be loaded using its AssetGuid via the UnityDB.FindAssetForInspector() method and casting the result to the correct type. - -CharacterSpecAsset characterSpecAsset = (CharacterSpecAsset)UnityDB.FindAssetForInspector(guid); -Debug.Log(characterSpecAsset.DisplayName); \ No newline at end of file diff --git a/data/frame.txt b/data/frame.txt deleted file mode 100644 index e00e5364c6b53d8a8ca8c7e88ae4795ced6238bf..0000000000000000000000000000000000000000 --- a/data/frame.txt +++ /dev/null @@ -1,41 +0,0 @@ -Frame Classes -The FrameBase class the hub for all of the Quantum game data. More... - -Classes -class Quantum.Frame - The user implementation of FrameBase that resides in the project quantum_state and has access to all user relevant classes. More... - -class Quantum.Core.FrameBase - The Frame class is the container for all the transient and static game state data, including the API for entities, physics, assets and others. More... - -struct Quantum.Core.FrameBase.FrameBaseUnsafe - Frame API to give access to C# unsafe pointers and advanced immediate operations. More... - -Variables -Physics2D.PhysicsEngine2D.Api Quantum.Core.FrameBase.Physics2D - Access to the Physics2D API. More... - -Physics3D.PhysicsEngine3D.Api Quantum.Core.FrameBase.Physics3D - Access to the Physics3D API. More... - -Properties -Navigation Quantum.Core.FrameBase.Navigation[get] - Access to the Navigation API. More... - -Detailed Description -The FrameBase class the hub for all of the Quantum game data. - -Variable Documentation -◆ Physics2D -Physics2D.PhysicsEngine2D.Api Quantum.Core.FrameBase.Physics2D -Access to the Physics2D API. - -◆ Physics3D -Physics3D.PhysicsEngine3D.Api Quantum.Core.FrameBase.Physics3D -Access to the Physics3D API. - -Properties -◆ Navigation -Navigation Quantum.Core.FrameBase.Navigation -get -Access to the Navigation API. \ No newline at end of file diff --git a/data/frames.txt b/data/frames.txt deleted file mode 100644 index acb172ce2bf396e8da8918626ab2968272b733d7..0000000000000000000000000000000000000000 --- a/data/frames.txt +++ /dev/null @@ -1,48 +0,0 @@ -Quantum's predict-rollback architecture allows to mitigate latency. Quantum always rolls-back and re-simulates frames. It is a necessary for determinism and involves the validation of player input by the server. Once the server has either confirmed the player input or overwritten/replaced it (only in cases were the input did not reach the server in time), the validated input of all players for a given frame is sent to the clients. Once the validated input is received, the last verified frame advances using the confirmed input. - -N.B.: A player's own input will be rolled back if it has not reached the server in time or could not be validated. - -Back To Top - - -Types Of Frame -Quantum differenciates between two types of frame: - -verified; and, -predicted. -Back To Top - - -Verified -A Verified frame is a trusted simulation frame. A verified frame is guaranteed to be deterministic and identical on all client simulations. The verified simulation only simulates the next verified frame once it has received the server-confirmed inputs; as such, it moves forward proportional to RTT/2 from the server. - -A frame is verified if both of the following condition are both true: - -the input from ALL players is confirmed by the server for this tick; and, -all previous ticks it follows are verified. -A partial tick confirmation where the input from only a subset of player has been validated by the server will not result in a verified tick/frame. - -Back To Top - - -Predicted -Contrary to verified frames, predicted frames do not require server-confirmed input. This means the predicted frame advances with prediction as soon as the simulation has accumulated enough delta time in the local session. - -The Unity-side API offers access to various versions of the predicted frame, see the API explanation below. - -Predicted : the simulation "head", based on the synchronised clock. -PredictedPrevious (predicted - 1): used for main clock-aliasing interpolation (most views will use this to stay smooth, as Unity's local clock may slightly drift from the main server clock. Quantum runs from a separate clock, in sync with the server clock - smoothly corrected). -PreviousUpdatePredicted: this is the exact frame that was the "Predicted/Head" the last time Session.Update was called (with the "corrected" data in it). Used for error correction interpolation (most of the time there will be no error). -Back To Top - - -API -The concept of Verified and Predicted frames exists in both the simulation and the view, albeit with a slightly different API. - - -Simulation -In the simulation, one can access the state of the currently simulated frame via the Frame class. - -Method Return Value Description -IsVerified bool Returns true if the frame is deterministic across all clients and uses server-confirmed input. -IsPredicted bool Returns true if the frame is a locally predicted one. \ No newline at end of file diff --git a/data/input.txt b/data/input.txt deleted file mode 100644 index 14268004224852696b241f81dff4b6c0975748cf..0000000000000000000000000000000000000000 --- a/data/input.txt +++ /dev/null @@ -1,120 +0,0 @@ -Introduction -Input is a crucial component of Quantum's core architecture. In a deterministic networking library, the output of the system is fixed and predetermined given a certain input. This means that as long as the input is the same across all clients in the network, the output will also be the same. - -Back To Top - - -Defining In DSL -Input can be defined in any dsl file. For example, an input struct where you have a movement direction and a singluar jump button would look something like this: - -input -{ - button Jump; - FPVector3 Direction; -} -The server is responsible for batching and sending down input confirmations for full tick-sets (all player's input). For this reason, this struct should be kept to a minimal size as much as possible. - -Back To Top - - -Commands -deterministic commands are another input path for Quantum, and can have arbitrary data and size, which make them ideal for special types of inputs, like "buy this item", "teleport somewhere", etc. - -Back To Top - - -Polling In Unity -To send input to the Quantum simulation, you must poll for it inside of unity. To do this, we subscribe to the PollInput callback inside of a MonoBehaviour in our gameplay scene. - -private void OnEnable() -{ - QuantumCallback.Subscribe(this, (CallbackPollInput callback) => PollInput(callback)); -} -Then, in the callback, we read from out input source and populate our struct. - -public void PollInput(CallbackPollInput callback) - { - Quantum.Input i = new Quantum.Input(); - - var direction = new Vector3(); - direction.x = UnityEngine.Input.GetAxisRaw("Horizontal"); - direction.y = UnityEngine.Input.GetAxisRaw("Vertical"); - - i.Jump = UnityEngine.Input.GetKeyDown(KeyCode.Space); - - // convert to fixed point. - i.Direction = direction.ToFPVector3(); - - callback.SetInput(i, DeterministicInputFlags.Repeatable); -} -NOTE: The float to fixed point conversion here is deterministic because it is done before it is shared with the simulation. - -Back To Top - - -Optimization -It is genrally best practice to make sure that your Input definition consumes as little bandwidth as possible in order to maximize the amount of players your game can handle. Below are a few ways to optimize it. - -Back To Top - - -Buttons -Instead of using booleans or similar data types to represent key presses, the Button type is used inside the Input DSL definition. This is because it only uses one bit per instance, so it is favorable to use where possible. Although they only use one bit over the network, locally they will contain a bit more game state. This is because the single bit is only representative of whether or not the button was pressed during the current frame, the rest of the information is computed locally. Buttons are defined as follows: - -input -{ - button Jump; -} -Back To Top - - -Encoded Direction -In a typical setting, movement is often represented using a direction vector, often defined in a DSL file as such: - -input -{ - FPVector2 Direction; -} -However, FPVector2 is comprised of two 'FP', which takes up 16 bytes of data, which can be a lot of data sent, especially with many clients in the same room. One such way of optimizing it, is by extending the Input struct and encoding the directional vector into a Byte instead of sending the full vector every single time. One such implemetation is as follows: - -First, we define our input like normal, but instead of including an FPVector2 for direction, we replace it with a Byte where we will store the encoded version. - -input -{ - Byte EncodedDirection; -} -Next, we extend the input struct the same way a component is extended (see: adding functionality): - -namespace Quantum -{ - partial struct Input - { - public FPVector2 Direction - { - get - { - if (EncodedDirection == default) - return default; - - Int32 angle = ((Int32)EncodedDirection - 1) * 2; - - return FPVector2.Rotate(FPVector2.Up, angle * FP.Deg2Rad); - } - set - { - if (value == default) - { - EncodedDirection = default; - return; - } - - var angle = FPVector2.RadiansSigned(FPVector2.Up, value) * FP.Rad2Deg; - - angle = (((angle + 360) % 360) / 2) + 1; - - EncodedDirection = (Byte) (angle.AsInt); - } - } - } -} -This implementation allows for the same usage as before, but it only takes up a singular byte instead of 16 bytes. It does this by utilzing a Direction property, which encodes and decodes the value from EncodedDirection automatically. No newline at end of file \ No newline at end of file diff --git a/data/kinematic character.txt b/data/kinematic character.txt deleted file mode 100644 index f9b0a516d2f15b33511f7c66013bb2e53152d544..0000000000000000000000000000000000000000 --- a/data/kinematic character.txt +++ /dev/null @@ -1,397 +0,0 @@ -Introduction -A Kinematic Character Controller, KCC for short, is used to move a character within the world according to its own set of rules. Using a KCC rather than physics/force based movement allows for tighter control and snappy movement. Although those concepts are core to every game, they vary tremendously in their definition as they are related to the overall gameplay. Therefore the KCCs included in the Quantum SDK are to be considered a starting point; however, game developers will likely have to create their own in order to get the best possible results for their specific context. - -Quantum comes with two pre-build KCCs, one for 2D (side-scrolling) and one for 3D movement. The API allows characters to move through terrains, climb steps, slide down slopes, and use moving platforms. - -The KCCs take physics data of both static and dynamic objects into consideration when calculating the movement vectors. Objects will block and define the character's movement. Collision callbacks with the environment objects will be trigged as well. - -Back To Top - - -Requirements -To use or add a KCC to an entity, the entity has to already have a Transform component. A PhysicsBody can be used but is not necessary; it is generally advised against using a PhysicsBody with a KCC as the physics system may affect it and result in unintended movement. - -If you are not familiar with Quantum's Physics yet, please review the Physics documentation first. - -Back To Top - - -Raycasts & ShapeOverlap -The KCC only uses ShapeOverlaps - circle for 2D and sphere for 3D - to calculate its movement. Thus an entity with only a KKC component will be ignored by raycasts. Should the entity be subject to raycasting, it has to also carry a PhysicsCollider. - -Back To Top - - -Note -This page covers both the 2D and 3D KCCs. - -Back To Top - - -The Character Controller Component -You can add the CharacterController component to your entity by either: - -adding the "Character Controller" component to the Entity Prototype in Unity; or, -adding the "Character Controller" component via code. -KCC 2D and 2D Components in Unity -The Character Controller 2D and 3D components attached to an Entity Prototype in the Unity Editor. -To add the Character Controller via code, follow the examples below. - -// 2D KCC -var kccConfig = FindAsset(KCC_CONFIG_PATH); -var kcc = new CharacterController2D(); -kcc.Init(f, kccConfig) -f.Add(entity, kcc); - -// 3D KCC -var kccConfig = FindAsset(KCC_CONFIG_PATH); -var kcc = new CharacterController3D(); -kcc.Init(f, kccConfig) -f.Add(entity, kcc); -Back To Top - - -Note -The component has to be initiliazed after being created. The available initializing options are: - -(code) the Init() method without parameter, it will load the DefaultCharacterController from Assets/Resources/DB/Configs. -(code) the Init() method with parameter, it will load the passed in CharacterControllerConfig. -(editor) add the CharacterControllerConfig to the Config slot in the Character Controller component. -Back To Top - - -The Character Controller Config -Create your own KCC config asset via the context menu under Create > Quantum > Assets > Physics > CharacterController2D/3D. - - -Default Config Assets -The default 2D and 3D KCC Config assets are located inside the Assets/Resources/DB/Configs folder. Here is how the 3D KCC config looks like: - -KCC 3D Default Config -The DefaultCharacterController3D Config Asset. -Back To Top - - -A Brief Explanation Into The Config Fields -Offset is used to define the KCC local position based into the entity position. It is commonly used to position the center of the KCC at the feet of the character. Remember: the KCC is used to move the character, so it does not necessarily have to encapsulate the character's whole body. -Radius defines the boundaries of the character and should encompass the character horizontal size. This is used to know whether a character can move in a certain direction, a wall is blocking the movement, a step is to be climbed, or a slope to be slid on. -Max Penetration smoothens the movement when a character penetrates other physics objects. If the character passes the Max Penetration, a hard fix will be applied and snap it into the correct position. Reducing this value to zero will apply all corrections fully and instantly; this may result in jagged movement. -Extent defines a radius in which collisions are detected preemptively. -Max Contacts is used to select the amount of contact points computed by the KCC. 1 will usually work fine and is the most performant option. If you experience jerky movement, try setting this to 2; the additional overhead is negligible. -Layer Mask defines which collider layers should be taken into consideration by the physics query performed by the KCC. -Air Control toggle to True and the KCC is able to perform movement adjustments when it not touching the ground. -Acceleration defines how fast the character accelerates. -Base Jump Impulse defines the strength of the impulse when calling the KCC Jump() method. If no value is passed to the method, this value will be used. -Max Speed caps the character's maximal horizontal speed. -Gravity applies a gravity force to the KCC. -Max Slope defines the maximal angle, in degrees, the character can walk up and down. -Max Slope Speed limits the speed at which the character slides down a slope when the movement type is Slope Fall instead of Horizontal Fall. -Back To Top - - -Character Controller API -The API shown below focuses on the 3D KCC. The 2D and 3D APIs are very similar though. - - -Properties And Fields -Each CharacterController component has these fields. - -public FP MaxSpeed { get; set;} -public FPVector3 Velocity { get; set;} -public bool Grounded { get; set;} -public FP CurrentSpeed { get;} -public AssetGUID ConfigId { get;} -Back To Top - - -Tip -The MaxSpeed is a cached value after initialization. It can therefore be modified at runtime, e.g. when performing dashes. - -Back To Top - - -API -Each KCC components has the following methods: - -// Initialization -public void Init(FrameBase frame, CharacterController3DConfig config = null); - -// Jump -public void Jump(FrameBase frame, bool ignoreGrounded = false, FP? impulse = null); - -// Move -public void Move(FrameBase frame, EntityRef entity, FPVector3 direction, IKCCCallbacks3D callback = null, int? layerMask = null, Boolean? useManifoldNormal = null, FP? deltaTime = null); - -// Raw Information -public static CharacterController3DMovement ComputeRawMovement(Frame frame, EntityRef entity, Transform3D* transform, CharacterController3D* kcc, FPVector3 direction, IKCCCallbacks3D callback = null, int? layerMask = null, bool? useManifoldNormal = null); -The Jump and Move methods are convenient for prototyping, while ComputeRawMovement provides the key information for creating your own custom movement. In the example KCC's provided by Quantum, the information from ComputeRawMovement is used by the internal steering method ComputeRawSteer to compute the steering used in Move. - -IMPORTANT: The implementations of Jump(), Move() and ComputeRawSteer() are presented below for fostering understanding and help create custom implementations specific to the game's requirements. - -Back To Top - - -CharacterController3DMovement -ComputeRawMovement() computes the environmental data necessary for the steering by performing a ShapeOverlap and processing the data. The method returns a CharacterController3DMovement struct which can then be applied to the character movement. The movement data provided can also be used to create a custom steering implementation. - -The CharacterController3DMovement struct holds the following information: - -public enum CharacterMovementType -{ - None, // grounded with no desired direction passed - FreeFall, // no contacts within the Radius - SlopeFall, // there is at least 1 ground contact within the Radius, specifically a contact with a normal angle vs -gravity <= maxSlopeAngle). It is possible to be "grounded" without this type of contact (see Grounded property in the CharacterController3DMovement) - Horizontal, // there is NO ground contact, but there is at least one lateral contact (normal angle vs -gravity > maxSlopeAngle) -} - -public struct CharacterController3DMovement -{ - public CharacterMovementType Type; - - // the surface normal of the closest unique contact - public FPVector3 NearestNormal; - - // the average normal from all contacts - public FPVector3 AvgNormal; - - // the normal of the closest contact that qualifies as ground - public FPVector3 GroundNormal; - - // the surface tangent (from GroundNormal and the derived direction) for Horizontal move, or the normalized desired direction when in CharacterMovementType.FreeFall - public FPVector3 Tangent; - - // surface tangent computed from closest the contact normal vs -gravity (does not consider current velocity of CC itself). - public FPVector3 SlopeTangent; - - // accumulated projected correction from all contacts within the Radius. It compensates with dot-products to NOT overshoot. - public FPVector3 Correction; - - // max penetration of the closest contact within the Radius - public FP Penetration; - - // uses the EXTENDED radius to assign this Boolean AND the GroundedNormalas to avoid oscilations of the grounded state when moving over slightly irregular terrain - public Boolean Grounded; - - // number of contacts within Radius - public int Contacts; -} -ComputeRawMovement() is used by the Move() method. - -Back To Top - - -Jump() -This is only a reference implementation. -The Jump simply adds an impulse to the KCC's current Velocity and toggles the Jumped boolean which will be processed by the internal ComputeRawSteer Method. - -public void Jump(FrameBase frame, bool ignoreGrounded = false, FP? impulse = null) { - - if (Grounded || ignoreGrounded) { - - if (impulse.HasValue) - Velocity.Y.RawValue = impulse.Value.RawValue; - else { - var config = frame.FindAsset(Config); - Velocity.Y.RawValue = config.BaseJumpImpulse.RawValue; - } - - Jumped = true; - } -} -Back To Top - - -Move() -This is only a reference implementation. -Move() takes the following things by taking into consideration when calculating the character's new position: - -the current position -the direction -the gravity -jumps -slopes -and more -All these aspects can be defined in the config asset passed to the Init() method. This is convenient for prototyping FPS/TPS/Action games which have terrains, mesh colliders and primitives. - -NOTE: Since it calculates everything and returns a final FPVector3 result, it does not give you much control over the movement itself. For tighter control over the movement, you should use ComputeRawMovement() and create your own custom steering + movement. - -public void Move(Frame frame, EntityRef entity, FPVector3 direction, IKCCCallbacks3D callback = null, int? layerMask = null, Boolean? useManifoldNormal = null, FP? deltaTime = null) { - Assert.Check(frame.Has(entity)); - - var transform = frame.GetPointer(entity); - var dt = deltaTime ?? frame.DeltaTime; - - CharacterController3DMovement movementPack; - fixed (CharacterController3D* thisKcc = &this) { - movementPack = ComputeRawMovement(frame, entity, transform, thisKcc, direction, callback, layerMask, useManifoldNormal); - } - - ComputeRawSteer(frame, ref movementPack, dt); - - var movement = Velocity * dt; - if (movementPack.Penetration > FP.EN3) { - var config = frame.FindAsset(Config.Id); - if (movementPack.Penetration > config.MaxPenetration) { - movement += movementPack.Correction; - } else { - movement += movementPack.Correction * config.PenetrationCorrection; - } - } - - transform->Position += movement; -} -Back To Top - - -ComputeRawSteer() -Steering involves computing the movement based on the position, radius and velocity of the character, and corrects the movement if necessary. - -This is only a reference implementation. -ComputeRawSteer is an internal method that does the bulk of the movement calculations based on the type of movement the character is currently performing. In the example KCCs, Move requests the movementPack values from ComputeRawMovement and passes them to ComputeRawSteer. - -private void ComputeRawSteer(FrameThreadSafe f, ref CharacterController3DMovement movementPack, FP dt) { - - Grounded = movementPack.Grounded; - var config = f.FindAsset(Config); - var minYSpeed = -FP._100; - var maxYSpeed = FP._100; - - switch (movementPack.Type) { - - // FreeFall - case CharacterMovementType.FreeFall: - - Velocity.Y -= config._gravityStrength * dt; - - if (!config.AirControl || movementPack.Tangent == default(FPVector3)) { - Velocity.X = FPMath.Lerp(Velocity.X, FP._0, dt * config.Braking); - Velocity.Z = FPMath.Lerp(Velocity.Z, FP._0, dt * config.Braking); - } else { - Velocity += movementPack.Tangent * config.Acceleration * dt; - } - - break; - - // Grounded movement - case CharacterMovementType.Horizontal: - - // apply tangent velocity - Velocity += movementPack.Tangent * config.Acceleration * dt; - var tangentSpeed = FPVector3.Dot(Velocity, movementPack.Tangent); - - // lerp current velocity to tangent - var tangentVel = tangentSpeed * movementPack.Tangent; - var lerp = config.Braking * dt; - Velocity.X = FPMath.Lerp(Velocity.X, tangentVel.X, lerp); - Velocity.Z = FPMath.Lerp(Velocity.Z, tangentVel.Z, lerp); - - // we only lerp the vertical velocity if the character is not jumping in this exact frame, - // otherwise it will jump with a lower impulse - if (Jumped == false) { - Velocity.Y = FPMath.Lerp(Velocity.Y, tangentVel.Y, lerp); - } - - // clamp tangent velocity with max speed - var tangentSpeedAbs = FPMath.Abs(tangentSpeed); - if (tangentSpeedAbs > MaxSpeed) { - Velocity -= FPMath.Sign(tangentSpeed) * movementPack.Tangent * (tangentSpeedAbs - MaxSpeed); - } - - break; - - // Sliding due to excessively steep slope - case CharacterMovementType.SlopeFall: - - Velocity += movementPack.SlopeTangent * config.Acceleration * dt; - minYSpeed = -config.MaxSlopeSpeed; - - break; - - // No movement, only deceleration - case CharacterMovementType.None: - - var lerpFactor = dt * config.Braking; - - if (Velocity.X.RawValue != 0) { - Velocity.X = FPMath.Lerp(Velocity.X, default, lerpFactor); - if (FPMath.Abs(Velocity.X) < FP.EN1) { - Velocity.X.RawValue = 0; - } - } - - if (Velocity.Z.RawValue != 0) { - Velocity.Z = FPMath.Lerp(Velocity.Z, default, lerpFactor); - if (FPMath.Abs(Velocity.Z) < FP.EN1) { - Velocity.Z.RawValue = 0; - } - } - - // we only lerp the vertical velocity back to 0 if the character is not jumping in this exact frame, - // otherwise it will jump with a lower impulse - if (Velocity.Y.RawValue != 0 && Jumped == false) { - Velocity.Y = FPMath.Lerp(Velocity.Y, default, lerpFactor); - if (FPMath.Abs(Velocity.Y) < FP.EN1) { - Velocity.Y.RawValue = 0; - } - } - - minYSpeed = 0; - - break; - } - - // horizontal is clamped elsewhere - if (movementPack.Type != CharacterMovementType.Horizontal) { - var h = Velocity.XZ; - - if (h.SqrMagnitude > MaxSpeed * MaxSpeed) { - h = h.Normalized * MaxSpeed; - } - - Velocity.X = h.X; - Velocity.Y = FPMath.Clamp(Velocity.Y, minYSpeed, maxYSpeed); - Velocity.Z = h.Y; - } - - // reset jump state - Jumped = false; -} -Back To Top - - -Collision Callbacks -Whenever the KCC detects intersections with colliders a callback is triggered. - -public interface IKCCCallbacks2D -{ - bool OnCharacterCollision2D(FrameBase f, EntityRef character, Physics2D.Hit hit); - void OnCharacterTrigger2D(FrameBase f, EntityRef character, Physics2D.Hit hit); -} - -public interface IKCCCallbacks3D -{ - bool OnCharacterCollision3D(FrameBase f, EntityRef character, Physics3D.Hit3D hit); - void OnCharacterTrigger3D(FrameBase f, EntityRef character, Physics3D.Hit3D hit); -} -To receive the callbacks and use its information implement the corresponding IKCCCallbacks interface in a system. - -Important Note that the collision callbacks return a Boolean value. This is allows you decide whether a collision should be ignored. Returning false makes the character pass through physics object it collided with. - -Besides implementing the callbacks the movement methods should also pass the IKCCCallbacks object; below is a code snippet using the collision callbacks. - -public unsafe class SampleSystem : SystemMainThread, IKCCCallbacks3D -{ - public bool OnCharacterCollision3D(FrameBase f, EntityRef character, Physics3D.Hit3D hit) { - // read the collision information to decide if this should or not be ignored - return true; - } - - public void OnCharacterTrigger3D(FrameBase f, EntityRef character, Physics3D.Hit3D hit) { - } - - public override void Update(Frame f) { - // [...] - // adding the IKCCCallbacks3D as the last parameter (this system, in this case) - var movement = CharacterController3D.Move((Entity*)character, input->Direction, this); - // [...] -} \ No newline at end of file diff --git a/data/list1.txt b/data/list1.txt deleted file mode 100644 index 81f81b2a1c3e60298871359b6357cb09b47094f5..0000000000000000000000000000000000000000 --- a/data/list1.txt +++ /dev/null @@ -1,10 +0,0 @@ -In quantum we can to list as metioned below. - -component Targets { - list Enemies; -} -The basic API methods for dealing with these Lists are: - -Frame.AllocateList() -Frame.FreeList(QListPtr ptr) -Frame.ResolveList(QListPtr ptr) diff --git a/data/materialization.txt b/data/materialization.txt deleted file mode 100644 index 285452ed9d4c2f95f43ba6b66923da84dab269d3..0000000000000000000000000000000000000000 --- a/data/materialization.txt +++ /dev/null @@ -1,335 +0,0 @@ -Introduction -The process of creating an entity or component instance from a Component Prototype or Entity Prototype is called Materialization . - -The materialization of scene prototypes baked into the map asset follow the same rules and execution flow as the materialization of code created instances using the Frame.Create API. - -Back To Top - - -Prototype Vs Instance -The component instances and entity instances are part of the game state; in other words they can be manipulated at runtime. Components declared in the DSL are used to generate their corresponding Component Prototypes. The code generated prototypes follow the naming convention MyComponent_Prototype. - -Component Prototypes and Entity Prototypes are both assets; this means they are not part of the game state, immutable at runtime and have to be identical for all clients at all time. Each Component Prototype has a ComponentPrototypeRef which can be used to find it the corresponding asset using the Frame.FindPrototype(MyComponentPrototypeRef). - -Back To Top - - -Component Prototypes -It is possible to extend a Component Prototype to include data which may not be directly used in materialization. This allows, for example, to have shared data between instances of a particular component or exclude read-only data from the frame to keep the game state slim. - -Code generated Component Prototypes are partial classes which can be easily extended: - -Create a C# file called MyComponentName_Prototype.cs; -Place the body of the script into the Quantum.Prototypes namespace; -( Optional ) Add using Quantum.Inspector; to have access to the inspector attributes presented in the Attributes section of the Manual \ ECS page. -It is then possible to add extra data to the Component Prototype asset and implement the partial MaterializeUser() method to add custom materialization logic. - -Back To Top - - -Example -The following example presents the materialization of the Vehicle component as found in the Arcade Racing Template. - -The Vehicle component holds mainly dynamic values computed at runtime. Since these cannot be initialized, the component definition in the DSL uses the ExcludeFromPrototype attribute on those parameters to exclude them from the Vehicle_Prototype asset designers can manipulate in the Unity editor. The Nitro parameter is only part that can be edited to allow designers to decide with how much nitro a specific Vehicle is initialized. - -component Vehicle -{ - [ExcludeFromPrototype] - ComponentPrototypeRef Prototype; - - [ExcludeFromPrototype] - Byte Flags; - [ExcludeFromPrototype] - FP Speed; - [ExcludeFromPrototype] - FP ForwardSpeed; - [ExcludeFromPrototype] - FPVector3 EngineForce; - [ExcludeFromPrototype] - FP WheelTraction; - - [ExcludeFromPrototype] - FPVector3 AvgNormal; - - [ExcludeFromPrototype] - array[4] Wheels; - - FP Nitro; -} -The Vehicle_Prototype asset is extended to provide designers with customizable read-only parameters. The Vehicle_Prototype asset can thus hold shared values for all instances of a specific vehicle entity prototype "type". The Prototype parameter in the Vehicle component is of type ComponentPrototypeRef which is the component specific equivalent to AssetRef. To populate it, the partial MaterializeUser() method is used to assign the reference of the Vehicle_Prototype. - -using Photon.Deterministic; -using Quantum.Inspector; -using System; - -namespace Quantum.Prototypes -{ -public unsafe partial class Vehicle_Prototype -{ - // PUBLIC METHODS - - [Header("Engine")] - public FP EngineForwardForce = 130; - public FP EngineBackwardForce = 120; - public FPVector3 EngineForcePosition; - public FP ApproximateMaxSpeed = 20; - - [Header("Hand Brake")] - public FP HandBrakeStrength = 10; - public FP HandBrakeTractionMultiplier = 1; - - [Header("Resistances")] - public FP AirResistance = FP._0_02; - public FP RollingResistance = FP._0_10 * 6; - public FP DownForceFactor = 0; - public FP TractionGripMultiplier = 10; - public FP AirTractionDecreaseSpeed = FP._0_50; - - [Header("Axles")] - public AxleSetup FrontAxle = new AxleSetup(); - public AxleSetup RearAxle = new AxleSetup(); - - [Header("Nitro")] - public FP MaxNitro = 100; - public FP NitroForceMultiplier = 2; - - // PARTIAL METHODS - partial void MaterializeUser(Frame frame, ref Vehicle result, in PrototypeMaterializationContext context) - { - result.Prototype = context.ComponentPrototypeRef; - } - - [Serializable] - public class AxleSetup - { - public FPVector3 PositionOffset; - public FP Width = 1; - public FP SpringForce = 120; - public FP DampingForce = 175; - public FP SuspensionLength = FP._0_10 * 6; - public FP SuspensionOffset = -FP._0_25; - } -} -} -The parameters in the Vehicle_Prototype hold values necessary to compute the dynamic values found in the component instance which impact the behaviour of the entity to which the Vehicle component is attached. For example, when a player picks up additional Nitro, the value held in the Vehicle component is clamped to the MaxNitro value found in the Vehicle_Prototype. This enforces the limits under penality of desynchronization and keeps the game state slim. - -namespace Quantum -{ - public unsafe partial struct Vehicle - { - public void AddNitro(Frame frame, EntityRef entity, FP amount) - { - var prototype = frame.FindPrototype(Prototype); - Nitro = FPMath.Clamp(Nitro + amount, 0, prototype.MaxNitro); - } - } -} -Back To Top - - -Materialization Order -Every Entity Prototype, including the scene prototypes, the materialization executes the following steps in order: - -An empty entity is created. -For each Component Prototype contained in the Entity Prototype: -the component instance is created on the stack; -the Component Prototype is materialized into the component instance; -( Optional ) MaterializeUser() is called; and, -the component is added to the entity which triggers the ISignalOnComponentAdded signal. -ISignalOnEntityPrototypeMaterialized is invoked for each materialized entity. -Load Map / Scene: the signal is invoked for all entity & Entity Prototype pair after all scene prototypes have been materialized. -Created with Frame.Create(): the signal is invoked immediately after the prototype has been materialized. -The Component Prototype materialization step materializes default components in a predetermined order. - -Transform2D -Transform3D -Transform2DVertical -PhysicsCollider2D -PhysicsBody2D -PhysicsCollider3D -PhysicsBody3D -PhysicsJoints2D -PhysicsJoints3D -PhysicsCallbacks2D -PhysicsCallbacks3D -CharacterController2D -CharacterController3D -NavMeshPathfinder -NavMeshSteeringAgent -NavMeshAvoidanceAgent -NavMeshAvoidanceObstacle -View -MapEntityLink -Once all default components have been materialized, the user defined components are materialized in alphabetically order. - -MyComponentAA -MyComponentBB -MyComponentCC -...ntroduction -The process of creating an entity or component instance from a Component Prototype or Entity Prototype is called Materialization . - -The materialization of scene prototypes baked into the map asset follow the same rules and execution flow as the materialization of code created instances using the Frame.Create API. - -Back To Top - - -Prototype Vs Instance -The component instances and entity instances are part of the game state; in other words they can be manipulated at runtime. Components declared in the DSL are used to generate their corresponding Component Prototypes. The code generated prototypes follow the naming convention MyComponent_Prototype. - -Component Prototypes and Entity Prototypes are both assets; this means they are not part of the game state, immutable at runtime and have to be identical for all clients at all time. Each Component Prototype has a ComponentPrototypeRef which can be used to find it the corresponding asset using the Frame.FindPrototype(MyComponentPrototypeRef). - -Back To Top - - -Component Prototypes -It is possible to extend a Component Prototype to include data which may not be directly used in materialization. This allows, for example, to have shared data between instances of a particular component or exclude read-only data from the frame to keep the game state slim. - -Code generated Component Prototypes are partial classes which can be easily extended: - -Create a C# file called MyComponentName_Prototype.cs; -Place the body of the script into the Quantum.Prototypes namespace; -( Optional ) Add using Quantum.Inspector; to have access to the inspector attributes presented in the Attributes section of the Manual \ ECS page. -It is then possible to add extra data to the Component Prototype asset and implement the partial MaterializeUser() method to add custom materialization logic. - -Back To Top - - -Example -The following example presents the materialization of the Vehicle component as found in the Arcade Racing Template. - -The Vehicle component holds mainly dynamic values computed at runtime. Since these cannot be initialized, the component definition in the DSL uses the ExcludeFromPrototype attribute on those parameters to exclude them from the Vehicle_Prototype asset designers can manipulate in the Unity editor. The Nitro parameter is only part that can be edited to allow designers to decide with how much nitro a specific Vehicle is initialized. - -component Vehicle -{ - [ExcludeFromPrototype] - ComponentPrototypeRef Prototype; - - [ExcludeFromPrototype] - Byte Flags; - [ExcludeFromPrototype] - FP Speed; - [ExcludeFromPrototype] - FP ForwardSpeed; - [ExcludeFromPrototype] - FPVector3 EngineForce; - [ExcludeFromPrototype] - FP WheelTraction; - - [ExcludeFromPrototype] - FPVector3 AvgNormal; - - [ExcludeFromPrototype] - array[4] Wheels; - - FP Nitro; -} -The Vehicle_Prototype asset is extended to provide designers with customizable read-only parameters. The Vehicle_Prototype asset can thus hold shared values for all instances of a specific vehicle entity prototype "type". The Prototype parameter in the Vehicle component is of type ComponentPrototypeRef which is the component specific equivalent to AssetRef. To populate it, the partial MaterializeUser() method is used to assign the reference of the Vehicle_Prototype. - -using Photon.Deterministic; -using Quantum.Inspector; -using System; - -namespace Quantum.Prototypes -{ -public unsafe partial class Vehicle_Prototype -{ - // PUBLIC METHODS - - [Header("Engine")] - public FP EngineForwardForce = 130; - public FP EngineBackwardForce = 120; - public FPVector3 EngineForcePosition; - public FP ApproximateMaxSpeed = 20; - - [Header("Hand Brake")] - public FP HandBrakeStrength = 10; - public FP HandBrakeTractionMultiplier = 1; - - [Header("Resistances")] - public FP AirResistance = FP._0_02; - public FP RollingResistance = FP._0_10 * 6; - public FP DownForceFactor = 0; - public FP TractionGripMultiplier = 10; - public FP AirTractionDecreaseSpeed = FP._0_50; - - [Header("Axles")] - public AxleSetup FrontAxle = new AxleSetup(); - public AxleSetup RearAxle = new AxleSetup(); - - [Header("Nitro")] - public FP MaxNitro = 100; - public FP NitroForceMultiplier = 2; - - // PARTIAL METHODS - partial void MaterializeUser(Frame frame, ref Vehicle result, in PrototypeMaterializationContext context) - { - result.Prototype = context.ComponentPrototypeRef; - } - - [Serializable] - public class AxleSetup - { - public FPVector3 PositionOffset; - public FP Width = 1; - public FP SpringForce = 120; - public FP DampingForce = 175; - public FP SuspensionLength = FP._0_10 * 6; - public FP SuspensionOffset = -FP._0_25; - } -} -} -The parameters in the Vehicle_Prototype hold values necessary to compute the dynamic values found in the component instance which impact the behaviour of the entity to which the Vehicle component is attached. For example, when a player picks up additional Nitro, the value held in the Vehicle component is clamped to the MaxNitro value found in the Vehicle_Prototype. This enforces the limits under penality of desynchronization and keeps the game state slim. - -namespace Quantum -{ - public unsafe partial struct Vehicle - { - public void AddNitro(Frame frame, EntityRef entity, FP amount) - { - var prototype = frame.FindPrototype(Prototype); - Nitro = FPMath.Clamp(Nitro + amount, 0, prototype.MaxNitro); - } - } -} -Back To Top - - -Materialization Order -Every Entity Prototype, including the scene prototypes, the materialization executes the following steps in order: - -An empty entity is created. -For each Component Prototype contained in the Entity Prototype: -the component instance is created on the stack; -the Component Prototype is materialized into the component instance; -( Optional ) MaterializeUser() is called; and, -the component is added to the entity which triggers the ISignalOnComponentAdded signal. -ISignalOnEntityPrototypeMaterialized is invoked for each materialized entity. -Load Map / Scene: the signal is invoked for all entity & Entity Prototype pair after all scene prototypes have been materialized. -Created with Frame.Create(): the signal is invoked immediately after the prototype has been materialized. -The Component Prototype materialization step materializes default components in a predetermined order. - -Transform2D -Transform3D -Transform2DVertical -PhysicsCollider2D -PhysicsBody2D -PhysicsCollider3D -PhysicsBody3D -PhysicsJoints2D -PhysicsJoints3D -PhysicsCallbacks2D -PhysicsCallbacks3D -CharacterController2D -CharacterController3D -NavMeshPathfinder -NavMeshSteeringAgent -NavMeshAvoidanceAgent -NavMeshAvoidanceObstacle -View -MapEntityLink -Once all default components have been materialized, the user defined components are materialized in alphabetically order. - -MyComponentAA -MyComponentBB -MyComponentCC -... \ No newline at end of file diff --git a/data/materials.txt b/data/materials.txt deleted file mode 100644 index b8006c0d1b09d8cf92aa8271ed0fea55ac99ef5b..0000000000000000000000000000000000000000 --- a/data/materials.txt +++ /dev/null @@ -1,54 +0,0 @@ -Overview -Every PhysicsBody requires a PhysicsMaterial (a quantum data-asset). The PhysicsMaterial holds properties necessary for the physics engine to resolve collisions, integration of forces and velocities. - -Back To Top - - -PhysicsMaterial Data-Asset -The PhysicsMaterial holds the parameters for: - -Restitution (sometimes referred to as "bounciness", or "bounce") -Restiution Combine Function -Friction Static -Friction Dynamic -Friction Combine Function -If no PhysicsMaterial asset is slotted, the default physics material will be assigned; the default physics material is the one linked in the SimulationConfig physics settings. - -Adjusting Properties to Physics Materials -Adjusting Properties to Physics Materials. -A PhysicsMaterial asset can be assigned to a PhysicsCollider directly: - -var material = f.FindAsset("steel"); -collider.Material = material; - -f.Set(entity, collider); -Back To Top - - -Important Note -A PhysicsMaterial is a data asset and lives in the Quantum Asset Database. As assets are not part of the rollback-able game state, every PhysicsMaterial is therefore to be considered immutable at runtime. Changing its properties while the game running leads to non-deterministic behaviour. - -PhysicsMaterials follow the same rules as other data-assets. - -// this is NOT safe and cannot be rolled-back: -collider->Material.Restitution = FP._0; - -// switching a reference is safe and can be rolled back: -var newMaterial = f.FindAsset("ice"); -collider->Material = newMaterial; -Back To Top - - -Combine Functions -The Combine Function used to resolve the restitution and friction for each collision manifold (a collision pair) is based on the combine functions' precedence order. The Physics system will chose the function with the highest precedent from the two colliders. The precedence order is: - -Max -Min -Average -Multiply -For instance: take a collision manifold with a Collider A and Collider B. Collider A's physics material has a Restitution Combine Function set to Max, while Collider B's physics material has its set to Average. Since Max has a higher priority than Average, the restitution for this collision will be solved using the Max function. - -The same logic applies to the Friction Combine Function. - -N.B.: The Friction Combine Function and Restitution Combine Function are resolved separately and thus carry different settings. - diff --git a/data/multi-client-runner.txt b/data/multi-client-runner.txt deleted file mode 100644 index e4d61d8c1ad95955ec08509c124f0b027b58f19e..0000000000000000000000000000000000000000 --- a/data/multi-client-runner.txt +++ /dev/null @@ -1,90 +0,0 @@ -Introduction -Quantum's Multi-Client Runner is a powerful tool that allows multiple local players to play together in the same Quantum room. This tool is especially useful for developers who wants to test and debug their game without having to build the game every time. - -Back To Top - - -Required Settings -The Multi-Client Runner requires a few pre-requisites in order to operate correctly. - -You need to make sure that you have the following: - -A valid AppId: You can get this by registering your game on the dashboard on the photon website. -Correctly configured Photon Server Settings. You can adjust and check these values by navigating to the scriptable object in your game's project files. -Ensuring the QuantumMultiClientRunner prefab is in your game scene. -Back To Top - - -Setup -To get started, navigate to the QuantumMultiClientRunner prefab and drag it into your game scene. This prefab is an example implementation of the Multi-Client Runner. - -Runner Search Screenshot -Once the prefab has been put into your game scene, select it and view the QuantumMultiClientRunner component. - -Multi Client Runner -In this component, there are several configurable values: - -DisableOnStart: When utilizing the MultiClientRunner, it is necessary to disable quantum scripts that are typically included in the standard game setup, such as EntityViewUpdater, Input, and CustomCallbacks. Please ensure that you add these scripts to the list of disabled scripts. -EditorSettings: You have the option to provide non-default editor settings for all additional clients after the first one. For example, changing the gizmo color. -AppSettings: Optionally provide different non-default server app settings. For example, a different region than normal. -RuntimeConfig: Optional custom runtime config settings. -PlayerCount: Max player count. -InitialPlayerCount: How many players to start the game with. -RuntimePlayer[]: Optional custom runtime player settings. -PlayerInputTemplate: A player input template that is instantiated for each client. This must contain a Unity script that implements the Unity message/method void PollInput(CallbackPollInput c). An example of a script that correctly implements this would look like the following: -public void PollInput(CallbackPollInput callback) - { - Quantum.Input i = new Quantum.Input(); - - var direction = new Vector3(); - direction.x = UnityEngine.Input.GetAxisRaw("Horizontal"); - direction.y = UnityEngine.Input.GetAxisRaw("Vertical"); - - i.Jump = UnityEngine.Input.GetKeyDown(KeyCode.Space); - - // convert to fixed point. - i.Direction = direction.ToFPVector3(); - - callback.SetInput(i, DeterministicInputFlags.Repeatable); -} -EntityViewUpdater An optional custom EntityViewUpdater game object that is instantiated for each client. Otherwise a new instance of the default EntityViewUpdater is created for each client. -Back To Top - - -Playing -After setup, you are now ready to use the MultiClient Runner. - -Once the game is running, you will notice a menu in the top left of your game's window. - -Runner Runtime Screenshot -This menu consists of several toggles that allow you to control each locally connected client: - -New Client Add additional online clients -I Toggle input of the client -V Toggle view of the client -G Toggle gizmos of the client -X Disconnects the client -You can also toggle multiple at the same time to control multiple clients at once. - -Back To Top - - -Code Example -The Multi-Client Runner's methods can also be controlled via user code. - -public void CreateNewLocalClient() -{ - var multiclient = FindObjectOfType(); - - // initializes a new local player - multiclient.CreateNewPlayer(); -} - -public void ShutDownLocalClient() -{ - // find the instance you want to shut down - var player = FindObjectOfType(); - - // stops the local player instance - player.Stop(); -} \ No newline at end of file diff --git a/data/nav-agent .txt b/data/nav-agent .txt deleted file mode 100644 index ba67ff221dfbc95f14df224df0d8bed8303ccfd8..0000000000000000000000000000000000000000 --- a/data/nav-agent .txt +++ /dev/null @@ -1,178 +0,0 @@ -Creating Navmesh Agents -Since Quantum 2.0 navmesh agents are split into multiple components. We noticed that developers working with navmesh and steering want to control the final movement result, which makes a lot of sense, because it often is so vital to the game experience. The new navmesh agent parts should help developers to pick a combination of navmesh support without losing multi-threaded performance and without executing unneeded parts or wasting unneeded memory. - -Agent components are NavMeshPathfinder, NavMeshSteeringAgent and NavMeshAvoidanceAgent. A stand-alone component is NavMeshAvoidanceObstacle. - -Agent entities can be created in two ways: using Entity Prototypes in Unity or assembling the entity in code. They still use the NavMeshAgentConfig Quantum asset. - -Creating Agents With Entity Prototypes In Unity -Creating Agents With Components In Code -Important Agent Settings -Pathfinder -Steering Agent -Update Interval -Using Navmesh Agent Callbacks -Common Navmesh Agent Setups - -Creating Agents With Entity Prototypes In Unity -Create an empty Quantum prototype via the Unity menu: GameObject/Quantum/Empty Entity -Select the entity and set Transform to 2D -Toggle NavMeshPathfinder component -Select the default NavMeshAgentConfig -Toggle Initial Target and select a transform from the Unity scene to provide an initial position to move to -Select the baked Quantum navmesh (see Navmesh workflow) -Toggle on NavMeshSteeringAgent -To see the path gizmos either: -Activate Show Debug Steering on the default NavMeshAgentConfig or -Activate the Navmesh Gizmo Draw Pathfinder Funnel in QuantumEditorSettings -Press play -Navmesh Agent Prototype -Back To Top - - -Creating Agents With Components In Code -Alternatively agent entities can be assembled in code. - -Initially the entity requires a Transform2D or Transform3D component and adding a View component will make it have a prefab rendered in the scene. - -The most important component is the NavMeshPathfinder. It performs path-finding, stores the target position and a user-defined number of waypoints and detects the waypoint progression. This component needs to be created over the NavMeshPathfinder.Create() Factory method passing in a NavMeshAgentConfig. - -The NavMeshSteeringAgent component is optional and requires a NavMeshPathfinder. It has max speed, acceleration and rotation speed variables that can be changed during run-time and it steers the entity along the path. Apart from not using this component developers can change the MovementType to Callback and inject their own movement while having up-to-date avoidance data. Disable rotation speed and acceleration by setting them to 0. - -The NavMeshAvoidanceAgent requires both the NavMeshPathfinder and the NavMeshSteeringAgent components which need to be Set() on an entity prior to this component. This agent performs avoidance computations to avoid other moving agents (HRVO) by using priorities and filtering with masks and layers. Initially set by the NavMeshAgentConfig priority, mask and layer can be changed during run-time on the component. - -If you want the agent to be steered by a physics body, which could for example prevent the agent from penetrating static collision, the entity requires a PhysicsCollider2D/3D and a PhysicsBody2D. To enable this you need to set the MovementType to DynamicBody in its NavMeshAgentConfig. - -public override void OnInit(Frame f) { - base.OnInit(f); - - var entity = f.Create(); - f.Set(entity, new Transform3D() { Position = FPVector3.Zero, Rotation = FPQuaternion.Identity }); - var config = f.FindAsset(NavMeshAgentConfig.DEFAULT_ID); - var pathfinder = NavMeshPathfinder.Create(f, entity, config); - - // find a random point to move to - var navmesh = f.Map.NavMeshes["Navmesh"]; - if (navmesh.FindRandomPointOnNavmesh(FPVector2.Zero, FP._10, f.RNG, *f.NavMeshRegionMask, out FPVector2 randomPoint)) { - pathfinder.SetTarget(f, randomPoint, navmesh); - } - - f.Set(entity, pathfinder); - f.Set(entity, new NavMeshSteeringAgent()); -} -Activate the NavMesh Agent Gizmos Draw Nav Mesh Agents to enable the agent gizmo drawing in the scene windows. - -Back To Top - - -Important Agent Settings - -Pathfinder -NavMeshPathfinder.SetConfig() can be executed during the component creation and during run-time. If the agent is currently following a path and the waypoint count from the new config is different the path is reset. The config is automatically updated on the NavMeshSteeringAgent and NavMeshAvoidanceAgent components of the entity and values for Speed, Acceleration, AvoidancePriority, Layer and Mask are reset to the config values. - -NavMeshAgentConfig.MaxRepathTimeout is the time in seconds that will trigger a agent path-finding when a waypoint is not reached in this time. This is more of a fail-safe to mitigate stuck agents. Set the value to 0 to disable. - -NavMeshAgentConfig.LineOfSightFunneling should be activated when navmesh regions are used that are located inside the middle of the main navmesh. For example building that can be destroyed. The extra triangles introduced by the regions can sometimes result is slightly odd paths near active regions. This option will remove unnecessary waypoint near the regions. - -NavMeshAgentConfig.DynamicLineOfSight makes the agent check if waypoints can be skipped each tick. This option is costly but will remove any unnecessary waypoints on its path. - -If NavMeshAgentConfig.DynamicLineOfSightWaypointRange is set on the other hand the line of sight check is executed each tick only when close to a waypoint (range). This works without DynamicLineOfSight being enabled. - -NavMeshAgentConfig.FindValidTargetCellRange is helping when during SetTarget() a position outside the navmesh is chosen. The range parameter will search neighbouring cells for an optimal replacement of the target that is inside the navmesh. - -Consider the target marked with a yellow x in the following image. The cells searched for a specific cell range are marked with numbers. Cell range 0 for example will fail to find a destination. Range 1 on the other hand would find the closes position on the navmesh as depicted with the yellow dotted line. Be aware the increasing the cell range to unreasonable high numbers will have a big impact on the performance. - -Navmesh Agent Waypoint Reached Detection Axis -Set the number of waypoints that are cached on the NavMeshPathfinder using NavMeshAgentConfig.CachedWaypointCount. Remember that storing more non-transient data will slow down the simulation. The first waypoint stored in the cache is the current position the agent has when SetTarget() was called and is used to enhance the waypoint reached detection. When the agent starts to steer towards the last waypoint it will automatically run path-finding again to mitigate situations where the agent does not have a valid waypoint when calculating a frame. - -Enable the waypoint reached detection help NavMeshAgentConfig.EnableWaypointDetection when you notice agents are having trouble reaching waypoints (for example due to slow rotation speed or avoidance). The subsequent parameter Axis Extend and Axis Offset are defining the waypoint reached detection axis (black line). If an agents enters the yellow zone, the waypoint is considered to be reached. - -Navmesh Agent Waypoint Reached Detection Axis -Waypoint Reached Detection Axis -If the pathfinder component has no accompanied steering component DefaultWaypointDetectionDistance is used to perform waypoint reached detection and should set to the agent max speed * delta time. See the "Useful Navmesh Agent Configurations" section how to enhance the waypoint reached detection. - -Back To Top - - -Steering Agent -NavMeshAgentConfig.StoppingDistance and AutoBraking are applied at the agent that is approaching the final target. StoppingDistance is the absolute distance that the agent stops in front of the destination, setting this value helps the agent to stabilize and not overshoot. The agent always stops when the remaining distance is less then the agents current movement distance per tick. - -AutoBraking is more of a visual feature that slows the agent down before reaching the destination and also can be used to stabilize the agent stopping behavior. The AutoBrakingDistance defines the radius around the destination in which the agent starts to slow down. Internally a square root is used to smooth the braking. - -If the navmesh agent is not repelled by geometry using the MovementType PhysicsBody, especially when using avoidance, the agent will move outside the navmesh. To completely prevent this and make the agent slide along the borders of the navmesh enable NavMeshAgentConfig.ClampAgentToNavmesh. - -Agents can potentially have a bigger radius then the MinAgentRadius of the navmesh. Quantum supports this by moving the agent waypoints further away from the border but this makes clamping the agent to the navmesh much more complicated and the parameter ClampAgentToNavmeshRadiusThreshold helps to chose which technique should be chosen. Increase the radius when smaller agents tend to move outside the navmesh. - -To stabilize the correction the agent is only moved a percentage (ClampAgentToNavmeshCorrection) of its whole penetration depth. - -Back To Top - - -Update Interval -For performance optimization reasons each individual agent (config) can be configured to run path-finding and avoidance not every simulation tick. Set NavMeshAgentConfig.UdpateInterval to a value higher than 1 to reduce the amount of updates it gets. This will make the agent less responsive but also saves CPU time. The agent entity index is used to define the exact tick to update, so not all entities are updated at the same tick. - -The formula is: - -updateAgent = entity.Index % agentConfig.UpdateInterval == f.Number % agentConfig.UpdateInterval -1 = update every tick -2 = update every other tick -8 = update every 8th tick, etc -Back To Top - - -Using Navmesh Agent Callbacks -All callbacks from the agent are called from the main thread and do not cause multi-threading issues when accessing an writing other components and entities. - -Navigation agent callbacks have to be opted in. Open your simulation config and toggle Enable Navigation Callbacks. - -Simulation Config -Enable Navigation Agent Callbacks in the Simulation Config -The following signals will provide imminent feedback that can be used to further control the agent. - -namespace Quantum { - public unsafe partial class NavMeshAgentTestSystem : SystemMainThread, - ISignalOnNavMeshSearchFailed, - ISignalOnNavMeshWaypointReached, - ISignalOnNavMeshMoveAgent { - } -} -ISignalOnNavMeshSearchFailed is called when the agent could not create a path between its current position and the target set in SetTarget(). For example the destination cannot be matched to the navmesh. Set the resetAgent parameter to false when you run SetTarget() during this callback. - -ISignalOnNavMeshWaypointReached is called when the agent reached a waypoint on its path to the target. Check out the WaypointFlags enum for more information about the waypoint: Target, LinkStart, LinkEnd. - -ISignalOnNavMeshMoveAgent is only called when the NavMeshAgentConfig.MovementType is set to Callback and the agent has a NavMeshSteeringAgent component. The desiredDirection parameter is the normalized direction that the internal steering and avoidance thinks the agent movement vector should be. - -public void OnNavMeshMoveAgent(Frame f, EntityRef entity, FPVector2 desiredDirection) { - var agent = f.Unsafe.GetPointer(entity); - - // simple demonstration how to move the agent. - if (f.Has(entity)) { - var transform = f.Unsafe.GetPointer(entity); - transform->Position.X.RawValue = transform->Position.X.RawValue + ((desiredDirection.X.RawValue * f.DeltaTime.RawValue) >> FPLut.PRECISION); - transform->Position.Y.RawValue = transform->Position.Y.RawValue + ((desiredDirection.Y.RawValue * f.DeltaTime.RawValue) >> FPLut.PRECISION); - transform->Rotation = FPVector2.RadiansSignedSkipNormalize(FPVector2.Up, desiredDirection); - } else if (f.Has(entity)) { - var transform = f.Unsafe.GetPointer(entity); - transform->Position.X.RawValue = transform->Position.X.RawValue + ((desiredDirection.X.RawValue * f.DeltaTime.RawValue) >> FPLut.PRECISION); - transform->Position.Z.RawValue = transform->Position.Z.RawValue + ((desiredDirection.Y.RawValue * f.DeltaTime.RawValue) >> FPLut.PRECISION); - var desiredRotation = FPVector2.RadiansSignedSkipNormalize(FPVector2.Up, desiredDirection); - transform->Rotation = FPQuaternion.AngleAxis(desiredRotation * FP.Rad2Deg, -FPVector3.Up); - } -} -Back To Top - - -Common Navmesh Agent Setups -Path-find Only - -Use the MapNavMeshPathfinder component to perform the multi-threaded path-finding, storing the target and waypoints and perform the waypoint index progression. Control the steering, avoidance and movement yourself in your own system. - -For the waypoint progression to work the pathfinder component requires information about how fast it is approaching the waypoint. Set the WaypointDetectionDistanceSqr property each frame. - -No Avoidance - -Only use Pathfinder and SteeringAgent components. No avoidance code will be executed and the components do not store any avoidance relevant data. Toggle off SimulationConfig.Navigation.EnableAvoidance to save CPU time. - -Custom Movement But With Quantum Avoidance - -Use all three components (Pathfinder, SteeringAgent and AvoidanceAgent). The AvoidanceAgents depends on parts of the SteeringAgent although you want to override it. In the NavMeshAgentConfig set the MovementType to Callback and implement the ISignalOnNavMeshMoveAgent signal (see previous section). The desiredDirection parameter includes the avoidance altered movement direction. \ No newline at end of file diff --git a/data/nav-agent-avoidance.txt b/data/nav-agent-avoidance.txt deleted file mode 100644 index 770e42ba4b7b9834e1ff8fa7a601a5326f91220a..0000000000000000000000000000000000000000 --- a/data/nav-agent-avoidance.txt +++ /dev/null @@ -1,77 +0,0 @@ -Agent Avoidance -Quantum implements a variation of the collision avoidance technique called Hybrid Reciprocal Velocity Obstacles. Here is an article that gives a glimpse at what is going on under the hood: Reciprocal Collision Avoidance and Navigation for Video Games. - -Navmesh Agent Prototype -Quantum Agent Avoidance -Setting Up Avoidance Agents -Setting Up Avoidance Obstacles -Jittering Agents - -Setting Up Avoidance Agents -Open and review your simulation config: AvoidanceRange will be crucial to the quality and performance cost of the avoidance system. It defines the range in which agents start to influence each other. The range is measured between the radii of two agents. - -MaxAvoidanceCandidates defines the maximum number of avoidance candidates used by each agent. More candidates requires more memory and CPU time but also increase the quality when using lots of agents. First try to reduce the number and see with how little you can actually get away with. The higher the AvoidanceQuality and the higher the amount of agents that are meeting each other the higher this number needs to be. - -Set EnableAvoidance to off, if you want to have pathfinder and steering agents but never use the avoidance. This will optimize the performance because avoidance tasks are not scheduled. This is not required if the NavigationSystem has been removed from SystemSetup.cs completely. - -Simulation Config -To activate avoidance add a NavMeshAvoidanceAgent component to an entity that already has a NavMeshPathfinder and a NavMeshSteeringAgent component. - -Then set up the avoidance section of its NavMeshAgentConfig: - -AvoidanceType.None will have the effect that the agent will not avoid others but others will avoid it. Much like a NavMeshAvoidanceObstacle component. - -Priority works as in Unity. Most important = 0. Least important = 99. Default = 50. Because the avoidance system relies on reciprocity the avoiding-work (who will avoid whom and how much) is always split between the agents. Higher priority agents do only 25% of the work while agents of the the same priority split the work 50/50. - -Use the Unity layers and set AvoidanceLayer and AvoidanceMask to filter agents. For example set some agents to be on the Heroes layer while others are on the Minions layer and set the mask for Heroes to ignore Minions. - -Toggle OverrideRadiusForAvoidance if you want a different Radius for avoidance than the one used for path-finding and steering. - -Solving avoidance while also trying to follow waypoints to steer around corners or through narrow passages can be hard. To mitigate the problem and to accept visual overlapping in favor of agents blocking each other toggle ReduceAvoidanceAtWaypoints. The avoidance applied when an agent is getting close to a waypoint is reduced. The ReduceAvoidanceFactor value is multiplied with the agent radius and then represents the distance in which the avoidance influence is reduced quadratically. - -Toggle DebugAvoidance to render gizmos into the scene view. The avoidance agent on the top is avoiding the moving agent on his right and the obstacle on his left. The red cones are the Velocity Obstacles of the agent on the top and green lines are the candidates while finally the white dot is the chosen candidate. The VO of a stationary object is truncated (see Navigation.Constants.VelocityObstacleTruncationFactor to play with that). - -Avoidance Agent Debug Gizmos -Back To Top - - -Setting Up Avoidance Obstacles -Avoidance Obstacles are static or moving entities that influence the avoidance behavior of Navmesh agents but are no agents themselves. They do not influence the path finder and should not be used to block parts of the game level. - -A NavMeshAvoidanceObstacle component requires a Transform2/3D component to work properly. - -If the entity that has a NavMeshAvoidanceObstacle component is moving, other agents require its velocity information to predict its future position. Be sure to regularly update the property NavMeshAvoidanceObstacle.Velocity manually. - -Set up an Avoidance Obstacle: - -as an EntityPrototype component in Unity: -Avoidance Obstacle Prototype -or as a Quantum component in source code: -var c = f.Create(); -f.Set(c, new Transform2D { Position = position }); -var obstacle = new NavMeshAvoidanceObstacle(); -obstacle.AvoidanceLayer = 0; -obstacle.Radius = FP._0_50; -obstacle.Velocity = FPVector2.Up; -f.Set(c, obstacle); -Back To Top - - -Jittering Agents -Solving avoidance with multiple agents moving different direction can cause the agents to switch their target direction very rapidly until finding a good course. To mitigate this the Angular Speed of the agents can be tuned down or additional smoothing is applied in the view by overriding the EntityView like this. The blending math is just one proposal. - -using UnityEngine; - -public class SmoothRotationEntityView : EntityView { - private Quaternion rotation; - public float Blending; - - protected override void ApplyTransform(ref UpdatePostionParameter param) { - // Override this in subclass to change how the new position is applied to the transform. - transform.position = param.NewPosition + param.ErrorVisualVector; - - // Unity's quaternion multiplication is equivalent to applying rhs then lhs (despite their doc saying the opposite) - rotation = param.ErrorVisualQuaternion * param.NewRotation; - transform.rotation = Quaternion.Lerp(transform.rotation, rotation, Time.deltaTime * Blending); - } -} \ No newline at end of file diff --git a/data/nav-creating mesh links.txt b/data/nav-creating mesh links.txt deleted file mode 100644 index b6c543e47e9c27cba02f70ab9fcec9bd1824f489..0000000000000000000000000000000000000000 --- a/data/nav-creating mesh links.txt +++ /dev/null @@ -1,48 +0,0 @@ -Using Navmesh Off Mesh Links -Quantum exports the Unity Off Mesh links into its own data structure and gives minimal support to work with navmesh links. - -Creating Navmesh Links -Toggle Navmesh Links -Hooking-In Gameplay -Link Traversing Exception - -Creating Navmesh Links -Create a Unity Off Mesh Link. Quantum ignores the Activated, Auto Update Positions and Navigation Area properties. -Off Mesh Link Setup -Bake the map and check the resulting link using the MapNavMeshDebugDrawer script. The links are rendered as blue arrows. -Off Mesh Link Debug -The agent now already automatically uses the navmesh link during its path-finding. -Off Mesh Link Path Gizmo -Back To Top - - -Toggle Navmesh Links -Links can be toggled on and off and restrict what agents can use them by using Quantum navmesh regions. Attach a MapNavMeshRegion script to the Off Mesh Link set the Id and Cast Region to No Region. - -Off Mesh Link Regions -Back To Top - - -Hooking-In Gameplay -With no alteration the agent will traverse the link with its normal speed. You can take over control of the agent when the link has been reached by listening to the ISignalOnNavMeshWaypointReached signal. Then either disable the agent until your animation has completed or override the movement code in the ISignalOnNavMeshMoveAgent signal (this requires a change of the navmesh config to toggle the MovementType to Callback). - -This code sample performs a teleport when stepping on the link start waypoint. - -public void OnNavMeshWaypointReached(Frame f, EntityRef entity, FPVector2 waypoint, Navigation.WaypointFlag waypointFlags, ref bool resetAgent) { - var agent = f.Get(entity); - var waypointIndex = agent.WaypointIndex; - // btw HasFlag() is convenient but slow - if ((waypointFlags & Navigation.WaypointFlag.LinkStart) == Navigation.WaypointFlag.LinkStart) { - // we can be pretty sure that there always is a next waypoint for a link start - var linkDestination = agent.GetWaypoint(f, waypointIndex + 1); - f.Unsafe.GetPointer(entity)->Position = linkDestination; - } -} -Back To Top - - -Link Traversing Exception -Query if an agent is currently traversing a link by using NavMeshPathfinder.IsOnLink(FrameBase). -When setting a new target while the agent is traversing a link the agent will finish the current link before executing the path-finding. This is done by setting the WaypointFlag.RepathWhenReached. -If the waypoint before the last waypoint is a LinkStart a re-path is triggered. This helps to prematurely mitigate problems with running a re-path when the link start waypoint has already been reached. -No automatic re-pathing (NavMeshAgentConfig.MaxRepathTimeout) will be executed as long as the agent is traversing a link.. \ No newline at end of file diff --git a/data/nav-creating navmesh.txt b/data/nav-creating navmesh.txt deleted file mode 100644 index ba67ff221dfbc95f14df224df0d8bed8303ccfd8..0000000000000000000000000000000000000000 --- a/data/nav-creating navmesh.txt +++ /dev/null @@ -1,178 +0,0 @@ -Creating Navmesh Agents -Since Quantum 2.0 navmesh agents are split into multiple components. We noticed that developers working with navmesh and steering want to control the final movement result, which makes a lot of sense, because it often is so vital to the game experience. The new navmesh agent parts should help developers to pick a combination of navmesh support without losing multi-threaded performance and without executing unneeded parts or wasting unneeded memory. - -Agent components are NavMeshPathfinder, NavMeshSteeringAgent and NavMeshAvoidanceAgent. A stand-alone component is NavMeshAvoidanceObstacle. - -Agent entities can be created in two ways: using Entity Prototypes in Unity or assembling the entity in code. They still use the NavMeshAgentConfig Quantum asset. - -Creating Agents With Entity Prototypes In Unity -Creating Agents With Components In Code -Important Agent Settings -Pathfinder -Steering Agent -Update Interval -Using Navmesh Agent Callbacks -Common Navmesh Agent Setups - -Creating Agents With Entity Prototypes In Unity -Create an empty Quantum prototype via the Unity menu: GameObject/Quantum/Empty Entity -Select the entity and set Transform to 2D -Toggle NavMeshPathfinder component -Select the default NavMeshAgentConfig -Toggle Initial Target and select a transform from the Unity scene to provide an initial position to move to -Select the baked Quantum navmesh (see Navmesh workflow) -Toggle on NavMeshSteeringAgent -To see the path gizmos either: -Activate Show Debug Steering on the default NavMeshAgentConfig or -Activate the Navmesh Gizmo Draw Pathfinder Funnel in QuantumEditorSettings -Press play -Navmesh Agent Prototype -Back To Top - - -Creating Agents With Components In Code -Alternatively agent entities can be assembled in code. - -Initially the entity requires a Transform2D or Transform3D component and adding a View component will make it have a prefab rendered in the scene. - -The most important component is the NavMeshPathfinder. It performs path-finding, stores the target position and a user-defined number of waypoints and detects the waypoint progression. This component needs to be created over the NavMeshPathfinder.Create() Factory method passing in a NavMeshAgentConfig. - -The NavMeshSteeringAgent component is optional and requires a NavMeshPathfinder. It has max speed, acceleration and rotation speed variables that can be changed during run-time and it steers the entity along the path. Apart from not using this component developers can change the MovementType to Callback and inject their own movement while having up-to-date avoidance data. Disable rotation speed and acceleration by setting them to 0. - -The NavMeshAvoidanceAgent requires both the NavMeshPathfinder and the NavMeshSteeringAgent components which need to be Set() on an entity prior to this component. This agent performs avoidance computations to avoid other moving agents (HRVO) by using priorities and filtering with masks and layers. Initially set by the NavMeshAgentConfig priority, mask and layer can be changed during run-time on the component. - -If you want the agent to be steered by a physics body, which could for example prevent the agent from penetrating static collision, the entity requires a PhysicsCollider2D/3D and a PhysicsBody2D. To enable this you need to set the MovementType to DynamicBody in its NavMeshAgentConfig. - -public override void OnInit(Frame f) { - base.OnInit(f); - - var entity = f.Create(); - f.Set(entity, new Transform3D() { Position = FPVector3.Zero, Rotation = FPQuaternion.Identity }); - var config = f.FindAsset(NavMeshAgentConfig.DEFAULT_ID); - var pathfinder = NavMeshPathfinder.Create(f, entity, config); - - // find a random point to move to - var navmesh = f.Map.NavMeshes["Navmesh"]; - if (navmesh.FindRandomPointOnNavmesh(FPVector2.Zero, FP._10, f.RNG, *f.NavMeshRegionMask, out FPVector2 randomPoint)) { - pathfinder.SetTarget(f, randomPoint, navmesh); - } - - f.Set(entity, pathfinder); - f.Set(entity, new NavMeshSteeringAgent()); -} -Activate the NavMesh Agent Gizmos Draw Nav Mesh Agents to enable the agent gizmo drawing in the scene windows. - -Back To Top - - -Important Agent Settings - -Pathfinder -NavMeshPathfinder.SetConfig() can be executed during the component creation and during run-time. If the agent is currently following a path and the waypoint count from the new config is different the path is reset. The config is automatically updated on the NavMeshSteeringAgent and NavMeshAvoidanceAgent components of the entity and values for Speed, Acceleration, AvoidancePriority, Layer and Mask are reset to the config values. - -NavMeshAgentConfig.MaxRepathTimeout is the time in seconds that will trigger a agent path-finding when a waypoint is not reached in this time. This is more of a fail-safe to mitigate stuck agents. Set the value to 0 to disable. - -NavMeshAgentConfig.LineOfSightFunneling should be activated when navmesh regions are used that are located inside the middle of the main navmesh. For example building that can be destroyed. The extra triangles introduced by the regions can sometimes result is slightly odd paths near active regions. This option will remove unnecessary waypoint near the regions. - -NavMeshAgentConfig.DynamicLineOfSight makes the agent check if waypoints can be skipped each tick. This option is costly but will remove any unnecessary waypoints on its path. - -If NavMeshAgentConfig.DynamicLineOfSightWaypointRange is set on the other hand the line of sight check is executed each tick only when close to a waypoint (range). This works without DynamicLineOfSight being enabled. - -NavMeshAgentConfig.FindValidTargetCellRange is helping when during SetTarget() a position outside the navmesh is chosen. The range parameter will search neighbouring cells for an optimal replacement of the target that is inside the navmesh. - -Consider the target marked with a yellow x in the following image. The cells searched for a specific cell range are marked with numbers. Cell range 0 for example will fail to find a destination. Range 1 on the other hand would find the closes position on the navmesh as depicted with the yellow dotted line. Be aware the increasing the cell range to unreasonable high numbers will have a big impact on the performance. - -Navmesh Agent Waypoint Reached Detection Axis -Set the number of waypoints that are cached on the NavMeshPathfinder using NavMeshAgentConfig.CachedWaypointCount. Remember that storing more non-transient data will slow down the simulation. The first waypoint stored in the cache is the current position the agent has when SetTarget() was called and is used to enhance the waypoint reached detection. When the agent starts to steer towards the last waypoint it will automatically run path-finding again to mitigate situations where the agent does not have a valid waypoint when calculating a frame. - -Enable the waypoint reached detection help NavMeshAgentConfig.EnableWaypointDetection when you notice agents are having trouble reaching waypoints (for example due to slow rotation speed or avoidance). The subsequent parameter Axis Extend and Axis Offset are defining the waypoint reached detection axis (black line). If an agents enters the yellow zone, the waypoint is considered to be reached. - -Navmesh Agent Waypoint Reached Detection Axis -Waypoint Reached Detection Axis -If the pathfinder component has no accompanied steering component DefaultWaypointDetectionDistance is used to perform waypoint reached detection and should set to the agent max speed * delta time. See the "Useful Navmesh Agent Configurations" section how to enhance the waypoint reached detection. - -Back To Top - - -Steering Agent -NavMeshAgentConfig.StoppingDistance and AutoBraking are applied at the agent that is approaching the final target. StoppingDistance is the absolute distance that the agent stops in front of the destination, setting this value helps the agent to stabilize and not overshoot. The agent always stops when the remaining distance is less then the agents current movement distance per tick. - -AutoBraking is more of a visual feature that slows the agent down before reaching the destination and also can be used to stabilize the agent stopping behavior. The AutoBrakingDistance defines the radius around the destination in which the agent starts to slow down. Internally a square root is used to smooth the braking. - -If the navmesh agent is not repelled by geometry using the MovementType PhysicsBody, especially when using avoidance, the agent will move outside the navmesh. To completely prevent this and make the agent slide along the borders of the navmesh enable NavMeshAgentConfig.ClampAgentToNavmesh. - -Agents can potentially have a bigger radius then the MinAgentRadius of the navmesh. Quantum supports this by moving the agent waypoints further away from the border but this makes clamping the agent to the navmesh much more complicated and the parameter ClampAgentToNavmeshRadiusThreshold helps to chose which technique should be chosen. Increase the radius when smaller agents tend to move outside the navmesh. - -To stabilize the correction the agent is only moved a percentage (ClampAgentToNavmeshCorrection) of its whole penetration depth. - -Back To Top - - -Update Interval -For performance optimization reasons each individual agent (config) can be configured to run path-finding and avoidance not every simulation tick. Set NavMeshAgentConfig.UdpateInterval to a value higher than 1 to reduce the amount of updates it gets. This will make the agent less responsive but also saves CPU time. The agent entity index is used to define the exact tick to update, so not all entities are updated at the same tick. - -The formula is: - -updateAgent = entity.Index % agentConfig.UpdateInterval == f.Number % agentConfig.UpdateInterval -1 = update every tick -2 = update every other tick -8 = update every 8th tick, etc -Back To Top - - -Using Navmesh Agent Callbacks -All callbacks from the agent are called from the main thread and do not cause multi-threading issues when accessing an writing other components and entities. - -Navigation agent callbacks have to be opted in. Open your simulation config and toggle Enable Navigation Callbacks. - -Simulation Config -Enable Navigation Agent Callbacks in the Simulation Config -The following signals will provide imminent feedback that can be used to further control the agent. - -namespace Quantum { - public unsafe partial class NavMeshAgentTestSystem : SystemMainThread, - ISignalOnNavMeshSearchFailed, - ISignalOnNavMeshWaypointReached, - ISignalOnNavMeshMoveAgent { - } -} -ISignalOnNavMeshSearchFailed is called when the agent could not create a path between its current position and the target set in SetTarget(). For example the destination cannot be matched to the navmesh. Set the resetAgent parameter to false when you run SetTarget() during this callback. - -ISignalOnNavMeshWaypointReached is called when the agent reached a waypoint on its path to the target. Check out the WaypointFlags enum for more information about the waypoint: Target, LinkStart, LinkEnd. - -ISignalOnNavMeshMoveAgent is only called when the NavMeshAgentConfig.MovementType is set to Callback and the agent has a NavMeshSteeringAgent component. The desiredDirection parameter is the normalized direction that the internal steering and avoidance thinks the agent movement vector should be. - -public void OnNavMeshMoveAgent(Frame f, EntityRef entity, FPVector2 desiredDirection) { - var agent = f.Unsafe.GetPointer(entity); - - // simple demonstration how to move the agent. - if (f.Has(entity)) { - var transform = f.Unsafe.GetPointer(entity); - transform->Position.X.RawValue = transform->Position.X.RawValue + ((desiredDirection.X.RawValue * f.DeltaTime.RawValue) >> FPLut.PRECISION); - transform->Position.Y.RawValue = transform->Position.Y.RawValue + ((desiredDirection.Y.RawValue * f.DeltaTime.RawValue) >> FPLut.PRECISION); - transform->Rotation = FPVector2.RadiansSignedSkipNormalize(FPVector2.Up, desiredDirection); - } else if (f.Has(entity)) { - var transform = f.Unsafe.GetPointer(entity); - transform->Position.X.RawValue = transform->Position.X.RawValue + ((desiredDirection.X.RawValue * f.DeltaTime.RawValue) >> FPLut.PRECISION); - transform->Position.Z.RawValue = transform->Position.Z.RawValue + ((desiredDirection.Y.RawValue * f.DeltaTime.RawValue) >> FPLut.PRECISION); - var desiredRotation = FPVector2.RadiansSignedSkipNormalize(FPVector2.Up, desiredDirection); - transform->Rotation = FPQuaternion.AngleAxis(desiredRotation * FP.Rad2Deg, -FPVector3.Up); - } -} -Back To Top - - -Common Navmesh Agent Setups -Path-find Only - -Use the MapNavMeshPathfinder component to perform the multi-threaded path-finding, storing the target and waypoints and perform the waypoint index progression. Control the steering, avoidance and movement yourself in your own system. - -For the waypoint progression to work the pathfinder component requires information about how fast it is approaching the waypoint. Set the WaypointDetectionDistanceSqr property each frame. - -No Avoidance - -Only use Pathfinder and SteeringAgent components. No avoidance code will be executed and the components do not store any avoidance relevant data. Toggle off SimulationConfig.Navigation.EnableAvoidance to save CPU time. - -Custom Movement But With Quantum Avoidance - -Use all three components (Pathfinder, SteeringAgent and AvoidanceAgent). The AvoidanceAgents depends on parts of the SteeringAgent although you want to override it. In the NavMeshAgentConfig set the MovementType to Callback and implement the ISignalOnNavMeshMoveAgent signal (see previous section). The desiredDirection parameter includes the avoidance altered movement direction. \ No newline at end of file diff --git a/data/nav-custom navmesh.txt b/data/nav-custom navmesh.txt deleted file mode 100644 index ec942ab997976575d10e3afb588a44f24db979cd..0000000000000000000000000000000000000000 --- a/data/nav-custom navmesh.txt +++ /dev/null @@ -1,235 +0,0 @@ -Generating BakeData -The recommended process is to generate the intermediate navmesh format MapNavMesh.BakeData and run this through MapNavMeshBaker.BakeNavMesh(MapData data, MapNavMesh.BakeData navmeshBakeData) which uses the triangle information from the BakeData to fill out all required data structures of a Quantum navmesh. - -MapNavMeshBaker.BakeNavMesh() was developed to be used during edit time and replacing it entirely could yield performance improvements, but also would be a much more elaborate task. - -Back To Top - - -MapNavMesh.BakeData Class -Type Field Description -String Name The name of the navmesh accessible inside the simulation by f.Map.NavMeshes[name] -Vector3 Position The position of the navmesh. Final navmesh vertices are stored in global space and their positions are translated by this during baking. -FP AgentRadius The radius of the largest agents that the navmesh is created for. Older versions of Quantum were permitting different agent radii, but that has been abolished. Now, agents can walk up until their pivot is on the edge of the navmesh. This way the margin agents should keep away from walls is baked into the triangles. This value is only used to render debug graphics. -List Regions All regions ids that are used in this navmesh. During baking the region ids will be added to the Region list of the Map asset and their index is baked into the navmesh triangles region mask (NavMeshTriangle.Regions). The regions are aggregated on the map because a map can have multiple navmeshes that share the region ids. -MapNavMeshVertex[] Vertices The vertices of the navmesh. -MapNavMeshTriangle[] Triangles The triangle of the navmesh. This is a regular mesh data structure where the triangles and vertices are kept in two separate arrays and the triangle points into the vertex array to mark their 3 vertices. -MapNavMeshLink[] Links Link between positions on the same navmesh. -enum ClosestTriangleCalculation The Quantum navmesh uses a grid for spatial partitioning. Each grid cell will have a fallback triangle assigned. The default search is quite slow (BruteForce) while SpiralOut more efficient is but it could result in empty fallback triangles. -int ClosestTriangleCalculationDepth The number of grid cells to expand the SpiralOut search. -bool EnableQuantum_XY When enabled the navmesh baking will flip Y and Z components of the vertex positions to support navmeshes generated in the XY plane. -bool LinkErrorCorrection Automatically correct navmesh link positions to the closest triangle during baking. -Back To Top - - -MapNavMeshTriangle Class -Triangles are expected to have clock-wise winding order. Not all fields have to be filled out. Some of them are only needed for the legacy navmesh drawing tool. - -Type Field Description -String Id Not required -String[] VertexIds Must have length of 3. The referenced vertices as ids. Required for SDK 2.1. or earlier. -Int32[] VertexIds2 Must have length of 3. The referenced vertices as indices into the vertex array. Required for SDK 2.2. -Int32 Area Not required -String RegionId The region that this triangle belongs to. Default is null. -FP Cost he cost of the triangle. Default should be FP._1. -Back To Top - - -MapNavMeshVertex Class -The types of Position has been replaced by FPVector3 SDK 2.2. - -Type Field Description -String Id Required for SDK 2.1 or earlier -Vector3 Position The position of the vertex -List Neighbors Not required -List Triangles Not required -Back To Top - - -MapNavMeshLink Class -The types of Start, End and CostOveride have been replaced by FPVector3 and FP respectively in SDK 2.2. - -Type Field Description -Vector3 Start Start position of the link. Must be on the same navmesh. -Vector3 End End position of the link. Must be on the same navmesh. -bool Bidirectional Can the link be traversed from both directions. -float CostOverride The cost of the connection. -String RegionId The region id that the link belongs to. Default is null. -String Name The name of the link. Can be queried by navmesh.Links[NavMeshPathfinder.CurrentLink()].Name. -Back To Top - - -Snippet -// Generate simple navmesh BakeData -var bakeData = new MapNavMesh.BakeData() { - AgentRadius = FP._0_20, - ClosestTriangleCalculation = MapNavMesh.FindClosestTriangleCalculation.SpiralOut, - ClosestTriangleCalculationDepth = 1, - Name = "DynamicNavmesh", - PositionFP = FPVector3.Zero, - Regions = new System.Collections.Generic.List(), - Vertices = new MapNavMeshVertexFP[] { - new MapNavMeshVertexFP { Position = FPVector3.Forward }, - new MapNavMeshVertexFP { Position = FPVector3.Right }, - new MapNavMeshVertexFP { Position = -FPVector3.Forward}, - new MapNavMeshVertexFP { Position = -FPVector3.Right}, - }, - Triangles = new MapNavMeshTriangle[] { - new MapNavMeshTriangle { VertexIds2 = new int[] { 0, 1, 2}, Cost = FP._1 }, - new MapNavMeshTriangle { VertexIds2 = new int[] { 0, 2, 3}, Cost = FP._1 } - } -}; -Back To Top - - -Replacing Navmesh Assets Before Starting The Simulation -Replacing the content of an existing Unity Quantum navmesh asset before starting the simulation. All clients and late-joiners have to perform this. - -Requires: - -navmesh-deterministic-baking branch for SDK 2.1 (please contact us) -The BakeData generation is deterministic -Replacing the asset must be done before the navmesh is loaded through the UnityDB and before the simulation has started. In this snippet the Unity navmesh asset is loaded to replace the Quantum asset inside it (.Settings) and the Guid and Path values are copied. Invalidating the .DataAsset will prevent the deserialization of the binary navmesh asset (_data asset) when it is finally loaded by Quantum. - -var navmesh = MapNavMeshBaker.BakeNavMesh(mapdata.Asset.Settings, bakeData, null); -var navmeshAsset = UnityEngine.Resources.Load("PathToNavmeshAsset"); -navmesh.Guid = navmeshAsset.Settings.Guid; -navmesh.Path = navmeshAsset.Settings.Path; -navmeshAsset.Settings = navmesh; -navmeshAsset.Settings.DataAsset.Id = AssetGuid.Invalid; - -// QuantumRunner.StartGame() -Back To Top - - -Injecting Navmeshes During Runtime - -Restrictions By The Map Asset -The Map asset carries two look up for convenient navmesh and Region name lookups that are populated when the map and associated navmeshes is loaded. Both lookups will not work with dynamic navmesh assets. - -public Dictionary NavMeshes; -public Dictionary RegionMap; -public NavMesh GetNavMesh(String name) {} -Back To Top - - -GetNavMesh() Alternative -The navmesh lookup in Map (f.Map.GetNavMesh(name)) cannot be used for dynamic navmeshes because modifying the dictionary on Map does not work for late-joiner or reconnecting players. Instead use this snippet to search for all navmeshes: - -public static NavMesh FindNavmeshByName(Frame f, string name) { - var result = f.Map.GetNavMesh(name); - if (result != null) { - return result; - } - - foreach (var a in f.DynamicAssetDB.Assets) { - if (a is NavMesh navmeshAsset) { - if (navmeshAsset.Name == name) { - return navmeshAsset; - } - } - } - - return null; -} -Alternatively create and manage a navmesh lookup saved on f.Globals: - -global { - dictionary, asset_ref> Navmeshes; -} -Back To Top - - -Using Regions On Dynamic Navmeshes -If a dynamic navmesh has regions itself it has to reuse all regions loaded by the static map and other dynamic regions. If it has new regions they are not allowed to use the same regions ids already used. Toggling would not work properly. - -Best to create one static RegionMap offline and use it inside dynamic generated navmeshes. The RegionMap is created during Map.Loaded() from the maps Region member. - -public string[] Regions; -Back To Top - - -Injecting Navmeshes Inside The Simulation -Deterministically create NavmeshBakeData and bake a Quantum navmesh inside the simulation during runtime. Uses Quantum dynamic database. - -Requires: - -navmesh-deterministic-baking branch for SDK 2.1 (please contact us) -NavmeshBakeData creation needs to be deterministic -Must be performed during a verified frame -The navmesh baking code has been moved (copied in 2.1) to the quantum.code project to be able to run outside Unity. - -using Quantum.Experimental; - -// May be more buffer required for serialization -private static byte[] _byteStreamData = new byte[1024 * 1024]; - -// Generate bake data -var bakeData = new NavmeshBakeData() { - AgentRadius = FP._0_20, - ClosestTriangleCalculation = NavMeshBakeDataFindClosestTriangle.SpiralOut, - ClosestTriangleCalculationDepth = 1, - Name = "DynamicNavmesh", - PositionFP = FPVector3.Zero, - Regions = new List(), - Vertices = new Experimental.NavmeshBakeDataVertex[] { - new NavmeshBakeDataVertex { Position = FPVector3.Forward }, - new NavmeshBakeDataVertex { Position = FPVector3.Right }, - new NavmeshBakeDataVertex { Position = -FPVector3.Forward}, - new NavmeshBakeDataVertex { Position = -FPVector3.Right}, - }, - Triangles = new NavmeshBakeDataTriangle[] { - new NavmeshBakeDataTriangle { VertexIds = new int[] { 0, 1, 2}, Cost = FP._1 }, - new NavmeshBakeDataTriangle { VertexIds = new int[] { 0, 2, 3}, Cost = FP._1 } - } -}; - -// Bake navmesh asset -var navmesh = NavmeshBaker.BakeNavMesh(f.Map, bakeData); - -// Create and add binary navmesh data asset (to support late joiners) -var byteStream = new ByteStream(_byteStreamData); -navmesh.Serialize(byteStream, true); -var binaryDataAsset = new BinaryData(); -binaryDataAsset.Data = byteStream.ToArray(); -var binaryDataAssetRef = new AssetRefBinaryData(); -binaryDataAssetRef.Id = f.AddAsset(binaryDataAsset); -navmesh.DataAsset = binaryDataAssetRef; - -// Add navmesh to Dynamic DB -f.AddAsset(navmesh); -Also use the FindNavmeshByName() snippet from the next section to correctly find dynamic navmesh assets by name. - -Back To Top - - -Injecting Navmeshes From Unity -Works for late-joiners. Must be initiated by one client. - -Requires: - -Latest dynamic asset injection addon -Create BakeData in Unity on one client and use the AssetInjection command: - -var map = QuantumRunner.Default.Game.Frames.Verified.Map; -// Bake navmesh -var navmesh = MapNavMeshBaker.BakeNavMesh(map, bakeData, null); -var data = AssetInjectionUtility.SerializeAsset(null, navmesh); -// Adjust PlayerRef 0 -AssetInjectionUtility.InjectAsset(QuantumRunner.Default.Game, 0, bakeData.Name, data); -To render the gizmos of the dynamic navmesh change the following line in QuantumGameGizmos.cs: - -// ################## NavMeshes ################## - -if (editorSettings.DrawNavMesh) { - var listOfNavmeshes = new System.Collections.Generic.List(); - if (editorSettings.DrawNavMesh) { - listOfNavmeshes.AddRange(frame.Map.NavMeshes.Values); - } - if (frame.DynamicAssetDB.IsEmpty == false) { - listOfNavmeshes.AddRange(frame.DynamicAssetDB.Assets.Where(a => a is NavMesh).Select(a => (NavMesh)a).ToList()); - } - foreach (var navmesh in listOfNavmeshes) { - // ... - } -} \ No newline at end of file diff --git a/data/nav-importing a unity.txt b/data/nav-importing a unity.txt deleted file mode 100644 index 1d59284562f8aa70e9b54fe396b124e9d82b5e02..0000000000000000000000000000000000000000 --- a/data/nav-importing a unity.txt +++ /dev/null @@ -1,102 +0,0 @@ -Importing A Unity Navmesh -Setup your Unity scene it generate a Unity Navmesh using either the global Navmesh Baker or the Navmesh Building Components (Navmesh Surfaces) -Create a new GameObject under the map (Quantum MapData script) and add a MapNavMeshUnity script to it. The name of the GameObject will later be the name of the Quantum navmesh. -Create Navmesh Script -Adding the MapNavMeshUnity script to import a Unity navmesh -Select the map and toggle the Bake All Mode to Everything and press Bake All and check the log for errors. We expect a log message similar to: -Imported Unity NavMesh 'Navmesh', cleaned up 1211 vertices, found 7 region(s), found 4 link(s) -Map Baking -Baking the map will import and bake the navmeshes -The Quantum navmesh will show up.. -..under the Quantum map asset NavMeshLinks -..inside your project view next to the map asset file (one Quantum asset file and one binary .bytes file) -Navmesh Project View -The Quantum navmesh files will show up in the project view -To visualize the baked Quantum navmesh add the MapNavMeshDebugDrawer to the navmesh GameObject and link the .bytes file under BinaryAsset. -Navmesh Gizmos -Navmesh gizmos are rendered into the non-running scene using the MapNavMeshDebugDrawer script -To visualize the navmesh during play mode select Draw Nav Mesh in QuantumEditorSettings -All MapNavMeshUnity scripts under the map will be evaluated during map baking. But because the global Unity navmesh baking only produces one navmesh adding multiple navmeshes to the map only makes sense: - -When you are using the surface addon to control multiple navmesh surfaces -When you are drawing the navmeshes manually: use MapNavMeshDefinition instead of MapNavMeshUnity (see Quantum V1 documentation) -Or when you are creating your custom baking logic that enables and disables parts of the map during multiple Unity navmesh baking iterations -Optionally the navmesh baking can be forced to automatically run on every scene saving, playmode change or build event. See Editor Features in QuantumEditorSettings. - -Navmesh Auto Baking -Quantum supports navmeshes being only located in the origin. We encourage that the gameplay takes place close to the origin with reasonable extends because of the precision of the fixed point arithmetic. - -Back To Top - - -Import Settings -Weld Identical Vertices The Unity NavMesh is a collection of non-connected triangles. This option is very important and combines shared vertices. -Weld Vertex Epsilon Don't make the epsilon too small, vertices required to fuse can be missed, also don't make the value too big as it will deform your navmesh. -Delaunay Triangulation This option will post processes the imported Unity navmesh with a Delaunay triangulation to produce more evenly distributed triangles (it reorders long triangles). -Delaunay Triangulation Restrict To Planes On 3D navmeshes the Delaunay triangulation can deform the navmesh on slopes while rearranging the triangles. This behaviour is also noticeable on Unitys navmesh and can affect a game when the navmesh height is used for gameplay (e.g. walking on the Navmesh). Check this option to restrict the triangulation to triangles that lie in the same plane. -Fix Triangles On Edges Imported vertices are sometimes lying on other triangle edges, which leads to unwanted border detection. With this option such triangles are split. -Closest Triangle Calculation Areas in the map grid without a navmesh will need to detect nearest neighbors. This computation is very slow. The SpiralOut option will be much faster but fallback triangles can be null. -Closest Triangle Calculation Depth Number of cells to search triangles into each direction when using SpiralOut. -Enable Quantum_XY Only visible when the QUANTUM_XY define is set. Toggle this on and the navmesh baking will flip Y and Z to support navmeshes generated in the XY plane. -Min Agent Radius The minimum agent radius supported by the navmesh. This value is the margin between the navmesh and a visual border. The value is overwritten by retrieving it from Unity navmesh bake settings (or the surface settings) when baking in the Editor. -Back To Top - - -Using Navmesh Surfaces -Using the Unity navmesh surface addon has benefits: - -Runtime navmesh computation is possible (be aware that this is cannot be done deterministically across clients, the generated navmesh binary data must be send around) -Creating multiple navmeshes is easy -Using the NavMeshModifier script helps to mitigate the Unity navmesh island issue (see Quantum FAQ) -More control over internal settings -You can link multiple surfaces to a Quantum navmesh by adding them to the NavMeshSurfaces list. During the map baking of one navmesh other surfaces in the scene will be temporarily deactivated. - -Navmesh Surfaces -Quantum supports the Unity Navmesh Surface Addon -Back To Top - - -Custom Baking Options -MapNavMeshBaker.BakeNavMesh() is the essential Quantum navmesh baking method and it uses the MapNavMesh.BakeData as input data. In the default configuration the bake data is generated from the imported Unity navmesh triangulation. In a custom setup you can fill that data structure by yourself. It is basically only a triangle soup. - -You can customize the navmesh baking in different ways. - -Adding static code to the baking pipeline by deriving from MapDataBakerCallback: -Implement OnCollectNavMeshBakeData to modify existing or inject new MapNavMesh.BakeData into the pipeline. -Implement OnCollectNavMeshes to modify existing or add new NavMesh objects to be serialized. -Implement OnBeforeBakeNavMesh or OnBakeNavMesh to completely customized the baking or perform pre or post processing. -public abstract class MapDataBakerCallback { - /// - /// Is called before any navmeshes are generated or any bake data is collected. - /// - public virtual void OnBeforeBakeNavMesh(MapData data) { } - - /// - /// Is called during navmesh baking with the current list of bake data retreived from Unity navmeshes flagged for Quantum navmesh baking. - /// Add new BakeData objects to the navMeshBakeData list. - /// - /// Current list of bake data to be baked - public virtual void OnCollectNavMeshBakeData(MapData data, List navMeshBakeData) { } - - /// - /// Is called after navmesh baking before serializing them to assets. - /// Add new NavMesh objects the navmeshes list. - /// - /// Current list of baked navmeshes to be saved to assets. - public virtual void OnCollectNavMeshes(MapData data, List navmeshes) { } - - /// - /// Is called after the navmesh generation has been completed. - /// Navmeshes assets references are stored in data.Asset.Settings.NavMeshLinks. - /// - public virtual void OnBakeNavMesh(MapData data) { } -} -The methods in MapDataBakerCallback are called by reflection during the map baking process. Just fill out the methods in a public class outside any assembly definition. No need to instantiate a GameObject. For more information on the map asset baking pipeline, please refer to the Asset page in the manual. - -Back To Top - - -Visualizing Pathfinding -Activate the Pathfinder Gizmo Draw Pathfinder Funnel on QuantumEditorSettings to see the paths gizmos in the scene view - -Set the Thread Count to 1 in SimulationConfig to make the gizmos work every time because from Unity we only have access to the main thread. \ No newline at end of file diff --git a/data/nav-importing navmesh.txt b/data/nav-importing navmesh.txt deleted file mode 100644 index 1d59284562f8aa70e9b54fe396b124e9d82b5e02..0000000000000000000000000000000000000000 --- a/data/nav-importing navmesh.txt +++ /dev/null @@ -1,102 +0,0 @@ -Importing A Unity Navmesh -Setup your Unity scene it generate a Unity Navmesh using either the global Navmesh Baker or the Navmesh Building Components (Navmesh Surfaces) -Create a new GameObject under the map (Quantum MapData script) and add a MapNavMeshUnity script to it. The name of the GameObject will later be the name of the Quantum navmesh. -Create Navmesh Script -Adding the MapNavMeshUnity script to import a Unity navmesh -Select the map and toggle the Bake All Mode to Everything and press Bake All and check the log for errors. We expect a log message similar to: -Imported Unity NavMesh 'Navmesh', cleaned up 1211 vertices, found 7 region(s), found 4 link(s) -Map Baking -Baking the map will import and bake the navmeshes -The Quantum navmesh will show up.. -..under the Quantum map asset NavMeshLinks -..inside your project view next to the map asset file (one Quantum asset file and one binary .bytes file) -Navmesh Project View -The Quantum navmesh files will show up in the project view -To visualize the baked Quantum navmesh add the MapNavMeshDebugDrawer to the navmesh GameObject and link the .bytes file under BinaryAsset. -Navmesh Gizmos -Navmesh gizmos are rendered into the non-running scene using the MapNavMeshDebugDrawer script -To visualize the navmesh during play mode select Draw Nav Mesh in QuantumEditorSettings -All MapNavMeshUnity scripts under the map will be evaluated during map baking. But because the global Unity navmesh baking only produces one navmesh adding multiple navmeshes to the map only makes sense: - -When you are using the surface addon to control multiple navmesh surfaces -When you are drawing the navmeshes manually: use MapNavMeshDefinition instead of MapNavMeshUnity (see Quantum V1 documentation) -Or when you are creating your custom baking logic that enables and disables parts of the map during multiple Unity navmesh baking iterations -Optionally the navmesh baking can be forced to automatically run on every scene saving, playmode change or build event. See Editor Features in QuantumEditorSettings. - -Navmesh Auto Baking -Quantum supports navmeshes being only located in the origin. We encourage that the gameplay takes place close to the origin with reasonable extends because of the precision of the fixed point arithmetic. - -Back To Top - - -Import Settings -Weld Identical Vertices The Unity NavMesh is a collection of non-connected triangles. This option is very important and combines shared vertices. -Weld Vertex Epsilon Don't make the epsilon too small, vertices required to fuse can be missed, also don't make the value too big as it will deform your navmesh. -Delaunay Triangulation This option will post processes the imported Unity navmesh with a Delaunay triangulation to produce more evenly distributed triangles (it reorders long triangles). -Delaunay Triangulation Restrict To Planes On 3D navmeshes the Delaunay triangulation can deform the navmesh on slopes while rearranging the triangles. This behaviour is also noticeable on Unitys navmesh and can affect a game when the navmesh height is used for gameplay (e.g. walking on the Navmesh). Check this option to restrict the triangulation to triangles that lie in the same plane. -Fix Triangles On Edges Imported vertices are sometimes lying on other triangle edges, which leads to unwanted border detection. With this option such triangles are split. -Closest Triangle Calculation Areas in the map grid without a navmesh will need to detect nearest neighbors. This computation is very slow. The SpiralOut option will be much faster but fallback triangles can be null. -Closest Triangle Calculation Depth Number of cells to search triangles into each direction when using SpiralOut. -Enable Quantum_XY Only visible when the QUANTUM_XY define is set. Toggle this on and the navmesh baking will flip Y and Z to support navmeshes generated in the XY plane. -Min Agent Radius The minimum agent radius supported by the navmesh. This value is the margin between the navmesh and a visual border. The value is overwritten by retrieving it from Unity navmesh bake settings (or the surface settings) when baking in the Editor. -Back To Top - - -Using Navmesh Surfaces -Using the Unity navmesh surface addon has benefits: - -Runtime navmesh computation is possible (be aware that this is cannot be done deterministically across clients, the generated navmesh binary data must be send around) -Creating multiple navmeshes is easy -Using the NavMeshModifier script helps to mitigate the Unity navmesh island issue (see Quantum FAQ) -More control over internal settings -You can link multiple surfaces to a Quantum navmesh by adding them to the NavMeshSurfaces list. During the map baking of one navmesh other surfaces in the scene will be temporarily deactivated. - -Navmesh Surfaces -Quantum supports the Unity Navmesh Surface Addon -Back To Top - - -Custom Baking Options -MapNavMeshBaker.BakeNavMesh() is the essential Quantum navmesh baking method and it uses the MapNavMesh.BakeData as input data. In the default configuration the bake data is generated from the imported Unity navmesh triangulation. In a custom setup you can fill that data structure by yourself. It is basically only a triangle soup. - -You can customize the navmesh baking in different ways. - -Adding static code to the baking pipeline by deriving from MapDataBakerCallback: -Implement OnCollectNavMeshBakeData to modify existing or inject new MapNavMesh.BakeData into the pipeline. -Implement OnCollectNavMeshes to modify existing or add new NavMesh objects to be serialized. -Implement OnBeforeBakeNavMesh or OnBakeNavMesh to completely customized the baking or perform pre or post processing. -public abstract class MapDataBakerCallback { - /// - /// Is called before any navmeshes are generated or any bake data is collected. - /// - public virtual void OnBeforeBakeNavMesh(MapData data) { } - - /// - /// Is called during navmesh baking with the current list of bake data retreived from Unity navmeshes flagged for Quantum navmesh baking. - /// Add new BakeData objects to the navMeshBakeData list. - /// - /// Current list of bake data to be baked - public virtual void OnCollectNavMeshBakeData(MapData data, List navMeshBakeData) { } - - /// - /// Is called after navmesh baking before serializing them to assets. - /// Add new NavMesh objects the navmeshes list. - /// - /// Current list of baked navmeshes to be saved to assets. - public virtual void OnCollectNavMeshes(MapData data, List navmeshes) { } - - /// - /// Is called after the navmesh generation has been completed. - /// Navmeshes assets references are stored in data.Asset.Settings.NavMeshLinks. - /// - public virtual void OnBakeNavMesh(MapData data) { } -} -The methods in MapDataBakerCallback are called by reflection during the map baking process. Just fill out the methods in a public class outside any assembly definition. No need to instantiate a GameObject. For more information on the map asset baking pipeline, please refer to the Asset page in the manual. - -Back To Top - - -Visualizing Pathfinding -Activate the Pathfinder Gizmo Draw Pathfinder Funnel on QuantumEditorSettings to see the paths gizmos in the scene view - -Set the Thread Count to 1 in SimulationConfig to make the gizmos work every time because from Unity we only have access to the main thread. \ No newline at end of file diff --git a/data/nav-links.txt b/data/nav-links.txt deleted file mode 100644 index a6a468e99d36b955d0bede1a510296d7b788b768..0000000000000000000000000000000000000000 --- a/data/nav-links.txt +++ /dev/null @@ -1,40 +0,0 @@ -Creating Navmesh Links -Create a Unity Off Mesh Link. Quantum ignores the Activated, Auto Update Positions and Navigation Area properties. -Off Mesh Link Setup -Bake the map and check the resulting link using the MapNavMeshDebugDrawer script. The links are rendered as blue arrows. -Off Mesh Link Debug -The agent now already automatically uses the navmesh link during its path-finding. -Off Mesh Link Path Gizmo -Back To Top - - -Toggle Navmesh Links -Links can be toggled on and off and restrict what agents can use them by using Quantum navmesh regions. Attach a MapNavMeshRegion script to the Off Mesh Link set the Id and Cast Region to No Region. - -Off Mesh Link Regions -Back To Top - - -Hooking-In Gameplay -With no alteration the agent will traverse the link with its normal speed. You can take over control of the agent when the link has been reached by listening to the ISignalOnNavMeshWaypointReached signal. Then either disable the agent until your animation has completed or override the movement code in the ISignalOnNavMeshMoveAgent signal (this requires a change of the navmesh config to toggle the MovementType to Callback). - -This code sample performs a teleport when stepping on the link start waypoint. - -public void OnNavMeshWaypointReached(Frame f, EntityRef entity, FPVector2 waypoint, Navigation.WaypointFlag waypointFlags, ref bool resetAgent) { - var agent = f.Get(entity); - var waypointIndex = agent.WaypointIndex; - // btw HasFlag() is convenient but slow - if ((waypointFlags & Navigation.WaypointFlag.LinkStart) == Navigation.WaypointFlag.LinkStart) { - // we can be pretty sure that there always is a next waypoint for a link start - var linkDestination = agent.GetWaypoint(f, waypointIndex + 1); - f.Unsafe.GetPointer(entity)->Position = linkDestination; - } -} -Back To Top - - -Link Traversing Exception -Query if an agent is currently traversing a link by using NavMeshPathfinder.IsOnLink(FrameBase). -When setting a new target while the agent is traversing a link the agent will finish the current link before executing the path-finding. This is done by setting the WaypointFlag.RepathWhenReached. -If the waypoint before the last waypoint is a LinkStart a re-path is triggered. This helps to prematurely mitigate problems with running a re-path when the link start waypoint has already been reached. -No automatic re-pathing (NavMeshAgentConfig.MaxRepathTimeout) will be executed as long as the agent is traversing a link.. diff --git a/data/nav-navmesh region.txt b/data/nav-navmesh region.txt deleted file mode 100644 index 50ae6c171dfb878bc3da84f7b2ef2b20af99d97d..0000000000000000000000000000000000000000 --- a/data/nav-navmesh region.txt +++ /dev/null @@ -1,60 +0,0 @@ -Using Navmesh Regions -While respecting performance considerations for deterministic roll-backs in Quantum navmesh Regions are a compromise to Unitys dynamic navmesh carving. They can be used to dynamically toggle pre-defined areas of the navmesh with very little performance overhead. - -Because the regions are encoded as a mask inside the triangles (unsigned long) the maximum number of different region ids per map is 64 (Navigation.Constants.MaxRegions). Reusing the same id for different regions is possible though. - -The main Quantum navmesh (Walkable) is not a region and can not be toggled. - - -Creating Navmesh Regions -Step 1) Quantum Regions piggy-back on the Unity navmesh areas. Create a new area. We chose the name Toggleable in the image below but any name will do. - -To make the detection work when you have toggle-able regions right next to each other they will need to use different area ids. Create multiple areas here in that case. - -Region Areas -Step 2) Add the new area(s) to the MapNavMeshUnity otherwise the baking will not know what areas to look for. - -Add Region Area -Step 3) Unity uses the objects MeshRenderer to project the area onto the navmesh. Create a GameObject with a MeshRenderer and attach the MapNavMeshRegion. - -Region Setup -The Id is a unique string that will be accessible from code via the Map.RegionMap from which you can later get the region id (int, flag). - -CastRegion must be set to CastRegion. The script is re-used for Off Mesh Links for example that do not require to project regions to the navmesh. - -Under NavMeshHelper you can double check if the the GameObject is set up correctly: for example is it set to static and is the selected area is our region area. - -When the navmesh surfaces are installed the inspector look slightly different. You need to add a NavMeshModifier script to set the navmesh area for example. - -During the region import we try to match the triangles back to the original region script and for this use the bounding box of the mesh. Because the triangles generated are not 100% exact the settings on MapNavMeshUnity have a RegionDetectionMargin settings that adds a bit of room during the fitting. Increase this value if regions are not exported but when it becomes too large there may be problems detecting neighbouring regions. - -Btw: The MeshRenderer that generates the regions only has to be active during the baking.. we should add a tool for that.. - -Step 4) Now bake the map and see the coloured area of triangles in the navmesh where the region was placed. - -Active Regions -Step 5) Toggle the region off in code and watch the agent circumventing it. - -public override void OnInit(Frame f) { - var regionId = f.Map.RegionMap["foo"]; - f.NavMeshRegionMask->ToggleRegion(regionId, false); -} -Inactive Regions -Step 6) Region activation is accessible and stored in the frame. - -The mask needs to be reset when a new map is loaded. Run FrameBase.ClearAllNavMeshRegions() for example during the ISignalOnMapChanged signal. - -public class ResetRegionsSystem : SystemSignalsOnly, ISignalOnMapChanged { - public void OnMapChanged(Frame f, AssetRefMap previousMap) { - f.ClearAllNavMeshRegions(); - } -} -Checkout the API of NavMeshRegionMask object. Here is a small overview: - -NavMeshRegionMask.Default has all regions enabled (set to 1) -NavMeshRegionMask.ToggleRegion(int region, bool enabled) toggle a region by its region id. The region id is the offset of the bit-shift and can be retrieved by the name using Map.RegionMap dictionary. -NavMeshRegionMask.IsRegionEnabled(int region) checks if the region is active. -NavMeshRegionMask.IsSubset(NavMeshRegionMask) can be used to check if all regions active in one mask are also enabled in the other. -NavMeshRegionMask.Clear() sets all regions to active. -NavMeshRegionMask.HasValidRegions returns true, when the mask has exactly one valid region set. -NavMeshRegionMask.IsMainArea checks if the mask is zero which will be true for triangles belonging to the main navmesh area and cannot be toggled off. \ No newline at end of file diff --git a/data/nav-overview.txt b/data/nav-overview.txt deleted file mode 100644 index b8230cdc07904ba00913eedbedd120e8fe4edabc..0000000000000000000000000000000000000000 --- a/data/nav-overview.txt +++ /dev/null @@ -1,22 +0,0 @@ -The Quantum navigation system provides a deterministic Navigation Mesh and a set of Navigation Agent components to navigate and steer entities through the game world. - -N.B.: Core.NavigationSystem() needs to be enabled in SystemSetup.cs for the navigation related functionalities to work. - -More information on the API is available in the documentation included in the SDK docs\PhotonQuantum-Documentation.chm. - -Importing A Unity Navmesh -Creating Navmesh Agents -Agent Avoidance -Using Navmesh Regions -Using Navmesh Off Mesh Links -Custom Navmesh Generation - -Features -A* path-finding algorithm -Autonomous agents navigate to target destination through the navmesh -Parts of the navmesh can be toggled on and off during run-time (regions) -HRVO agent avoidance -Dynamic avoidance obstacles (not navmesh carving) -Off-mesh links -3D Navigation supported (since Quantum 2.1) -Region triangle weights (since Quantum 2.1) diff --git a/data/online-sess.txt b/data/online-sess.txt deleted file mode 100644 index 9bd49818007fc09deaf6ab0e5c807b4fa5f96e4f..0000000000000000000000000000000000000000 --- a/data/online-sess.txt +++ /dev/null @@ -1,340 +0,0 @@ -Overview -The Quantum online services are build on top of the common Photon online infrastructure (Photon Realtime). Connecting to an online session usually goes through three connection phases: - -Custom Authentication: Photon does not offer player accounts and recommends securing the logins using a proprietary or third-party authentication provider and set up photon custom authentication. -Game Server Connection: Before starting the online simulation the clients have to connect to the Photon Cloud and enter a Room using the Photon Realtime library. The process, including rudimentary random matchmaking, is described here, inside the DemoMenu from the SDK and the official Photon Realtime doc photon matchmaking and lobby. -Quantum Simulation Start Sequence: In this phase the Quantum simulation is started, client configuration data is send and the Quantum session is joined and synchronized between clients. -Back To Top - - -How To Start A Quantum Online Session -The diagram shows the typical connection flow to give an overview of the connection handling required to start, restart and stop an online Quantum game. - -Starting Online Session: Connection Sequence -Starting Online Session: Connecting With Photon -Back To Top - - -Further Readings -photon realtime quick start -quantum reconnection manual -Back To Top - - -The Demo Menu -UIConnect -UIConnecting -UIReconnecting -UIRoom -UIGame -The DemoMenu (included in the SDK) demonstrates the complete process. To better understand what's going on the individual UI classes (UIConnect, UIConnecting, UIReconnecting, UIRoom, UIGame) should be seen as a state machine. The similarities to the diagram above are visible. - -The player presses a button to connect to the Photon Cloud and uses random matchmaking to finally join a Photon Room. -After waiting for other players pressing a button signals the game start and the players each will start their Quantum sessions. -The player can press disconnect, stop the Unity editor or restart the application to then use the reconnect button to get back to the same game session. -Demo Menu -Demo Menu Flowchart -Back To Top - - -UIConnect -There are a few extras that are supposed to make the DemoMenu a convenient development tool to start with but also make the code harder to read. For example the RegionDropdown and AppVersionDropdown. - - -RegionDropdown -There is a scriptable asset (Photon/QuantumDemo/Menu/Resources/PhotonRegions.asset) that contains a set of regions selectable by dropdown to quickly change the region when running the menu. The LastSelectedRegion is kept inside the PlayerPrefs. - -Back To Top -Back To "The Demo Menu" - - -AppVersionDropdown -The AppVersion (which is set on AppSettings when connecting) is a way to separate the users when performing matchmaking. This can be used for live games that use the same AppId but also during testing/development. The implementation covers two cases: - -Developers can run the app multiple times on their own machine and not want any others joining their games. For this purpose, it is recommended to use the Private AppVersion which uses a Guid stored in Photon/QuantumDemo/Menu/Resources/PhotonPrivateAppVersion.asset (excluded from version control). Only builds that were build on their machine can play together. -For a focus test or QA tests players should join certain groups. Names listed in the Photon/QuantumDemo/Menu/Resources/PhotonAppVersions.asset will be selectable in the drop down list of the menu. -Back To Top -Back To "The Demo Menu" - - -AppSettings -To connect to the Photon Cloud the configuration called AppSettings is required. Inside the Quantum integration this is usually accessed via a Singleton pattern: PhotonServerSettings.Instance.AppSettings which of course can be exchanged with another way of injecting the correct data. - -Notice that the settings object is copied. Otherwise there would a risk of saving changes to the asset. - -var appSettings = PhotonServerSettings.CloneAppSettings(PhotonServerSettings.Instance.AppSettings); -Back To Top -Back To "The Demo Menu" - - -QuantumLoadBalancingClient -The one instanance of the connection class QuantumLoadBalancingClient is stored as a static reference in UIMain.Client. UI development has lot's of flavors and the Singleton there and state machine here are the simplest way and should challenge developers to change the menu flow to their way of creating UI. - -The QuantumLoadBalancingClient exists only to assign a Nickname more conveniently and to cache the BestRegionSummaryFromStorage (region ping results) to PlayerPrefs. To start Quantum only the inherited class is LoadBalancingClient is required. - -Back To Top -Back To "The Demo Menu" - - -ConnectUsingSettings -The Demo Menu does not use any authentication process instead is will just connect to the Photon Cloud which will generate a user id for the client. - -ConnectUsingSettings() will use the mentioned AppSettings enhanced by Region and AppVersion selection to connect to the Photon Master Server. HideScreen() and UIConnecting.ShowScreen() are used to issue the state progression. - -if (UIMain.Client.ConnectUsingSettings(appSettings, Username.text)) { - HideScreen(); - UIConnecting.ShowScreen(); -} -Back To Top -Back To "The Demo Menu" - - -OnReconnectClicked -The sample demonstrates three reconnection scenarios: - -The connection object (UIMain.Client) is valid and PlayerTtlInSeconds is set -The client may still be considered online in which case it can try to "fast reconnect" using UIMain.Client.ReconnectAndRejoin(). It is not necessary to check for PlayerTtlInSeconds here the reconnection has to be inside a 10 second timeout before the server removes the clients residue from the room and fast reconnect is not possible anymore. - -ReconnectAndRejoin() would transport the client back into their original room. - -The connection object (UIMain.Client) is valid but the client hat been offline for more than 10 sec (or PlayerTtlInSeconds) -The client will reconnect to the master server UIMain.Client.ReconnectToMaster() and form there join back into the room (room info saved on ReconnectInformation). - -The connection object is lost probably because of an app restart -Data to reconnect is saved in Unity's PlayerPrefs using ReconnectInformation. Relevant data to cache is RoomName, Region, AppVersion, UserId (not if the custom authentication runs again any way) and finally the Timeout: when the saved data is not worth to reconnect to. - -UIMain.Client and AppSettings is reconfigured then the connection to the master server is opened and a rejoin or join is executed. Rejoin only works when the UserId is the same. - -if (UIMain.Client == null && ReconnectInformation.Instance.IsValid) { - UIMain.Client = new QuantumLoadBalancingClient(PhotonServerSettings.Instance.AppSettings.Protocol); - UIMain.Client.UserId = ReconnectInformation.Instance.UserId; - - var appSettings = PhotonServerSettings.CloneAppSettings(PhotonServerSettings.Instance.AppSettings); - appSettings.FixedRegion = ReconnectInformation.Instance.Region; - appSettings.AppVersion = ReconnectInformation.Instance.AppVersion; - - if (UIMain.Client.ConnectUsingSettings(appSettings, LastUsername)) { - HideScreen(); - UIReconnecting.ShowScreen(); - } -} -Back To Top -Back To "The Demo Menu" - - -Further Readings -quantum reconnection manual -photon realtime analyzing disconnects -Back To Top -Back To "The Demo Menu" - - -UIConnecting -The state will listen to Photon connection callbacks IConnectionCallbacks and IMatchmakingCallbacks to progress the connection and to perform error handling. - - -OnConnectedToMaster -Once the connection to the master server is successful the random matchmaking is initiated right away and a OpJoinRandomRoomParams object is created. In this example the CustomRoomProperties are used to negotiate the map selection which can be changed after entering the game room. - -RoomOptions.IsVisible indicates that the room is open for matchmaking -RoomOptions.MaxPlayers normally reflects the same number as Quantum players -RoomOptions.Plugins has to be new string[] { "QuantumPlugin" } -RoomOptions.PlayerTtl and -RoomOptions.EmptyRoomTtl reflect the settings on the PhotonServerSettings -var joinRandomParams = new OpJoinRandomRoomParams(); -_enterRoomParams = new EnterRoomParams(); -_enterRoomParams.RoomOptions = new RoomOptions(); -_enterRoomParams.RoomOptions.IsVisible = true; -_enterRoomParams.RoomOptions.MaxPlayers = Input.MAX_COUNT; -_enterRoomParams.RoomOptions.Plugins = new string[] { "QuantumPlugin" }; -_enterRoomParams.RoomOptions.CustomRoomProperties = new Hashtable { - { "HIDE-ROOM", false }, - { "MAP-GUID", defaultMapGuid }, -}; -_enterRoomParams.RoomOptions.PlayerTtl = PhotonServerSettings.Instance.PlayerTtlInSeconds * 1000; -_enterRoomParams.RoomOptions.EmptyRoomTtl = PhotonServerSettings.Instance.EmptyRoomTtlInSeconds * 1000; - -if (!UIMain.Client.OpJoinRandomOrCreateRoom(joinRandomParams, _enterRoomParams)) { - UIMain.Client.Disconnect(); -} -OpJoinRandomOrCreateRoom() initiates the random matchmaking and the connection to the game server. - -Back To Top -Back To "The Demo Menu" - - -OnJoinRandomFailed -There is a way to mitigate ErrorCode.NoRandomMatchFound by just creating a new room. - -Back To Top -Back To "The Demo Menu" - - -OnJoinedRoom -When successfully joined a room the state progresses to UIRoom. - -Other error callbacks report and show a dialog which disconnects the client and returns the players to the main menu. - -Back To Top -Back To "The Demo Menu" - - -UIReconnecting - -OnConnectedToMaster -When running ReconnectAndRejoin() this is skipped and OnJoinedRoom() is called directly. - -In the other cases of reconnecting two paths are possible: - -UIMain.Client.OpJoinRoom() -UIMain.Client.OpRejoinRoom() -The different is that Rejoin requires the client to be still considered to be active on the server (based on the 10 second timeout and the addition PlayerTTL). Both will have error handling in OnJoinRoomFailed(). - -Back To Top -Back To "The Demo Menu" - - -OnJoinedRoom -Success, continue with the UIRoom state. - -Back To Top -Back To "The Demo Menu" - - -OnJoinRoomFailed() -Two errors are very common to handle here: - -ErrorCode.JoinFailedFoundActiveJoiner: Tried to join but the client is still marked active in the room (the server does not know that it disconnected). Mitigation: retry until 10 seconds are over. -ErrorCode.JoinFailedWithRejoinerNotFound: Tried to rejoin but there is not client marked active in the room. Mitigation: join normally. -Back To Top -Back To "The Demo Menu" - - -UIRoom -In addition to matchmaking and connection callbacks UIRoom listens to IInRoomCallbacks and IOnEventCallback. - -This is the state before the Quantum simulation where clients are already in the Photon Room. The sample uses room properties to communicate some game configuration like the map selection. Only the master client can change the settings. - -To start a Quantum session usually a RuntimeConfig is required which includes custom settings for each game. By default it can be changed inside the Menu.scene (Menu > RuntimeConfig). - -Back To Top -Back To "The Demo Menu" - - -Start The Game -The sample uses the Photon communication tool OpRaiseEvent(UIMain.PhotonEventCode.StartGame) to communicate the starting of the game which is then dispatched by all client in OnEvent(). - -public void OnEvent(EventData photonEvent) { - switch (photonEvent.Code) { - case (byte)UIMain.PhotonEventCode.StartGame: -For client coming after the start or are rejoining the information that the game has started is saved in the room properties: - -var ht = new ExitGames.Client.Photon.Hashtable {{"STARTED", true}}; -UIMain.Client.CurrentRoom.SetCustomProperties(ht); -All clients returning to the room initially check if the game has already started. - -UIMain.Client.CurrentRoom.CustomProperties.TryGetValue("MAP-GUID", out mapGuidValue) -UIMain.Client.CurrentRoom.CustomProperties.TryGetValue("STARTED", out var started)) -Starting or restarting the Quantum session is done inside StartQuantumGame(). - -Initially the RuntimeConfig is copied using FromByteArray(ToByteArray()) to not accidentally write on the source and a map guid is set. - -var config = RuntimeConfigContainer != null ? RuntimeConfig.FromByteArray(RuntimeConfig.ToByteArray(RuntimeConfigContainer.Config)) : new RuntimeConfig(); -config.Map.Id = mapGuid; -The StartParameters are configured. There are different settings when joining as a spectator and for rejoiners there is an optional local snapshot that can be send (the recoding happens in UIGame). - -var param = new QuantumRunner.StartParameters { -RuntimeConfig = config, -DeterministicConfig = DeterministicSessionConfigAsset.Instance.Config, -ReplayProvider = null, -GameMode = Spectate ? Photon.Deterministic.DeterministicGameMode.Spectating : Photon.Deterministic.DeterministicGameMode.Multiplayer, -FrameData = IsRejoining ? UIGame.Instance?.FrameSnapshot : null, -InitialFrame = IsRejoining ? (UIGame.Instance?.FrameSnapshotNumber).Value : 0, -PlayerCount = UIMain.Client.CurrentRoom.MaxPlayers, -LocalPlayerCount = Spectate ? 0 : 1, -RecordingFlags = RecordingFlags.None, -NetworkClient = UIMain.Client, -StartGameTimeoutInSeconds = 10.0f -}; -The clientId used to start the Quantum game is usually the Photon UserId. It is important to be the same for reconnecting players in order to be assigned to the same Quantum player slot. For testing proposes the ClientIdProvider script located in the menu scene (Menu > UICanvas > Menu > IdProvider) can be configured to use different sources. - -var clientId = ClientIdProvider.CreateClientId(IdProvider, UIMain.Client); -QuantumRunner.StartGame(clientId, param); -After calling QuantumRunner.StartGame() the Quantum starting sequence is executed automatically. It will also trigger the map scene loading configured in the SimulationConfig. The demo menu state transitions to UIGame. - -Back To Top -Back To "The Demo Menu" - - -UIGame -The game UI state handles: - -Displaying and listening to a disconnect button -Recording snapshot on the OnDisconnected() callback -Stop the quantum simulation when disconnecting by calling QuantumRunner.ShutdownAll(true) -Back To Top -Back To "The Demo Menu" - - -Testing Disconnects -Inside OnLeaveClicked() change UIMain.Client.Disconnect() to something else to simulate slightly more natural disconnects or exceptions in the networking thread. - -public void OnLeaveClicked() { - UIMain.Client.Disconnect(); - // Debugging: use these instead of UIMain.Client.Disconnect() - //UIMain.Client.SimulateConnectionLoss(true); - //UIMain.Client.LoadBalancingPeer.StopThread(); -} -Back To Top - - -Quantum Start Sequences -Starting A Quantum Online Session -Reconnecting Into A Quantum Online Session -Further Readings -What happens after QuantumRunner.StartGame() is called? The following diagram visualizes the protocol and order of callbacks. - -Back To Top - - -Starting A Quantum Online Session -The client joins the server session and is selected to upload the DeterministicConfig and RuntimeConfig (can be overwritten on the server authoritatively by running an enterprise Quantum server). The server uses the first configs it received and progresses to the simulation start while down-streaming the accepted configs to all clients. - -On the client the GameStarted callback is invoked followed by OnInit() on all systems. - -Clients optionally run SetPlayerData() to upload their RuntimePlayer data, which in turn is returned by the server resulting in OnPlayerDataSet signal for everyone. - -Online Session: Start Sequence -Online Session: Start Sequence -Back To Top -Back To "Quantum Start Sequences" - - -Reconnecting Into A Quantum Online Session -During the reconnection or late-joining sequence the protocol changes if a snapshot has to be send to the client. Read the Reconnection Manual for more details about snapshots timings. - -For buddy-snapshots another client is tasked with uploading a recent snapshot. In the meantime the reconnecting client is signaled a successful start and OnInit() runs for the systems. Once the snapshot has been received the GameResynced callback is executed and input is coming in to catch up the last "second". - -If desired SetPlayerData() can be invoked again. - -Online Session: Restart Sequence -Online Session: Restart Sequence -Back To Top -Back To "Quantum Start Sequences" - - -Further Readings -quantum cheat protection manual -quantum reconnection manual -quantum config files -Back To Top - - -Stopping The Session And Disconnecting -To stop the Quantum simulation locally run QuantumRunner.ShutdownAll(bool immediate). Only set immediate:true when it's not called from within a Quantum callback. When set to false the shutdown is postponed until the next Unity update. - -ShutdownAll will destroy the QuantumRunner object which triggers the local Quantum simulation to be stopped. It will also result in either a connection Disconnect() or LeaveRoom() depending what is set as StartParameters.QuitBehaviour. - -If the client should exit the game gracefully, for example to clean up the player avatar for remote clients, extra logic has to be implemented into the simulation. Either a client issued command or monitoring the player connected state (see PlayerConnectedSystem). - -Considering that players also close their app or Alt+F4 their games there might not always be an opportunity to send a graceful disconnect. \ No newline at end of file diff --git a/data/overview.txt b/data/overview.txt deleted file mode 100644 index f2c910af9824443ffcc06cfd71fcfc245ab4e446..0000000000000000000000000000000000000000 --- a/data/overview.txt +++ /dev/null @@ -1,6 +0,0 @@ -Introduction -The Quantum physics engine is cross-platform, deterministic and fully supports Quantum's predict rollback model. - -This page covers both 2D, 3D and 2.5D Physics documentation. - -Important: the 2D and 3D APIs are very similar. This will become apparent in the examples we will cover; each example will be presenting the 2D code followed by its 3D equivalent. \ No newline at end of file diff --git a/data/player- input connection flags.txt b/data/player- input connection flags.txt deleted file mode 100644 index b09343c0c952123fb74d18678ffc55ca8a57a4fe..0000000000000000000000000000000000000000 --- a/data/player- input connection flags.txt +++ /dev/null @@ -1,56 +0,0 @@ -Introduction -The DeterministicInputFlags are used by Quantum to: - -detect whether a player is present , i.e. connected, to the simulation; -decide how to predict the next tick's input for a given player; and, -know whether the input on a verified frame was provided by a client or was replaced by the server. -It is possible to automate the checks by implementing PlayerConnectedSystem, for more information please refer to its entry on the player page. - -Back To Top - - -Types -public enum DeterministicInputFlags : byte { - Repeatable = 1 << 0, - PlayerNotPresent = 1 << 1, - ReplacedByServer = 1 << 2 -} -PlayerNotPresent = means there is no client connected for this player index. -ReplacedByServer = means the player index is controlled by a client, but the client did not send the input in time which resulted in the server repeating or replacing/zeroing out the input. -Repeatable = tells both the server and other clients to copy this input data into the next tick (on server when replacing input due to timeout, and on other clients for the local prediction algorithm). This can be set by the developer from Unity when injecting player input and should be used on direct-control-like input such as movement; it is not meant for command-like input (e.g. buy item). -Back To Top - - -Implementation Example -IMPORTANT: DeterministicInputFlags can only be trusted on verified frames. - -The code snippet below is an extra from the LittleGuys sample found on the BotSDK page. - -private void UpdateIsBot(Frame f, EntityRef littleGuyEntity) -{ - // Return if players shouldn't be replaced by bots - if (!f.RuntimeConfig.ReplaceOnDisconnect) - return; - - // Only update this information if this frame is Verified. - if (!f.IsVerified) return; - - var littleGuyComponent = f.Unsafe.GetPointer(littleGuyEntity); - - // Get the input flags for that player - var inputFlags = f.GetPlayerInputFlags(littleGuyComponent->PlayerRef); - - // Bitwise operations to see if the PlayerNotPresent flag is activated - var playerDisconnected = (inputFlags & DeterministicInputFlags.PlayerNotPresent) == DeterministicInputFlags.PlayerNotPresent; - - // Store it in the IsBot field so this can be evaluated in other parts of code - littleGuyComponent->IsBot = playerDisconnected; - - // Only initialize the entity as a bot if it doesn't have the HFSM Agent component yet - if (playerDisconnected && f.TryGet(littleGuyEntity, out var hfsmAgent) == false) - { - // We're replacing players only by the HFSM, but this could easily be changed to be GOAP instead - - HFSMHelper.SetupHFSM(f, littleGuyEntity, f.RuntimeConfig.ReplacementHFSM); - } -} \ No newline at end of file diff --git a/data/player- replacement bot.txt b/data/player- replacement bot.txt deleted file mode 100644 index 8b3986ff15e8fc6628f9369a31228c8258d7eee3..0000000000000000000000000000000000000000 --- a/data/player- replacement bot.txt +++ /dev/null @@ -1,75 +0,0 @@ -Introduction -It is often useful to let AI get the control over players' characters in one of two situations: - -To replace players who got disconnected from the game during an ongoing match. This helps creating fairer matches as Bots can help while the player tries to reconnect to the game, or even to compensate for a player who did a rage quit. -To fill a room with fake players when the minimum amount of necessary players to start a game session has not been reached. This is particularly important during the early stages of a game's release cycle when the playerbase is still small. -Back To Top - - -The Setup -In Quantum, the AI logic for such a feature is executed locally by every client's machine; meaning there is no concept of a "master client who simulates Bots input". - -Although it is usually simple to run an AI to control some game entities, doing so is game specific. Of course, the complexity of the AI implementation itself can range from very simple to very complex. - -The easiest way to start doing this is to signalize whether an entity is, at any point in time, controlled by AI. This can be achieved in different ways: - -Adding a "flag component", like a component AI {} which is added / removed from entities when needed. A system can then iterate over every entity which has an AI component to perform the controlling logic; -Using a Boolean in a component to turn on / off the AI controls, like component MyCharacter { bool ControlledByAI; }; -Adding more AI-specific components with lots of extra data, such as the Bot SDK's agent components (HFSM, BT, etc), or a custom one; -Now, when should it be done? It depends on the chosen use cases mentioned before; these will be explored throughout the next sections. - -Back To Top - - -Replacing A Real Player During A Game Match -This can be achieved by activating the PlayerConnectedSystem and then reacting to the ISignalOnPlayerConnected and ISignalOnPlayerDisconnected signals. The system and its accompanying signals are explained in the player documentation. -Once the player disconnects: find the entity or entities controlled by that player, and setup the AI for them as explained above. -When the player connects again, check if there are entities which were controlled by that player and remove the AI setup so the player takes back the control from the AI. - -It is worth mentioning the system above uses the PlayerInputFlags in order to work, which can also be used independently of the PlayerConnectedSystem, if desired. Find more information about player input flags here. - -Back To Top - - -Filling A Room With Bots -In this case, there are no actual players involved. In other words, entities are created which are never meant to be controlled by an actual person. - -Since no players are involved, no connectivity logic is needed either. It is possible for the custom game logic to fill the room with entities, like in this sample algorithm / snippet: - -In a Quantum system, wait an interval of time after the game started so Players have time to connect and send their player data; -When Players arrive, using the OnPlayerDataSet callback, save in the game state (e.g. in a variable in frame.Global) the amount of players who have successfully connected and joined to the game; -After the interval, subtract this amount from the expected player count from the frame API, like so: -int fillAmount = frame.PlayerCount - frame.Global->ConnectedPlayersCount; -Use the result to perform a for loop where the bot entities will be created: -for(int i = 0; i < fillAmount; i++) -{ - // Create a new Entity here - // Setup it as a Bot as explained earlier on this document -} -The snippet above is very simple and should be adjusted to the game's and game design's requirements; for instance, it may be useful to assign special information to the bot entities such as faked player information, team data, etc. - -Back To Top - - -Selecting A Bot To Create -Depending on the game type, it can be useful to create a new Bot based on some already known data. For example pick a Bot for a character which was not yet chosen or with varying levels of difficulty. - -TIP: The RuntimeConfig asset can hold some references to Entity Prototypes (i.e AssetRefEntityPrototype) so you can reference a variety of characters to pick from. Alternatively, there could be a single type of character with a reference to different AI assets to control it (e.g. different State Machines based on the difficulty level). - -Back To Top - - -Players And Bots Architecture -Characters are controlled by Quantum Systems. These systems usually know how to read player inputs to change their character's game state, such as moving them, rotating them and triggering attacks. - -Now, controlling these same characters with AI logic can be done in multiple ways. Here is an example code architecture which usually works well: - -Players naturally have an Input which can be polled with frame.GetPlayerInput(playerIndex), which returns you a pointer to a struct of type Input; -Bots can also have the same struct in a custom component - component Bot { Input Input } - , and the AI logic itself might be used just to fill the data inside of it; -Fill the input data before any character system runs. This way, if systems know how to get the input regardless of who is filling it, then no additional special checks in the systems are needed to know if that entity is a player or a Bot; -This means that the AI system might (almost) never directly influence the entity state, but rather it generates fake inputs based on its decision making logic. -The advantage of using such architecture is a clear separation of: Inputs | Players and Bots | Characters, by providing decoupled systems. - -Remember: this is just a suggestion. This architecture is not at all mandatory and the same result can be achieve in many other ways. - -Here is a visualisation of this strategy used in the twin stick shooter sample: \ No newline at end of file diff --git a/data/player-overview.txt b/data/player-overview.txt deleted file mode 100644 index 4798cd78be2f7346a71660dc581c36f5358158f5..0000000000000000000000000000000000000000 --- a/data/player-overview.txt +++ /dev/null @@ -1,194 +0,0 @@ -Introduction -Quantum is agnostic to the concept of player entities. All entities are the same in the eyes of the simulation. Therefore, when we refer to "the player" in this document, we mean the player controlled entity. - -Back To Top - - -Player Identification -A player can be identified in two ways: - -their player index; and, -their PlayerRef. -Back To Top - - -Player Index Assignment -The Quantum player index is assigned by the server based on the order in which the Session.Join() messages arrive. This is not to be confused with the Photon Id which is based on order in players joined the Photon room. It is not possible to set the "Desired Quantum Id" on a Photon Player. - -N.B.: In the event of a disconnect, we guarantee the client gets the same player index IF it reconnects with the same ClientId; regardless of their Photon Id - public static QuantumRunner StartGame(String clientId, Int32 playerCount, StartParameters param). - -Back To Top - - -Player Index Vs PlayerRef -The PlayerRef is a wrapper for the player index in the Quantum ECS. The PlayerRef is 1-based, while player index starts at 0. The reason is that default(PlayerRef) will return a "null/invalid" player ref struct for convenience. - -There are automatic cast operators that can cast an Integer to a PlayerRef and vice-versa. - -default(PlayerRef), internally a 0, means NOBODY -PlayerRef, internally 1, is the same as player index 0 -PlayerRef, internally 2, is the same as player index 1 -Back To Top - - -Photon Id -You can identify a player's corresponding Photon Id via the Frame API: - -Frame.PlayerToActorId(PlayerRef player) converts a Quantum PlayerRef to an ActorId (Photon client id); or, -Frame.ActorIdToAllPlayers(Int32 actorId) the reverse process of the previous method. -Use this if you plan on showing player names via PhotonPlayer.Nickname for example. - -IMPORTANT: The Photon Id is irrelevant to the Quantum simulation. - -Back To Top - - -Join The Game -When a game starts, the followings things happen in sequence: - -QuantumRunner.Session.Join() sends join request to server with desired player count. -The request is received by the server where it is validated and a confirmation is sent back to the user. If the information attached to the request is not valid, the request will be refused. -The Start game message is received by the client. -The player can now send and receive inputs. -(OPTIONAL) - In case of a late join, the client may receive a snapshot-resync; in this case step 4 would not be sending inputs while waiting for the snapshot. -(OPTIONAL) - SendPlayerData can now be used. It may be used as many times as needed during the game session (at game start and/or during the session itself). Every time SendPlayerData is called, it sends a serialized version of RuntimePlayer to the server, which then attaches to a tick input set confirmation and thus deterministically triggers the signal. -Config Sequence Diagram -Sequence Diagram -For more information on the configuration files involved, please refer to the Configuration Files document of the manual. - -Back To Top - - -SendPlayerData -QuantumGame.SendPlayerData(RuntimePlayer features) is a data path to deterministically inject a special kind of data (serialized RuntimePlayer) into the input stream. Although SendPlayerData is commonly called at game start to set up all the player; it is also possible to call it during the game session if the data needs to be updated. - -After starting, joining the Quantum Game the CallbackGameStarted callback fires. It is at this moment that each player may call the SendPlayerData method to be added as a player in everyone else's simulation. Calling this explicitly greatly simplifies the process for late-joining players. - -public class MyCallbacks : MonoBehaviour { - private void OnEnable() { - QuantumCallback.Subscribe(this, OnGameStart); - } - - private void OnGameStart(CallbackGameStarted callback) { - // paused on Start means waiting for Snapshot - if (callback.Game.Session.IsPaused) return; - - // It needs to be sent for each local player. - foreach (var lp in callback.Game.GetLocalPlayers()) { - Debug.Log("CustomCallbacks - sending player: " + lp); - callback.Game.SendPlayerData(lp, new Quantum.RuntimePlayer { }); - } - } -} -Back To Top - - -Player Entities Are Instantiated In Local Mode But Not In Multiplayer Mode -Most likely QuantumGame.SendPlayerData() was not executed for each player. If you are using the demo menus to start the game add the script CustomCallbacks.cs anywhere to the menu scene. - -Back To Top - - -PlayerConnectedSystem -To keep track of players' connection to a quantum session Input & Connection Flags are used. The PlayerConnectedSystem automates the procedure and notifies the simulation if a player has connected to the session or disconnected from it. To make use of the system, it has to be added to the SystemSetup. - -public static class SystemSetup { - public static SystemBase[] CreateSystems(RuntimeConfig gameConfig, SimulationConfig simulationConfig) { - return new SystemBase[] { - // pre-defined core systems - ... - - new PlayerConnectedSystem(), - - // custom systems - ... - } - } -} -In order to received the connection and disconnection callbacks the ISignalOnPlayerConnected and ISignalOnPlayerDisconnected have to be implemented in a system. - -Back To Top - - -ISignalOnPlayerDataSet -Implementing ISignalOnPlayerDataSet in a system give you access to public void OnPlayerDataSet(Frame f, PlayerRef playerRef). OnPlayerDataSet is called every time a serialized RuntimePlayer is part of a specific tick input. - -Back To Top - - -RuntimePlayer -The class RuntimePlayer is meant to hold player specific information such as for instance the character selected. For RuntimePlayer to work with your custom needs, you have to implement the serialization method - in the case of asset links, the GUID needs to be serialized. - -RuntimePlayer is a partial class in the quantum.code project. To facilitate upgrading SDKs and future proofing, your custom implementations are to be done in RuntimePlayer.User.cs. In here you can add the parameters you would like to specify for each player and their serialization can be implemented in the SerializeUserData method. The result will resemble this: - -namespace Quantum { - partial class RuntimePlayer { - public AssetRefCharacterSpec CharacterSpec; - - partial void SerializeUserData(BitStream stream) - { - stream.Serialize(ref CharacterSpec.Id.Value); - } - } -} -Back To Top - - -Accessing At Runtime -The RuntimePlayer asset associated with a player can be retrieved by querying Frame.GetPlayerData() with their PlayerRef. - -public void OnPlayerDataSet(Frame f, PlayerRef player){ - var data = f.GetPlayerData(player); -} -Back To Top - - -Initializing A Player Entity -The entity controlled by a player can be initialized at any point during the simulation. A common approach is to initialize it when the player connects(ISignalOnPlayerConnected) and / or the player data is received (ISignalOnPlayerDataSet). - -ISignalOnPlayerConnected: The player entity can be initialized with whatever information is already available in the simulation or the asset database. -ISignalOnPlayerDataSet: The player entity can be initialized with the information associated with the player's RuntimePlayer specific information. This is convenient for things such as selected character model / skin or inventory loadout. -Back To Top - - -Simulation Vs View -First, a few clarifications: - -From the simulation's perspective (Quantum), player controlled entity are entities with player input. It does not know of local or remote players. -From the view's perspective (Unity), we poll input from the players on the local client. -To recapt, in the simulation there is no such thing as "local" or "remote" players; however, in the view a player is either "local" or it is not. - -Photon.Deterministic.DeterministicGameMode.Local -Photon.Deterministic.DeterministicGameMode.Multiplayer -Photon.Deterministic.DeterministicGameMode.Replay -Back To Top - - -Max Amount Of Players -The max player count is essential to know in advance for it defines how much space needs to be allocated inside the memory chunk for each frame. By default the maximum amount of players is 6. To change it add the following lines to any of your qtn-files: - -#define PLAYER_COUNT 8 -#pragma max_players PLAYER_COUNT -The define acts like a define and can be used inside the DSL (e.g. for allocating arrays with the player count). -The pragma actually defines how many player the simulation can handle. -Back To Top - - -Local Player -Quantum offers to APIs in the View to check if a player is local: - -QuantumRunner.Default.Game.Session.IsLocalPlayer(int player); and, -QuantumRunner.Default.Game.PlayerIsLocal(PlayerRef playerRef). -Back To Top - - -Multiple Local Players -QuantumRunner.Default.Game.GetLocalPlayers() returns an array that is unique for every client and represents the indexes for players that your local machine controls in the Quantum simulation. - -It returns one index if there is only one local player. Should several players be on the same local machine controls, then the array will have the length of the local player count. -These are exactly the same indexes that are passed into QuantumInput.Instance.PollInput(int player). -The indexes are defined by the server (unless it is a local game). -The indexes are always within [0, PlayerCount-1]. PlayerCount represents the total player count in the match. It is passed into QuantumRunner.StartGame. -The index values are arbitrary (within the range of 0 to max players for this session) and depend on the order of multiple players connecting and disconnecting and when their messages reach the server. -If a local machine has more than one player, the values are not necessarily consecutive. -When rejoining the game you can be assigned the same player index as long as you call Session.Join() with the same GUID and the room has not been filled with new players since you disconnected. \ No newline at end of file diff --git a/data/prediction culling.txt b/data/prediction culling.txt deleted file mode 100644 index 7bc56128d6c9ac2bc830a1f213387d2676a49297..0000000000000000000000000000000000000000 --- a/data/prediction culling.txt +++ /dev/null @@ -1,107 +0,0 @@ -Introduction -Prediction Culling is used in games where players only have a partial view of the game world at any given time. It is safe to use and simple to activate. - -Prediction Culling allows to save CPU time during the Quantum prediction and rollback phases. Having it enabled will allow the predictor to run exclusively for important and visible entities to the local player(s), while leaving anything outside the view to be simulated only once per tick once the inputs are confirmed by the server; thus avoiding rollbacks wherever possible. - -Although the performance benefits vary from game to game, they can be quite large; this is particularly important the more players you have, as the predictor will eventually miss at least for one of them. Take for instance a game running at a 30Hz simulate rate. If the game requires an average of a ten tick rollback per confirmed input, this means the game simulation will have to lightweight enough to run close to 300Hz (including rollbacks). Using Prediction Culling, full frames will be simulated at the expected 30/60Hz at all times, and the culling will be applied to the prediction area is running within the prediction buffer. - -Back To Top - - -Setting Up Prediction Culling -As a consequence of Prediction Culling means the predicted simulation can never be accepted as the final result of a frame since part of it was culled, thus it never advanced the simulation of the whole game state. - -To set up prediction culling, there are two steps; one in Quantum and one in Unity. - -Back To Top - - -In Quantum -Enabling prediction culling in Quantum is a simple as adding the culling systems to SystemSetup.cs before any of the other systems. - -namespace Quantum { - public static class SystemSetup { - public static SystemBase[] CreateSystems(RuntimeConfig gameConfig, SimulationConfig simulationConfig) { - return new SystemBase[] { - // pre-defined core systems - new Core.CullingSystem2D(), - new Core.CullingSystem3D(), - - new Core.PhysicsSystem2D(), - new Core.PhysicsSystem3D(), - - new Core.NavigationSystem(), - new Core.EntityPrototypeSystem(), - - // user systems go here - }; - } - } -} -By default both Core.CullingSystem2D() and Core.CullingSystem3D() are included in SystemSetup. - -Back To Top - - -In Unity -In Unity, you need to set the prediction area. This will be used to decide which entities to cull from prediction. You can update the prediction area by calling SetPredictionArea() on every Unity update: - -// center is either FPVector2 or FPVector3 -// radius is an FP -QuantumRunner.Default.Game.SetPredictionArea(center, radius); -Back To Top - - -What To Expect - -Physics And Navmesh Agents -The physics engines and the NavMesh related systems are affected by Prediction Culling. - -When Prediction Culling is enabled, they will only consider and update entities within the visible area on non-verified, i.e. predicted, frames. - -CPU cycles are saved on account of physics and navmesh related agents skipping updates for any entity with the relevant component (PhysicsCollider, PhysicsBody, NavMeshPathFinder, NavMeshSteeringAgent, NavMeshAvoidanceAgent) and outside the area of interest as defined by the Prediction Area center point and radius on the local machine. - -Back To Top - - -Iterators -Your own code will also benefit from Prediction Culling. Any filter that includes a Transform2D or Transform3D will be subject to culling based on their positions. - -Essentially whenever a prediction frame is running, calling any of the methods below will only return entities within the limits o the prediction radius, while the same call will return all active instances when simulating the verified frames after input confirmations arrive). - -f.Filter() -f.Unsafe.FilterStruct() -N.B.: While filters benefit from Prediction Culling, component iterators do NOT. - -f.GetComponentIterator() -f.Unsafe.GetComponentBlockIterator() -Back To Top - - -Manual Culling Control Flags -It is also possible to manually flag entities for culling on predicted frames via the API provided via the Frame. - -Method Description -SetCullable(EntityRef entityRef, bool cullable) Sets if an entity can be culled or not. Does nothing if the entity does not exist (including invalid entity refs). -IsCulled(EntityRef entityRef) If an entity is currently culled from the simulation, regardless of the frame state (Predicted or Verified). -True if the entity is culled (for instance, not inside the prediction area) or does not exist. -False otherwise (if the entity exists and is not being culled). -Culled(EntiyRef entityRef) If an entity is prediction-culled. -True if the frame is Predicted AND the entity IsCulled. -False otherwise (if the frame is Verified or the entity is not culled). -Cull(EntiyRef entityRef) Manually marks a cullable and existing entity as culled for this tick. Does nothing if the entity does not exist or is not cullable. -ClearCulledState() Resets the culling state of all entities on that frame. Called automatically at the beginning of every frame simulation. -To keep a consistent state and avoid desync, de-flag the culled entities on verified frames in the same systems you originally flag them. from the same system, so you keep a consistent state and do not desync. - -Back To Top - - -Avoiding RNG Issues -Using RNGSession instances with Prediction Culling is perfectly safe and determinism is guaranteed. However, their combined use can result in some visual jitter when two entities share a RNGSession, such as the default one stored in Quantum's _globals_. This is due to new a RNG value being generated for a predicted entity after a verifited frame was simulated, thus changing/modifying the entity's final position. - -The solution is to store an isolated RNGSession struct in each entity subject to culling. The isolation guarantees culling will not affect the final positions of predicted entities unless the rollback actually required it. - -struct SomeStruct { - RNGSession MyRNG; -} -You can inject each RNGSession with their seeds in any way you desire. \ No newline at end of file diff --git a/data/profiling.txt b/data/profiling.txt deleted file mode 100644 index 9ac69a4ed219af600e3a7a1ac78944c2eeabf027..0000000000000000000000000000000000000000 --- a/data/profiling.txt +++ /dev/null @@ -1,99 +0,0 @@ -Introduction -Profiling in general is a good tool to find the relative performance between parts of the code and enable developers to drill down on hotspots. But it's not useful to find absolute performance measurements because profiling tools, especially Unity's, impact the performance with their overhead. - -The recommended performance analysis path is: - -Measure simulation times, rendering times, etc using a Quantum Release build (quantum solution) and an IL2CPP Unity build only the Quantum Graph Profiler attached to give overall numbers over time. -Then, with the rough idea where to look (is it the simulation, is it rendering, etc), follow up with a profiling session using the Unity Profiler or Quantum Task Profiler. -Keep in mind that a Quantum debug build can be 5x slower than a release build. As well as that a debug+mono build can be 10x slower that a release+il2cpp build. - -Back To Top - - -Unity Profiler -Quantum performance stats are integrated into the Unity Profiler and are started by default inside the QuantumRunner script. - -Quantum.Profiling.HostProfiler.Init(..) -You can add custom sections in your Quantum simulation code by this know Unity Profiler pattern: - -HostProfiler.Start("Foo"); -{ - HostProfiler.Start("Bar1"); - // do work - HostProfiler.End(); - - HostProfiler.Start("Bar2"); - // do work - HostProfiler.End(); -} -HostProfiler.End(); -With the most current Quantum SDK versions (2.1) Quantum also supplies data for the Timeline profiler in Unity. Quantum only provides profiling data in Debug configuration. - -Back To Top - - -Quantum Task Profiler -The Quantum Task Profiler is a custom and stand-alone graphical performance profiler for Unity similar to the Unity Timeline profiler. It only provides data when the quantum solution is compiled in Debug or in ReleaseProfiler configuration (the latter being added in Quantum 2.1). Similar to the Unity Profiler an app running on a remote device connects to the Unity Editor via UDP inside the same local network. - -Quantum Task Profiler -Quantum Task Profiler -Back To Top - - -Remote Profiling -It is possible to remotely hook into the Quantum Task Profiler to build running on the same network as the editor (UDP Port 30000). To enable the feature, simply toggle the Remote Profiler checkbox at the bottom of the QuantumEditorSettings asset. Close and reopen the Task Profiler View afterwards. - -Profiling Graphs -Toggle for the Remote Profiler in the QuantumEditorSettings -Back To Top - - -Quantum Graph Profiler -The Quantum Graph provider is an extra tool that can be integrate into an app to visually analyze performance and network statistics. - -Please download the version for your Unity Editor: - -Unity Version Release Date Download -Unity 2018.4 Jan 30, 2020 Profilers_Q2_20201030_Unity2018 -Unity 2019.4+ Jan 21, 2022 QuantumProfilers_20220121 -Back To Top - - -Real-Time Profiling -These runtime graphs help tracking the overall performance of the game and the Quantum simulation under various network conditions. The graphs and their values are based on the Unity update rate where each value equals the accumulated time/count/etc... in a single Unity frame. - -The profiler offers graphs for: - -Engine Delta Time: equals Time.unscaledDeltaTime between Unity frames. Sometimes Engine Delta Time may not reflect the target FPS, to fix this set QualitySettings.vSyncCount = 0; -Frame Time: all scripts logic including Unity internal and rendering, but excluding the wait for end of frame; -User Scripts Time: the time to run FixedUpdate() + Update() + LateUpdate(); -Render Time: equals time from last LateUpdate() until the end of render; -Simulation Time: equals QuantumRunner.Default.Game.Session.Stats.UpdateTime; -Predicted Frames: the amount of predicted Quantum frames simulated in a Unity frame equals QuantumRunner.Default.Game.Session.PredictedFrames; -Verified Frames: the amount of verified Quantum frames simulated in a Unity frame; -Network Activity: the time since the last data transmission from the server; -Ping: network peer round trip time (RTT); -Markers: up to 8 custom boolean can track values using markers. Each marker is represented by unique color; by default Red = input replaced by server and Orange = checksum calculated. -Profiling Graphs -Real-Time Profiling Graphs -Back To Top - - -A Note On Markers -For better legibility, the markers graph is running 2x faster than the others. This can be adjusted via the Samples property on the Profilers prefab. - -Multiple instances of MarkersProfiler are supported: - -Get an instance by name MarkersProfiler profiler = MarkersProfiler.Get(GAMEOBJECT_NAME); -Call profiler.SetMarker(INDEX); -Back To Top - - -Other Tools -The real-time profiling tool also contains other (more basic) tools for: - -changing target FPS (Application.targetFrameRate); and, -to simulate network conditions (lag, jitter, loss). -These are useful to quickly simulate different rendering speeds and bad networks. The effects can be seen immediately in graphs (predicted frames, simulation time, ...). - -N.B.: When simulating network loss, set values carefully. Use 1-3% to simulate loss on network and higher values to simulate local loss (e.g. bad connection to router behind 3 walls). \ No newline at end of file diff --git a/data/quantum 100.txt b/data/quantum 100.txt deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/data/quantum overview.txt b/data/quantum overview.txt deleted file mode 100644 index 85c2b0ef55f36e3c3ca96043eebf23adc3f669ee..0000000000000000000000000000000000000000 --- a/data/quantum overview.txt +++ /dev/null @@ -1,162 +0,0 @@ -Overview -Photon Quantum is a high-performance deterministic ECS (Entity Component System) framework for online multiplayer games made with Unity. - -It is based on the predict/rollback approach which is ideal for latency-sensitive online games such as action RPGs, sports games, fighting games, FPS and more. - -Quantum also helps the developer to write clean code, fully decoupling simulation logic (Quantum ECS) from view/presentation (Unity), while also taking care of the network implementations specifics (internal predict/rollback + transport layer + game agnostic server logic): - -Quantum implements a state-of-the-art tech stack composed of the following pieces: - -Server-managed predict/rollback simulation core. -Sparse-set ECS memory model and API. -Complete set of stateless deterministic libraries (math, 2D and 3D physics, navigation, etc.). -Rich Unity editor integration and tooling. -All built on top of mature and industry-proven existing Photon products and infrastructure (photon realtime transport layer, photon server plugin to host server logic, etc.); - -Determinism Without Lockstep -In deterministic systems, game clients only exchange player input with the simulation running locally on all clients. In the past, this has used a lockstep approach, in which game clients would wait for the input from all other players before updating each tick/frame of the simulation. - -In Quantum, however, game clients are free to advance the simulation locally using input prediction, and an advanced rollback system takes care of restoring game state and re-simulating any mispredictions. - -Because Quantum also relies on a game-agnostic authoritative server component (photon server plugin) to manage input latency and clock synchronization, clients never need to wait for the slowest one to rollback/confirm the simulation forward: - -Quantum Server-Managed predict/Rollback -In Quantum, deterministic input exchange is managed via game-agnostic server logic. This prevents game clients with bad network from interfering with the experience of players on good networks. -These are the basic building blocks of a Quantum game: -Quantum Server Plugin: manages input timing and delivery between game clients, acts as clock synchronization source. Can be extended to integrate with customer-hosted back-end systems (matchmaking, player services, etc.). -Game Client Simulator: communicates with Quantum server, runs local simulation, performing all input prediction and rollbacks. -Custom Gameplay Code: developed by the customer as an isolated pure C# simulation (decoupled from Unity) using the Quantum ECS. Besides providing a framework for how to organize high-performance code, Quantum's API offers a great range of pre-built components (data) and systems (logic) that can be reused in any game such as deterministic 3D vector math, 2D and 3D physics engines, navmesh pathfinder, etc. - -Old School Coding -Starting from the assumption that all simulation code must be high-performance out of the box, Quantum internal systems are all designed with that in mind from the ground up. - -The key to Quantum's high performance is the use of pointer-based C# code combined with its sparse-set ECS memory model (all based memory aligned data-structs and a custom heap allocator - no garbage collection at runtime from simulation code). - -The goal is to leave most of the CPU budget for view/rendering code (Unity), including here the re-simulations induced by input mispredictions, inherent to the predict/rollback approach: - -Quantum hyper-fast predict/rollback -Quantum is designed to make your simulation code run as fast as possible, leaving most of the CPU budget for rendering updates. -Although the use of pointer-based C# is exposed (for performance), most of the complexity is hidden away from the developer by the clever use of a custom DSL and automatic code generation. - -Back To Top - - -Code Generation -In Quantum, all gameplay data (game state) is kept either in the sparse-set ECS data structures (entities and components) or in our custom heap-allocator (dynamic collections and custom data), always as blittable memory-aligned C# structs. - -To define all data structures that go into that, the developer uses a custom DSL (domain specific language) that lets him concentrate on the game concepts instead of performance-oriented restrictions: - -// components define reusable game state data groups - -component Resources -{ - Int32 Mana; - FP Health; -} - -// structs, c-style unions, enums, flags, etc, can be defined directly from the DSL as well -struct CustomData -{ - FP Resources; - Boolean Active; -} -The code-snippet above would generate the corresponding types (with explicit memory alignment), serialization code, and a all boiler plate control logic for special types (like components). - -The auto-generated API lets you both query and modify the game state with comprehensive functions to iterate, modify, create or destroy entities (based on composition): - -var es = frame.Filter(); -// Next fills in copies of each of the components + the EntityRef -while (es.NextUnsafe(out var entity, out var transform, out var resources)) { - transform->Position += FPVector3.Forward * frame.DeltaTime; -} -Back To Top - - -Stateless Systems -While Quantum's DSL covers game state data definition with concepts such as entities, components and auxiliary structures (structs, enums, unions, bitsets, collections, etc.), there needs to be a way to organize the custom game logic that will update this game state. - -You write custom logic by implementing Systems, which are stateless pieces of logic that will be executed every tick update by Quantum's client simulation loop: - -public unsafe class LogicSystem : SystemMainThread -{ - public override void Update(Frame f) - { - // your game logic here (f is a reference for the generated game state container). - } -} -The Systems API game loop call order, signals for system intercommunication (both custom and pre-built, such as the physics engine collision callbacks), events and several other extension hooks. - -Back To Top - - -Events -While the simulation is implemented in pure C#, without referencing Unity's API directly, there are two important features to let gameplay code communicate with the rendering engine: events and the asset linking system. - -Events are a way for the game code to inform the rendering engine that important things happened during the simulation. One good example is when something results in damage to a character. - -Using the state from the previous section as a basis, imagine that damage reduces the health value from the resources component of a character entity. From the Unity rendering scripts, the only noticeable data will be the new health value itself, without any way to know what caused the damage, and also what was the previous health value, etc. - -Event definition in a file DSL: - -event Damage -{ - entity_ref Character; - FP Amount; -} -Gameplay code raises events as a simple API call (generated): - -public void ApplyDamage(Frame f, EntityRef c, FP amount) -{ - // missing here, the logic to apply damage to the character itself - - // this sends an event to the "view" (Unity) - f.Events.Damage(amount, c); -} -Quantum's event processor will handle all generated events after the tick update is done, taking care of events that require server-confirmed input, event repetitions, and also cancelation/confirmation when simulation rollbacks do occur. - -Events raised from the simulation code can then be consumed in runtime from callbacks created in Unity scripts: - -public void OnDamage(DamageEvent dmg) -{ - // instantiate and show floating damage number above the target character, etc -} -Back To Top - - -Asset Linking -Unity is known for its flexible editor and smooth asset pipeline. The Asset Linking system allows game and level designers to create and edit data-driven simulation objects from the Unity Editor, which are then fed into the simulation. This is essential to prototype and to add final balancing touches to the gameplay. - -From the C# simulation project, the developer creates a data-driven class exposing the desired attributes: - -public partial class CharacterClass -{ - public Int32 MaxMana; - public FP MaxHealth; -} -Then from Unity, level designers can create as many instances of this asset as needed, each one being automatically assigned with a Unique GUID: - -Character Classes - Asset Linking -Example of the Asset Linking system: data-driven character-class asset containers being created/modified directly from the Unity Editor. -Then, programmers can use data from these assets directly from inside the simulation: - -var data = frame.FindAsset("character_class_id"); -var mana = data.MaxMana; -It's also possible to reference these assets directly in components from the state definition DSL: - -component CharacterAbilities -{ - asset_ref CharacterData; -} -Back To Top - - -Deterministic Library -In Quantum, the simulation needs to compute the same results on all clients, given they use the same input values. This means it has to be deterministic, which implies neither using any float or doubles variables, nor anything from the Unity API such as their vectors, physics engine, etc. - -To help game developers to implement this style of gameplay, Quantum comes bundled with a flexible and extensible deterministic library, both with a set of classes/struct/functions and also some components that can be used directly when defining entities with the DSL. - -The following modules are available: - -Deterministic math library: FP (Fixed Point) type (Q48.16) to replace floats/doubles, FPVector2, FPVector3, FPMatrix, FPQuaternion, RNGSession, FPBounds2, and all extra math utils including safe casts, and parsers from native types. The math library is implemented with performance as a primary goal, so we make intense use of inlining, lookup tables and fast operators whenever possible. -2D and 3D Physics Engines: high performance stateless 2D/3D physics engines with support for static and dynamic objects, callbacks, joints, etc. -NavMesh/PathFinder/Agents: includes both an exporter from an existing Unity navmesh or an editor to manipulate the mesh directly. Also includes industry standard HRVO collision avoidance, funneled paths, and many more features. \ No newline at end of file diff --git a/data/quantum project.txt b/data/quantum project.txt deleted file mode 100644 index 237294fbe77dca82cf03eee8633593f3c90738be..0000000000000000000000000000000000000000 --- a/data/quantum project.txt +++ /dev/null @@ -1,300 +0,0 @@ -Introduction -A collection of background information, tutorials and best-practices to customize the integration of the Quantum SDK into specialized workflows. - -Back To Top - - -Release And Debug Builds -The Quantum project outputs its dll and references directly into the Unity project (by default QuantumSDK\quantum_unity\Assets\Photon\Quantum\Assemblies\). Depending on the build configuration selected in Visual Studio (or Rider) the dlls from QuantumSDK\assemblies\debug or \release are referenced and copied. - -To switch from a debug to a release build, rebuild the quantum solution with the desired configuration. - -Build Configuration -Toggle Build Configuration In Visual Studio -Rebuild -Rebuild The Quantum Solution In Visual Studio -The debug build will make it possible to debug the quantum.code.csproj and place breakpoints. After rebuilding the solution, attach Visual Studio (or Rider) to the running Unity Editor. - -The debug build has significant performance penalties compared to a release build. For performance tests always use a Quantum release build (and Unity IL2CPP). Read more about this in the profiling section. - -The debug build contains assertions, exceptions, checks and debug outputs that help during development and which are disabled in release configuration. For example: - -Log.Debug() and Log.Trace(), for example called from the quantum code project, will not be outputting log anymore. -As well as all Draw.Circle() methods. -NavMeshAgentConfig.ShowDebugAvoidance and ShowDebugSteering will not draw gizmos anymore. -Assertions and exceptions inside low level systems like physics are disabled. -Back To Top - - -Quantum-Unity Code Integration -A guide to demonstrate how to import and keep the Quantum simulation code in Unity. - -Note: The procedure requires Unity 2019.4 and up. - -The default way of working with Quantum is to have the simulation code (quantum_code) completely separate from Unity (quantum_unity). The double solution approach is not to everyone's liking, so with Quantum v2 we introduced an option to include quantum_code projects in the solution Unity generates with QuantumEditorSettings.MergeWithVisualStudioSolution setting. However, there are still use cases where having simulation code inside of Unity may be desirable. For instance, it lets users modify and rebuild simulation code without a license for Visual Studio or Rider. - -You can convert your project to use this approach. - -IMPORTANT: This is a one-way conversion. - -Any files that you add/remove in Unity will not be added to/removed from quantum_code/quantum.code/quantum.code.csproj. This is not a problem if do not intend to use the project; if you plan on using the console runners and/or server plug-ins, you will have to update the project manually yourself. - -Back To Top - - -Integration Steps -Delete quantum_unity/Assets/Photon/Quantum/Assemblies/quantum.code.dll -Copy tools/codeintegration_unity/QuantumCodeIntegration and tools/codeintegration_unity/QuantumCode to quantum_unity/Assets/Photon -Copy everything (except for bin, obj and Properties directories) from quantum_code/quantum.code to quantum_unity/Assets/Photon/QuantumCode -If you get compile errors due to generated code being missing after opening the Unity project, run the codegen via the Quantum/Code Integration/Run All CodeGen menu. - -Back To Top - - -Gotchas -PhotonQuantumCode.asmdef explicitly removes Unity assemblies references. This is to ensure the nondeterministic Unity code is not mixed with the simulation code; this ensures there's always a way back to quantum_code as a standalone project. -N.B.: Any issues arising from including Unity assemblies will not receive any support. - -If for whatever the reason you happen to run into a "chicken and egg" problem (cannot compile because codegen is out of date, cannot run codegen because there are compile errors) and there is no Quantum/Code Integration menu, you can always run the codegen manually via the console (on non-Windows platforms prefix these with mono): -tools/codegen/quantum.codegen.host.exe quantum_unity/Assets/Photon/QuantumCode - -tools/codegen_unity/quantum.codegen.unity.host.exe quantum_unity/Library/ScriptAssemblies/PhotonQuantumCode.dll quantum_unity/Assets - -Back To Top - - -Quantum DSL Integration -This section is about how to integrate the Quantum DSL files and their compilation into a workflow. - - -Qtn File Discovery -The qtn-files will be compiled as a pre build step of the quantum_code.csproj by calling tools\codegen\quantum.codegen.host.exe as a pre build step with either a cs-proj file or a folder as paramter. There are two ways we recommend to set up the codegen: - -1. (DEFAULT) Add the qtn-files explicitly to quantum_code.csproj: - - - - - -The PreBuildEvent looks like this: - -# win -"$(ProjectDir)..\..\tools\codegen\quantum.codegen.host.exe" "$(ProjectPath)" -# mac -mono "$(ProjectDir)..\..\tools\codegen\quantum.codegen.host.exe" "$(ProjectPath)" -2. If tools\codegen\quantum.codegen.host.exe is called with a folder instead of a file it will search for every qtn-file inside the given folder. - -Change the PreBuildEvent of quantum_code.csproj to this: - -# win -"$(ProjectDir)..\..\tools\codegen\quantum.codegen.host.exe" "$(ProjectDir)" -# mac -mono "$(ProjectDir)..\..\tools\codegen\quantum.codegen.host.exe" "$(ProjectDir)" -Back To Top - - -Qtn File Syntax Highlighting -To enable syntax highlighting in QTN files, follow the guide for your respective IDE. - - -Visual Studio -In Visual Studio, you can add syntax highlighting for QTN files by associating it with another type (e.g. C# or Microsoft Visual C++). To do this go to Tools -> Options -> Text Editor -> File Extension. - -Back To Top - - -JetBrains Rider -In JetBrains Rider, you can add syntax highlighting to QTN file by defining a new file type. - -Step 1: Navigate to File->Settings->Editor->File Types. -File Types -The `File Types` settings in JetBrains Rider. -Step 2: In the Recognized File Types category, press the + sign at the right of the to add a new file type. -New File Type -The `New File Type` window in JetBrains Rider. -Step 3: Check the settings for line comments, block comments, etc... -Step 4: Paste the list into the keywords level 1 (see below). -#define -#pragma -abstract -any -array -asset -asset_ref -bitset -button -byte -collision -component -dictionary -entity -entity_ref -enum -event -fields -filter -flags -global -has -import -input -int -list -local -long -not -player_ref -remote -sbyte -set -short -signal -struct -synced -uint -ulong -union -use -ushort -using -Step 5: Paste the list into the keywords level 2 (see below). -( -) -* -: -; -< -= -> -? -[ -] -{ -} -Step 6: In the File Name Patterns category, press the + sign at the right. -Step 7: Enter *.qtn as the wildcard for the type. -DSL Syntax Highlighting -DSL Syntax Highlighting in .QTN files (JetBrains Rider). -Back To Top - - -Quantum Code Generation Tools -Quantum uses two code generation tools that are required to run before and after the quantum.code.dll compilation. - -Before compilation: Quantum Codegen (CodeGen.cs) - -After compilation: Quantum Unity Codegen (Unity scripts) - -Back To Top - - -Quantum Codegen -Executes the Quantum DSL code generation by converting found qtn files to C# code (Core/CodeGen.cs). Has two modes, one selects all qtn files recursively while the other only checks explicitly names qtn files in the csproj file. - -Location tools\codegen\quantum.codegen.host.exe -Platform Windows, Mono -Usage quantum.codegen.host.exe project-folder|project-file -project-folder The path to the folder that the quantum.code.csproj is located. This mode will select all qtn files found recursively in the provided folder. -project-file The path of the quantum.code.csproj file. This mode will select all qtn files that are explicitly listed as items: - - - -Back To Top - - -Quantum Unity Codegen -Runs the Quantum codegen part that generates the Unity asset scripts (AssetBase), editors, prototype classes and the AOT file, which includes necessary class and generic declarations for AOT compilers. - -Location tools\codegen_unity\quantum.codegen.unity.host.exe -Platform Windows, Mono -Usage quantum.codegen.unity.host.exe AssemblyPath OutputDir -AssemblyPath Path to quantum.code.dll file. -OutputDir Output folder for Unity scripts. Usually quantum_unity\Assets. See default paths and how to customize them below. -Back To Top - - -Overwrite Asset Script Location And Disable AOT File Generation -Create the file tools\codegen_unity\quantum.codegen.unity.dll.config. - -Caveat: The file will be overwritten during the default upgrade procedure. - - - - - - - - - - -Back To Top - - -Quantum Unity Codegen (Netcore) -This version of the Quantum codegen Unity tool will be able to load a quantum.code.dll compiled with netstandard2.0. - -Location tools\codegen_unity\netcoreapp3.1\quantum.codegen.unity.host.exe -Platform Windows, Linux -Usage quantum.codegen.unity.host.exe --additional-deps AdditionalDepsDir -AssemblyPath Path to quantum.code.dll file. -OutputDir Output folder for Unity scripts. Usually quantum_unity\Assets. See default paths and how to customize them below. -AdditionalDepsDir Additional dependencies are required to load the quantum dlls which are usually located in assemblies\release. -Set up as PostBuildEvent from Visual Studio: - -"$(ProjectDir)..\..\tools\codegen_unity\netcoreapp3.1\quantum.codegen.unity.host.exe" "$(TargetDir)\quantum.code.dll" "$(ProjectDir)..\..\quantum_unity\Assets" --additional-deps "$(ProjectDir)..\..\assemblies\$(Configuration)" - - To Document Top - -Hide Sidebar -Search … -PRODUCTS -QUANTUM | v2v1 -API Reference -Getting Started - -Quantum 100 - -Game Samples - -Technical Samples - -AddOns - -Manual - -Quantum Ecs - -Animation -Assets - -Cheat Protection -Commands -Configuration Files -Custom Server Plugin - -Entity Prototypes -Frames -Game Session - -Git Ignore Files -Input -Materialization -Multi-Client Runner -Navigation - -Physics - -Player - -Prediction Culling -Profiling -Quantum Project -WebGL -Concepts And Patterns - -Consoles - -Reference - -Languages English , 日本語 , 한국어 , 繁体中文 diff --git a/data/quantum.code.csproj b/data/quantum.code.csproj deleted file mode 100644 index 7eec4ac7ad681f24975ba6864c19637d01bfe59d..0000000000000000000000000000000000000000 --- a/data/quantum.code.csproj +++ /dev/null @@ -1,98 +0,0 @@ - - - - - Debug - AnyCPU - {FBF32099-B197-4AB9-8E5A-B44D9D3750BD} - Library - Properties - Quantum - quantum.code - v4.6.2 - 512 - - - - true - portable - false - ..\..\Quantum 8Ballpool\Assets\Photon\Quantum\Assemblies\ - TRACE;DEBUG;PROFILER_REPORT - prompt - 4 - true - false - latest - true - - - pdbonly - true - ..\..\Quantum 8Ballpool\Assets\Photon\Quantum\Assemblies\ - PROFILER_REPORT - prompt - 4 - true - false - latest - true - - - - - - False - ..\..\assemblies\release\PhotonDeterministic.dll - ..\..\assemblies\debug\PhotonDeterministic.dll - - - False - ..\..\assemblies\release\quantum.core.dll - ..\..\assemblies\debug\quantum.core.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "$(ProjectDir)..\..\tools\codegen_unity\quantum.codegen.unity.host.exe" "$(TargetDir)\quantum.code.dll" "$(SolutionDir)..\Quantum 8Ballpool\Assets" - "$(ProjectDir)..\..\tools\codegen\quantum.codegen.host.exe" "$(ProjectPath)" - mono "$(ProjectDir)..\..\tools\codegen_unity\quantum.codegen.unity.host.exe" "$(TargetDir)\quantum.code.dll" "$(SolutionDir)..\Quantum 8Ballpool\Assets" - mono "$(ProjectDir)..\..\tools\codegen\quantum.codegen.host.exe" "$(ProjectPath)" - - \ No newline at end of file diff --git a/data/queries.txt b/data/queries.txt deleted file mode 100644 index 23c003c2c7ab6f54a3c22a4fd10c1386bb6b81a0..0000000000000000000000000000000000000000 --- a/data/queries.txt +++ /dev/null @@ -1,264 +0,0 @@ -Introduction -Queries may take into account dynamic entities and static colliders. The API for rays, lines and shape overlaps is very similar in that is always results a collection of hits (with the same kind of data in their fields). - -Back To Top - - -Queries - -Linecast And Raycast -// For 2D -var hits = f.Physics2D.LinecastAll(FPVector2.Zero, FPVector2.One); -for (int i = 0; i < hits.Count; i++) { - var hit = hits[i]; -} - -// For 3D -var hits = f.Physics3D.LinecastAll(FPVector3.Zero, FPVector3.One); -for (int i = 0; i < hits.Count; i++){ - var hit = hits[i]; -} -The resulting HitCollection object, contains the following properties: - -Each item in the HitCollection holds an EntityRef or Static collider info. They mutually exclusive - one will be valid, and the other null; -Count should always be used to iterate over the HitCollection; and, -Hits are not sorted. You can sort them by calling Sort() and passing in a FPVector2, this will result in the hits being sorted according to their distance to the reference point provided to the function. -Raycasts are syntax-sugar for Linecasts. They work the same and simply require a start, direction and max-distance instead of start and end. Additionally, you may pass these optional parameters to the linecast and raycast: - -LayerMask, to specify which physics layers to perform the cast against; and, -QueryOptions, to specify the type of collider to consider in the cast. -Back To Top - - -Shape Queries -Quantum supports two different types of shape queries: - -ShapeOverlap; and, -ShapeCasts (since v2.1). -These can be used with all dynamic shapes supported in Quantum. - -Note: CompoundShapes can be used for performing shape queries. For more information, please read the Shape Config page. - -Back To Top - - -ShapeOverlaps -OverlapShape() returns a HitCollection. The required parameters are: - -a center position (FPVector2 or FPVector3); -a rotation (FP or FPQuaternion for the 3D equivalent); and, -a shape (Shape2D or Shape3D - either from a PhysicsCollider, or created at the time of calling). -// For 2D -var hits = f.Physics2D.OverlapShape(FPVector2.Zero, FP._0, Shape2D.CreateCircle(FP._1)) -for (int i = 0; i < hits.Count; i++){ - var hit = hits[i]; -} - -// For 3D -var hits = f.Physics3D.OverlapShape(FPVector3.Zero, FPQuaternion.Identity, Shape3D.CreateSphere(1)); -for (int i = 0; i < hits.Count; i++){ - var hit = hits[i]; -} -Back To Top - - -ShapeCasts -ShapeCasts (2D & 3D) are available since Quantum 2.1 . - -ShapeCastAll() returns a HitCollection. The required parameters are: - -the center position ( FPVector2 or FPVector3); -the rotation of the shape ( FP or FPQuaternion for the 3D equivalent); -the shape pointer ( _Shape2D* _ or Shape3D* - either from a PhysicsCollider, or created at the time of calling); and, -the distance and direction expressed as a vector ( FPVector2 or FPVector3 ). -// For 2D -var shape = Shape2D.CreateCircle(FP._1); -var hits = f.Physics2D.ShapeCastAll(FPVector2.Zero, FP._0, &shape, FPVector2.One); -for (int i = 0; i < hits.Count; i++){ - var hit = hits[i]; -} - -// For 3D -var shape = Shape3D.CreateSphere(1); -var hits = f.Physics3D.ShapeCastAll(FPVector3.Zero, FPQuaternion.Identity, &shape, FPVector3.One); -for (int i = 0; i < hits.Count; i++){ - var hit = hits[i]; -} -It uses a custom GJK-based algorithm. The GJKConfig settings are available in the SimulationConfig asset's Physics > GJKConfig section. The settings allow to balance accuracy and performance as both come with their trade-offs. The default values are balanced to compromise for regular sized shapes. - -Simplex Min/Max Bit Shift: Allows better precision for points in the Voronoy Simplex by progressively shifting their raw values, avoiding degenerate cases without compromising the valid range of positions in the Physics space. Consider increasing the values if the scale of the shapes involved and/or the distance between them is very small. -Shape Cast Max Iterations: The max number of iterations performed by the algorithm while searching for a solution below the hard tolerance. Increasing it might result in more accurate results, at the cost of performance in worst-case scenarios, and vice-versa. -Shape Cast Hard Tolerance: An iteration result (closest distance between the shapes) below this threshold is acceptable as a finishing condition. Decreasing it might result in more accurate results, at the cost of more iterations, and vice-versa. -Shape Cast Soft Tolerance: A shape cast resolution that fails to find an acceptable result below the defined Hard Tolerance within the Max Iterations allowed will still return positive if the best result found so far is below this soft threshold. In these cases, increasing this threshold enhances the probability of false-positives, while decreasing it enhances false-negatives. -Back To Top - - -Sorting Hits -All queries returning a HitCollection can be sorted. - -Sort(): takes a FPVector2 in 2D and a FPVector3 in 3D and sorts the collection according to the hits' respective distance to the point provided. -SortCastDistance(): used for sorting the results of ShapeCast query. It takes no arguments and orders the hits based on the cast distance. -Back To Top - - -Options -All queries, including their broadphase version, can use QueryOptions to customize the operation and its results. QueryOptions create a mask that filters which types of objects are taken into account and what information will be computed. You can combined these by using the binary | operator. - -Back To Top - - -Hit Normals -To offer the most performant query, all default queries only check whether the two shapes are overlapping. - -In order to receive additional information, more computation will be needed which in turn creates additional overhead; it is therefore necessary you explicitly specify it by passing ComputeDetailedInfo as the QueryOptions parameter. This will enable the computation of the hit's: - -point -normal -penetration -For ray-triangle checks the normal is always the triangle's normal. Since this is cached in the triangle data, there is no additional computation in this case. - -Back To Top - - -Filtering Hits -The following QueryOptions allow you to define the mask used by the query. If an object does not match the QueryOptions specified as the parameter, it will be skipped; only objects matching the QueryOptions will be evaluated and returned in the result. - -HitStatics : will only hit static colliders -HitKinematics : will hit entities who meet any of the following conditions: -entities with a PhysicsCollider and no PhysicsBody -entities with a PhysicsCollider and a disabled PhysicsBody -entities with a PhysicsCollider and a kinematic PhysicsBody -HitDynamics : will only hit entities with an enabled and non-kinematic PhysicsBody -HitTriggers : has to be used in combination with other flags to hit trigger colliders. -HitAll : will hit all entities that have a PhysicsCollider -By default, a query will use the HitAll option. Choosing any other option will save computation. - -Back To Top - - -Broadphase Queries -Quantum comes with an option for injecting physics queries (ray-casts and overlaps) to be resolved during the physics systems. For this you need to: - -Create a system. -Insert it before Core.PhysicsSystem when adding it to SystemSetup.cs. -Retrieve the information in any system running after Core.PhysicsSystem. -This setup benefits from the parallel resolution on physics steps which makes it significantly faster than normal querying after physics. - -public static class SystemSetup { - public static SystemBase[] CreateSystems(RuntimeConfig gameConfig, SimulationConfig simulationConfig) { - return new SystemBase[] { - - // pre-defined core systems - new Core.CullingSystem2D(), - new Core.CullingSystem3D(), - - // Placing systems scheduling Broadphase queries here - // allows them to benefit from the CullingSystems on predicted frames. - new ProjectileHitQueryInjectionSystem(), - - new Core.PhysicsSystem2D(), - new Core.PhysicsSystem3D(), - - new Core.NavigationSystem(), - new Core.EntityPrototypeSystem(), - - // user systems go here - // This is also where systems retrieving the results of broadphase queries go - new ProjectileHitRetrievalSystem(), - }; - } -} -Note: Sometimes broadphase queries are also referred to as injected queries or scheduled queries, because they are scheduled/injected into the physics engine before the solver runs. - -Back To Top - - -Injecting Queries -You can inject a query from any main thread system running before physics. The injected query will return a 0-based index, using this same index you will be able to retrieve the results after the physics system ran. The query's index is meant to be generated and consumed within the frame, therefore it can be stored anywhere - including outside the rollback-able frame data. - -namespace Quantum -{ - public unsafe struct ProjectileFilter - { - public EntityRef EntityRef; - public Transform3D* Transform; - public Projectile* Component; - } - - public unsafe class ProjectileHitQueryInjectionSystem : SystemMainThread - { - public override void Update(Frame f) - { - var projectileFilter = f.Unsafe.FilterStruct(); - var projectile = default(ProjectileFilter); - - while (projectileFilter.Next(&projectile)) - { - projectile.Component->PathQueryIndex = f.Physics3D.AddRaycastQuery( - projectile.Transform->Position, - projectile.Transform->Forward, - projectile.Component->Speed * f.DeltaTime); - - var spec = f.FindAsset(projectile.Component->WeaponSpec.Id); - - projectile.Component->DamageZoneQueryIndex = f.Physics3D.AddOverlapShapeQuery( - projectile.Transform->Position, - projectile.Transform->Rotation, - spec.AttackShape.CreateShape(f), - spec.AttackLayers); - } - } - } -} -IMPORTANT: The query indices returned by AddXXXQuery are absolutely necessary to retrieve the results of the queries later on. It is thus advisable to save in a component attached to the entity who will need to process the hits down the line. - -Back To Top - - -Retrieving Query Results -The query results can be retrieved from any system that runs after the physics. To retrieve the results (HitCollection*) you need the pass the index previous saved into Frame.Physics.GetQueryHits(). - -using Photon.Deterministic; - -namespace Quantum -{ - public unsafe class ProjectileHitRetrievalSystem : SystemMainThread - { - public override void Update(Frame f) - { - var projectileFilter = f.Unsafe.FilterStruct(); - var projectile = default(ProjectileFilter); - - while (projectileFilter.Next(&projectile)) - { - var hitsOnTrajectory = f.Physics3D.GetQueryHits(projectile.Component->PathQueryIndex); - if (hitsOnTrajectory.Count <= FP._0) - { - projectile.Transform->Position = - projectile.Transform->Rotation * - projectile.Transform->Forward * - projectile.Component->Speed * f.DeltaTime; - continue; - } - - var damageZoneHits = f.Physics3D.GetQueryHits(projectile.Component->DamageZoneQueryIndex); - - for (int i = 0; i < damageZoneHits.Count; i++) - { - // Apply damage logic - } - } - } - } -} -In addition to that, you can grab all broadphase results via the public bool GetAllQueriesHits(out HitCollection* queriesHits, out int queriesCount) call which is also available via Frame.Physics. - -Back To Top - - -Note -A few important points to keep in mind when using broadphase queries: - -The performance is around 20x better for large numbers (e.g. projectiles). -They are based on the frame state before the Physics system kicks in. -Broadphase queries do not carry over between frames; i.e. they need to be injected at the start of a frame before the Physics. A broadphase query injected after the Physics has run will never return a result. This is because Quantum's Physics are stateless. \ No newline at end of file diff --git a/data/reconnecting.txt b/data/reconnecting.txt deleted file mode 100644 index 840013a1406bee4f776fb35a63fed14e88697bb0..0000000000000000000000000000000000000000 --- a/data/reconnecting.txt +++ /dev/null @@ -1,353 +0,0 @@ -The following documentation will shed light into all aspects of reconnecting into a running Quantum session. The basic flow is implemented in the demo menu sample that is shipped with the Quantum SDK. - -The reconnection process consists of two main parts: How to get back into the Photon Realtime room and what to do the Quantum simulation? - -Detecting Disconnects -Photon Realtime Fast Reconnect -Requirements: PlayerTTL -Requirements: RoomTTL (Waiting For Snapshots) -Requirements: Photon UserId -Possible Error: ReconnectAndRejoin Returns False -Possible Error: PlayerTTL Ran Out -Possible Error: Authentication Token Timeout -Possible Error: Connection Still Unavailable -Reconnecting After App Restart -Different Master Server -Other Photon Realtime Topics -Best Region Summary -AppVersion -Further Readings -Reconnecting Into A Running Quantum Game -Quantum ClientId -Further Readings -Restarting The Quantum Session -EntityViews And UI -Events -SetPlayerData -StartParameters.QuitBehaviour -Late-Joining And Buddy-Snapshots -Local Snapshots - -Detecting Disconnects -The most common cases of disrupted connections: - -IConnectionCallbacks.OnDisconnected(DisconnectCause cause) is called with something other than the DisconnectCause.DisconnectByClientLogic reason. -Mobile app loses focus -App restarts -A custom way to detect signal loss prematurely -How To Disconnects For Debugging - -In the Unity Editor just hit Play to stop and start the application -LoadBalancingClient.SimulateConnectionLoss(true) will stop sending and receiving and will result in a DisconnectCause.ClientTimeout disconnection after 10 seconds. -LoadBalancingClient.LoadBalancingPeer.StopThread() immediately causes a disconnect DisconnectCause.None. -Use an external network tool (for example clumsy) to block the game server ports -Clumsy: -Filter (udp.DstPort == 5056 or udp.SrcPort == 5056) or (tcp.DstPort == 4531 or tcp.SrcPort == 4531) -Drop 100% -Back To Top - - -Photon Realtime Fast Reconnect -To return to the room there are two ways: - -Connecting through the name server (Photon Cloud) and, when arriving at the master server, joining the room as new Photon Actor (this is referred to as the default way) -Or reconnecting and rejoining -LoadBalancingClient.ReconnectAndRejoin() -The method will try to directly connect to the game server and rejoin the room using cached authentication data, server address, room name, and so on. This information is cached on the LoadBalancingClient object. - -Rejoining a room will assign the same Photon Actor id to the client. - -Rejoining a room can also be performed after reconnecting or connecting to the master server: - -LoadBalancingClient.ReconnectToMaster() -// .. -public void IConnectionCallbacks.OnConnectedToMaster() { - _client.OpReJoinRoom(roomName); -} -The rejoin operation only works if the client has not left the room, yet. (see next section PlayerTTL) - -Caveat: Fast reconnect in Quantum version 2.0.x will only work when providing a local snapshot (StartParameters.InitialFrame and StartParameters.FrameData). - -var frameData = QuantumRunner.Default.Game.Frames.Verified.Serialize(DeterministicFrameSerializeMode.Blit); -var initialFrame = QuantumRunner.Default.Game.Frames.Verified.Number; -Back To Top - - -Requirements: PlayerTTL -Clients inside a room are generally active. They become inactive.. - -after a timeout of 10 seconds (by default) without answering the server -after calling LoadBalancingClient.Disonnect() -after calling LoadBalancingClient.OpLeaveRoom(true) -Now, there are two options: - -A) the room runs the player-left logic (PlayerTTL is 0, default) - -B) the player is marked inactive and kept in that state for PlayerTTL milliseconds before running the leave-room routine. The PlayerTTL value needs to be explicitly set in the RoomOptions when creating the room. Usually 20 seconds (20000 ms) is a good value to start with. - -Fast Reconnect will allow clients back into their room when they are still active (before the 10 second timeout which OpJoinRoom() does not allow) and inactive (during the PlayerTTL) timeout. - -When the client rejoined successfully IMatchmakingCallbacks.OnJoinedRoom() is called. - -The demo menu sample implements two options to check out (UIConnect.OnReconnectClicked()). - -When PlayerTTL > 0 do ReconnectAndRejoin() -When PlayerTTL == 0 do ReconnectToMaster() followed by OpJoinRom() -Back To Top - - -Requirements: RoomTTL (Waiting For Snapshots) -When the room detects that all clients are inactive it will close itself right away. To prevent that set RoomOptions.EmptyRoomTTL. This may be important when your room only has a small number of players and the probability that all of them have connection problems at the same time is given. Because there needs to be someone present to send a snapshot, this will only work reliably with custom server plugin and server-side snapshots. - -Consider this case: In a two player online game one player is reconnecting or late-joining and waiting for a snapshot while the other player starts to have connections problems. The snapshot is never send and the player is stuck waiting. - -The simple solution is to detect the game start timeout and disconnect the waiting player. This can be done by passing QuantumRunner.StartParameters.StartGameTimeoutInSeconds and checking QuantumRunner.HasGameStartTimedOut and finally implementing custom error handling (like giving player feedback and returning back to the lobby UI). As shown in UIGame.Update(). - -Back To Top - - -Requirements: Photon UserId -photon realtime: lobby and matchmaking | userids and friends - -In Photon, a player is identified using a unique UserID. To return to the room using rejoin the UserId has to be the same. It does not matter if the UserId originally has been set explicitly or by Photon. - -Once in the room, the UserId does not matter for Quantum as it uses a different id to identify players (see section Quantum CliendId). - -The Photon UserId can be.. - -set by the client when connecting (AuthenticationValues.UserId) -if left empty it is set by Photon -or set by an external authentication service -To complete the background info about Photon ids: - -Photon Actor Number (also referred to as actor id) identifies the player in his current room and is assigned per room and only valid in that context. Clients leaving and joining back a room will get a new actor id. A successful OpRejoinRoom() or ReconnectAndRejoin() will retain the actor id. Quantum provides a way to backtrace the actor ids and match them to a player Frame.PlayerToActorId(PlayerRef). But keep in mind, that they can change for player leaving and joining back (not rejoining). -Photon Nickname is a Photon client property that gets propagated in the rooms to know a bit more about the other clients. Has nothing to do with Quantum. -Back To Top - - -Possible Error: ReconnectAndRejoin Returns False -The current connection handler LoadBalancingClient is missing relevant data to perform a reconnect. Run your default connection sequence and try to join or rejoin the room in a regular way. - -Also see section Reconnecting After App Restart. - -Back To Top - - -Possible Error: PlayerTTL Ran Out -When rejoining past the PlayerTTL timeout ErrorCode.JoinFailedWithRejoinerNotFound is thrown. - -This also means that we are connected to the MasterServer and can join the room with OpJoinRoom(). - -public void (IMatchmakingCallbacks.)OnJoinRoomFailed(short returnCode, string message) { - switch (returnCode) { - case ErrorCode.JoinFailedWithRejoinerNotFound: - // Try to join the room without rejoining - _client.OpJoinRoom(new EnterRoomParams { RoomName = roomName }); - break; -} -Back To Top - - -Possible Error: Authentication Token Timeout -The authentication ticket expires after 1 hour. It will be refreshed automatically before running out in the course of the Quantum game session (photon realtime: encryption | token refresh). If your general game sessions are long and you want to support reconnecting players after around 20 minutes you need to handle this error. Resolution is to restart the default connection routine and try to join back into the room. - -public void OnDisconnected(DisconnectCause cause) { - switch (cause) { - case DisconnectCause.AuthenticationTicketExpired: - case DisconnectCause.InvalidAuthentication: - // Restart with your default connection sequence - break; -Back To Top - - -Possible Error: Connection Still Unavailable -Of course the connection can still be obstructed or other errors can occur. In each case a IConnectionCallbacks.OnDisconnected(DisconnectCause cause) is called. - -Back To Top - - -Reconnecting After App Restart -The LoadBalancingClient object caches data (token) relevant to the rejoining operation and that information can get lost when restarting the application. - -In that case the connection has to be restarted from scratch while reusing the same UserId, FixedRegion and AppVersion. When arriving at the master server either Rejoin() or Join() back into the room. - -Due to the lost connection cache, rejoining may fail with ErrorCode.JoinFailedFoundActiveJoiner because the server did not register the disconnect, yet (10 sec timeout). In this case retry until rejoining worked or another error occurs. - -The demo menu sample class ReconnectInformation demonstrates how relevant minimum viable reconnection data is saved and restored using Unity PlayerPrefs. - -The reconnection process is applied inside UIConnect.OnReconnectClicked() and UIReconnecting.OnConnectedToMaster(). - -Saving the Photon UserId to PlayerPrefs can of course be replaced by custom authentication. - -It is also possible to store and load a snapshot inside PlayerPrefs, which may be interesting for games with a very low player count. To store binary data in PlayerPrefs as a string use base64 en- and decoding. - -Back To Top - - -Different Master Server -ReconnectAndRejoin() and ReconnectToMaster() both prevent a fringe case that would let the client end up on a different master server than before when connecting back via the cloud. Reasons are: - -There are multiple cluster for one app -Master server has been replaced (rotated out) -Best region ping has a new result -Back To Top - - -Other Photon Realtime Topics -These features are not important for reconnection but are part of the demo menu sample so we might as well cover them here. - - -Best Region Summary -The class QuantumLoadBalancingClient wraps around Photon Realtime LoadBalancingClient. This is just for conveniently caching the best region ping results. After successfully connecting to the master server we store them into Unity PlayerPrefs and provide them for the next connection attempt via the AppSettings object to speed up connecting to the best region. - -The region ping is forced from time to time but to be certain players are not stuck with a bad ping result it could be smart to implement invalidation so players are not stuck with a bad or wrong result forever (e.g. if ping is above threshold clear BestRegionSummary every other day). Also it could happen that players travel to other parts of the world where a new ping would be required to find the closest region. - -Back To Top - - -AppVersion -The demo menu sample uses random matchmaking to match players. The AppVersion we supply with the AppSettings will group the player bases for the same AppId into separate groups. Players connecting to the same AppId and different AppVersions will not find each other at all. - -This is useful when running multiple game versions live as well as during development to prevent other clients (that have a different code base and would instantly desync the game) to join a game running by a developer. - -UIConnect.cs covers adding generic AppVersions and having a private key for uninterrupted testing. The private AppVersion string is generated for every workspace once (see PhotonPrivateAppVersion.cs). Every build that has been created from that workspace is able to match players with each other. - -Back To Top - - -Further Readings -photon realtime: analysing disconnects | quick rejoin (reconnectandrejoin) -photon realtime: known issues | mobile background apps -Photon Realtime: .NET Client API | LoadBalancingClient -Back To Top - - -Reconnecting Into A Running Quantum Game - -Quantum ClientId -The ClientId is a secret between the client and the server. Other clients never know it. It is passed when starting the QuantumRunner. - -public static QuantumRunner StartGame(String clientId, StartParameters param) -Independently of having joined as a new Photon room actor or having rejoined, reconnecting clients are identified by their ClientId and will be assigned to the same player index they previously had if the slot was not filled by another players in the meantime. In short: player must use the same ClientId when reconnecting. - -Quantum will not let a client start the session while another active player with the same ClientId is inside the room and waits for the disconnect timeout (10 seconds): - -DISCONNECTED: Error #5: Duplicate client id -This is why ReconnectAndRejoin() is required to recover from short term connection losses. - -Back To Top - - -Further Readings -quantum: player manual -Back To Top - - -Restarting The Quantum Session -After a disconnect the QuantumRunner and QuantumSession are not usable any more and must be destroyed and recreated. - -When the client either joined or re-joined back into the room that runs the Quantum game the QuantumRunner needs to be restarted. The simulation will be paused until the snapshot arrives from another client. Then will catch-up and sync to the most recent game time. - -Rough outline: - -detect disconnect, destroy QuantumRunner -reconnect and rejoin the room -re-start Quantum by calling QuantumRunner.Start() -To stop and destroy the QuantumSession call: - -QuantumRunner.ShutdownAll(true); -Only call this method with immediate:true when you are on the Unity main thread and never from inside a Quantum callback. Call with immediate:false or delay the call manually until it gets picked up from a Unity update call. - -The demo menu sample demonstrates how starting a new game, late-joining or re-joining a running game require very similar procedures. In UIRoom.OnShowScreen() we detect that that game has already been started by evaluating the room properties and then immediately start the game. - -Back To Top - - -EntityViews And UI -Late-joins and reconnection players put high demands on how flexible your game is constructed. It needs to support starting the game from any point in time and possibly reusing instantiated prefabs and UI as well as stopping and cleaning up the game at any possible moment. Side effects are high loading times, having unwanted VFX and animations in the new scene, being stuck in UI transitions, etc. - -If you want to keep the EntityViewUpdater and the EntityViews alive to reuse them, they need to manually be stopped from being updated, re-match them with the new QuantumGame instance, subscribe to the new callbacks, etc. - -On the other side the handling of Quantum is extremely simple: shutdown runner, start runner. - -Back To Top - - -Events -The client will not receive previous events that were raised before the player joined or rejoined. The game view should be able to fully initialize/reset itself by polling the current state of the simulation and use future events/polling to keep itself updated. - -Back To Top - - -SetPlayerData -Calling SetPlayerData() for reconnecting players is optional. It depends if your avatar setup logic in the simulation requires this. - -Back To Top - - -StartParameters.QuitBehaviour -When the Quantum shutdown sequence is being executed (QuantumRunner.ShutdownAll) the QuantumNetworkCommunicator class will optionally perform room leave operations or disconnect the LoadBalancing client. Set to QuitBehaviour.None on the QuantumRunner.StartParameters to handle it yourself. - -Back To Top - - -Late-Joining And Buddy-Snapshots -A Quantum game snapshot is a platform independent blob of data that contains the complete state of the game after a verified (all input has been received) tick. The Quantum simulation can be started from a snapshot and seamlessly continue from that state on. - -A client can create its own snapshot when the simulation is still running (local snapshot), the snapshot can be requested from other clients (buddy snapshot) or it can be send down from a custom server plugin that runs the simulation. - -Starting or restarting from snapshots is very handy and is provided turn-key by Quantum. Otherwise late-joining or reconnecting clients would have to start the game session from the very beginning and fast-forward through the input history send by the server which can render the client app useless until it caught up and also input history stored on the server is limited to ~10 minutes. - -The buddy snapshot process is started automatically when any client is starting its QuantumRunner (no matter if the client is starting the session for the first time, late-joining or reconnecting). The session will be put into paused mode DeterministicSession.IsPaused and a snapshot will be requested. Successful late joins will log the following messages: - -Waiting for snapshot. Clock paused. -Detected Resync. Verified tick: 6541 -Buddy snapshots are requested for clients connecting 5 seconds after the initial start. - -The server uses a load balancing mechanism to decide which client it will ask for a buddy snapshot to not overburden individual clients. - -Errors during the snapshot process will be forwarded to the client using the Disconnect message (e.g. the snapshot waiting state will time out after 15 seconds): - -Error #13: Snapshot request failed -Error #14: Snapshot request timed out -There are a few differences when starting from a snapshot during the game starting routines: - -Instead of CallbackGameStarted the callback CallbackGameResynced is executed. -System.OnInit() is called before the snapshot is received. -Back To Top - - -Local Snapshots -As an optional reconnection strategy a local snapshot of the last verified tick can be saved and used when starting the new QuantumRunner. This works best when the anticipated time offline is small. Local snapshots are generally more bandwidth friendly and faster. - -Guidelines - -Quantum enforces tight limitations around the local snapshot acceptance timing, because starting from a snapshot that is too old can degrade the user experience. - -By default local snapshots that are older than 10 seconds are not accepted by the server and instead a buddy-snapshot is requested. The process works transparently and from the clients perspective the only difference is the received snapshot age. - -For games that have a low user count (e.g. 1 vs 1) the chance that there is no other client online that can provide a buddy snapshot is high. These types of games usually require to work with the EmptyRoomTTL value and Quantum prolongs the local snapshot acceptance time to EmptyRoomTTL but to a maximum of two minutes. - -Workflow - -Detect disconnect -Take snapshot -Shutdown QuantumRunner -Fast Photon Reconnect -restart Quantum with snapshot -Read through reconnecting sequence in the demo menu. UIGame.OnDisconnect creates a snapshot when the disconnect reason is other than the client disconnected itself. It uses a timeout of 10 seconds after which the snapshot is not used and a new one is requested from another client/server because the catching up time would become too long. - -_frameSnapshot = QuantumRunner.Default.Game.Frames.Verified.Serialize(DeterministicFrameSerializeMode.Blit); -_frameSnapshotNumber = QuantumRunner.Default.Game.Frames.Verified.Number; -_frameSnapshotTimeout = Time.time + 10.0f; -During the reconnecting in UIRoom.StartQuantumGame() the snapshot info is set as StartParameter. - -var param = new QuantumRunner.StartParameters { - FrameData = IsRejoining ? UIGame.Instance?.FrameSnapshot : null, - InitialFrame = IsRejoining ? (UIGame.Instance?.FrameSnapshotNumber).Value : 0, - // ... -} -When successful Quantum will log this with the requested tick number: - -Resetting to tick 4316 -Detected Resync. Verified tick: 4316 \ No newline at end of file diff --git a/data/runtime.txt b/data/runtime.txt deleted file mode 100644 index 359a440ccdd78407a8f28c4739db7b3d9e3aac5d..0000000000000000000000000000000000000000 --- a/data/runtime.txt +++ /dev/null @@ -1,81 +0,0 @@ -Quantum.RuntimeConfig Class Reference -In contrast to the SimulationConfig, which has only static configuration data, the RuntimeConfig holds information that can be different from game to game. More... - -Public Member Functions -String Dump () - Dump the content into a human readable form. More... - -void Serialize (BitStream stream) - Serializing the members to be send to the server plugin and other players. More... - -Static Public Member Functions -static RuntimeConfig FromByteArray (Byte[] data) - Deserialize the class from a byte array. More... - -static Byte[] ToByteArray (RuntimeConfig config) - Serialize the class into a byte array. More... - -Public Attributes -AssetRefMap Map - Asset reference of the Quantum map used with the upcoming game session. More... - -Int32 Seed - Seed to initialize the randomization session under Frame.RNG. More... - -AssetRefSimulationConfig SimulationConfig - Asset reference to the SimulationConfig used with the upcoming game session. More... - -Detailed Description -In contrast to the SimulationConfig, which has only static configuration data, the RuntimeConfig holds information that can be different from game to game. - -By default is defines for example what map to load and the random start seed. It is assembled from scratch each time starting a game. - -Developers can add custom data to quantum_code/quantum.state/RuntimeConfig.User.cs (don't forget to fill out the serialization methods). - -Like the DeterministicSessionConfig this config is distributed to every other client after the first player connected and joined the Quantum plugin. - -Member Function Documentation -◆ Serialize() -void Quantum.RuntimeConfig.Serialize ( BitStream stream ) -inline -Serializing the members to be send to the server plugin and other players. - -Parameters -stream Input output stream -◆ Dump() -String Quantum.RuntimeConfig.Dump ( ) -inline -Dump the content into a human readable form. - -Returns -String representation -◆ ToByteArray() -static Byte [] Quantum.RuntimeConfig.ToByteArray ( RuntimeConfig config ) -inlinestatic -Serialize the class into a byte array. - -Parameters -config Config to serialized -Returns -Byte array -◆ FromByteArray() -static RuntimeConfig Quantum.RuntimeConfig.FromByteArray ( Byte[] data ) -inlinestatic -Deserialize the class from a byte array. - -Parameters -data Config class in byte array form -Returns -New instance of the deserialized class -Member Data Documentation -◆ Seed -Int32 Quantum.RuntimeConfig.Seed -Seed to initialize the randomization session under Frame.RNG. - -◆ Map -AssetRefMap Quantum.RuntimeConfig.Map -Asset reference of the Quantum map used with the upcoming game session. - -◆ SimulationConfig -AssetRefSimulationConfig Quantum.RuntimeConfig.SimulationConfig -Asset reference to the SimulationConfig used with the upcoming game session. \ No newline at end of file diff --git a/data/settings.txt b/data/settings.txt deleted file mode 100644 index 571a7778abfe1c905297dac59fa39053d7181b11..0000000000000000000000000000000000000000 --- a/data/settings.txt +++ /dev/null @@ -1,51 +0,0 @@ -Overview -The Physics settings can be edited in the Map Asset associated with a Scene in its Map Data script, and in the Simulation Config Asset linked in the Quantum Runner script. The settings in the Map are specific to a given scene, while the Simulation Config can be shared among multiple scenes. - -Back To Top - - -Map Data -The scene's playable area related settings can be found in the scene's MapData script or the Map Asset slotted in the Asset field. - -Adjust the World Size for your Playable Area -The Physics settings as seen in the MapData script in the Scene. -Aside from World Size, tweaking these settings should only be a concern if the physics simulation is a bottleneck in your game. - -Setting Description -World Size The physics scene size in the bucketing axis. The broad phase is clamped by a bounding box of all physics entries between -WorldSize/2 to WorldSize/2. It is therefore crucial to ensure the world is big enough to encompass all entities. If an entity is outside the world, it will cost you performance as it is added to either the first or last bucket. Everything outside the bounding box is considered to be at the world's edge, from the physics engine perspective, which will result in false collision candidates. -In the non bucketing axis, the physics world is only limited by the value range of FP.UsableMin to FP.UsableMax. -Buckets Count The amount of buckets used in the broad phase, which are resolved in parallel. Use a reasonable amount according to how many physics entries (colliders) you have. Too many buckets and the handling overhead increases without any performance gain because there are only few entries in each one; too few buckets and there will be an excessive amount of entries in each, slowing down the broad phase performance. -Buckets Subdivisions Regular queries (overlaps and raycasts) use a stabbing approach for checking as few entries as possible in the buckets subdivisions. Tweak the number in accordance with the expected amount of entries and regular queries you perform. Too many subdivisions will add overhead without performance, while too few will result in queries taking longer to resolve, because they will have to check too many entries. -NOTE on Buckets Count & Buckets Subdivisions -The default buckets count and bucket subdivisions values (16 buckets with 8 subdivisions) are usually good for up to 1~2K entries. You should thus not have to worry about tweaking them unless the physics are the bottleneck of your game. In that case, use the Task Profiler for evaluating the performance and tweak the values based on the findings (broad phase and regular queries resolution respectively). -Bucketing Axis Physics entries are put into buckets according to their position in the bucketing axis. -Sorting Axis The queries in a bucket are sorted according to their position in the sorting axis. -NOTE on Bucketing Axis & Sorting Axis -The Y-Axis represents the vertical axis of the physics simulation. In 2D this is equal to the Y-Axis, whereas in 3D the Y-Axis is mapped to the Z-Axis as the 3D space partitioning is performed on the XZ-Plane. -Choose these a bucketing and sorting axis based on how the entries are spread out in the world. Selecting a different axis for bucketing and sorting (e.g. X-Y or Y-X) is good for uniformly spread entries across that plane. If entries are concentrated in one axis, consider using the same axis for both bucketing and sorting. -Triangle Mesh Cell Size Defines the size of the cells into which the 3D triangle soup is divided. This number should be adapted based on how dense the meshes' triangles density to get a reasonable amount of triangles per cell. -For better visualization enable related fields in the QuantumEditorSettings asset's Collider gizmos section. -This will affect the performance of both the broad phase and regular queries. Use the Task Profiler to analyse the performance and find the most suitable number for your game. -Back To Top - - -Simulation Config -The SimulationConfig data asset contains an extensive set of settings for the physics engines: - -Physics Settings on SimulationConfig -SimulationConfig Asset. -Layers and the corresponding Layer Collision Matrix can be imported from Unity ones. Once imported, the Collision Matrix can be edited directly in the settings. - -Back To Top - - -Optimization Tips -In this section we are covering some general considerations to take when optimizing your physics settings for enhanced performance: - -Collision Matrix, make sure to only enable collisions between layers that actually require to be checked against each other; -Angular Velocity (physics controlled rotation), disabling this option leads to faster and more stable physics simulation; -Kinematic Entities, use kinematic entities rather than dynamic entities whenever possible. Kinematics do not check for collisions against each other, unless one of them is a trigger kinematic. -Raycasts, use reasonable distances for rays to prevent them from becoming a bottleneck. -PhysicsBody, enabling resting bodies in the settings allows resting entities to be excluded from collisions checks and reduce the load on the collision detection system. Resting bodies can be awaken again by either another moving body, or by code. -Thread Count, tweaking this options allows to raise the amount of threads available to the Quantum Simulation at runtime. -Profiler, run it on your code systems before and during performance tweaks. Bottlenecks are often tied to custom code rather than the physics engine. Furthermore, the profiler helps to identify which settings work best under the game specific load. diff --git a/data/shape config.txt b/data/shape config.txt deleted file mode 100644 index 3219e8e3b223272eca54cc5a4db67faf1fb69e2e..0000000000000000000000000000000000000000 --- a/data/shape config.txt +++ /dev/null @@ -1,208 +0,0 @@ -Introduction -The shape config holds information on a shape. It can be used to easily expose configuration options in the editor and streamline shape initialization in code. ShapeConfigs exist for both 2D and 3D - Shape2DConfig and Shape3DConfig respectively. - -Currently, ShapeConfigs targeted for use with dynamic entities; as such, the shape types supported by it are: - -// in 2D - -Circle -Box -Polygon -Edge -Compound (a mixture of all these) -// in 3D - -Sphere -Box -Compound (i.e. a mixture of Spheres, Boxes, and/or Compound) -All of the aforementioned shapes are compatible with ShapeOverlaps and PhysicsColliders. - -Back To Top - - -Exposing ShapeConfig To The Editor -Including either a Shape2DConfig or Shape3DConfig in your custom asset will automatically expose it in the Editor. - -namespace Quantum -{ - public unsafe partial class WeaponSpec - { - public Shape3DConfig AttackShape; - public LayerMask AttackLayers; - public FP Damage; - public FP KnockbackForce; - } -} -The asset resulting from the snippet above will offer the following exposing all ShapeConfigs options in the inspector: - -ShapeConfig Setting of the example asset as shown in the Unity Editor -ShapeConfig Setting of the WeaponSpec example asset as shown in the Unity Editor. -You may have already come that in the PhysicsCollider section called Shape 2D/3D of Entity Prototype script. - -Back To Top - - -Creating/Using A Shape From ShapeConfig -When using a ShapeConfig, you can simply call its CreateShape method. This will automatically process the information held in the ShapeConfig asset, create the appropriate shape and its parameter with the data found in the config. - -private static void Attack(in Frame f, in EntityRef entity) -{ - // A melee attack performed by using an OverlapShape on the attack area. - - var transform = f.Unsafe.GetPointer(entity); - var weapon = f.Unsafe.GetPointer(entity); - var weaponSpec = f.FindAsset(weapon->WeaponSpec.Id); - - var hits = f.Physics3D.OverlapShape( - transform->Position, - transform->Rotation, - weaponSpec.AttackShape.CreateShape(f), - weaponSpec.AttackLayers); - - // Game logic iterating over the hits. -} -The same can be done when initializing the Shape of a PhysicsCollider. - -Back To Top - - -Compound Shape -A Compound Shape is a shape made of several other shapes. The shapes that can be used to create a compound shape are the ones listed in the introduction section. - -PhysicsColliders and ShapeOverlaps are fully compatible with compound shapes. - -As of now, Quantum offers persistent compound shapes, i.e. a shape pointing to a buffer of other shapes in the heap. This buffer will persist between frames until it is manually disposed. - -Back To Top - - -Create New Compound Shape -A compound shape can be create from a ShapeConfig by simply calling the CreateShape method, or manually via the Shape.CreatePersistentCompound, and later dispose of it calling FreePersistent on. Here is an example of how the lifetime can be managed, the same applies to 3D Shapes: - -// creating a persistent compound. This does not allocate memory until you actually add shapes -var compoundShape = Shape2D.CreatePersistentCompound(); - -// adding shapes to a compound (shape1 and 2 can be of any type) -compoundShape.Compound.AddShape(f, shape1); -compoundShape.Compound.AddShape(f, shape2); - -(...) // Game logic - -// this compound persists until you manually dispose it -compoundShape.Compound.FreePersistent(f); -The API also offers methods such as RemoveShapes, GetShapes, and FreePersistent; check the API documentation in the SDK's docs folder for more information on those and other methods. - -Back To Top - - -CopyFrom An Existing Compound Shape -You can also create a new compound shape by copying an existing one. - -// Using the exising compoundShape from the example above. - -var newCompoundShape = Shape2D.CreatePersistentCompound(); -compoundShape.Compound.CopyFrom(f, ref newCompoundShape); -Creating a new compound shape also means a new buffer. This will need to be freed by the developer manually like any other persistent compound shape. - -Back To Top - - -Accesing Individual Shapes -An example of how to iterate through the Shapes is to use the GetShapes method to get the pointers buffer, and use a simple for loop, where the integer index can be used to access all Shape* contained in the compound. -Do not surprass the count returned by the method as it is the boundary where the shape pointers are contained in memory. - -if (shape->Compound.GetShapes(frame, out Shape3D* shapesBuffer, out int count)) -{ - for (var i = 0; i < count; i++) - { - Shape3D* currentShape = shapes + i; - // do something with the shape - } -} -Back To Top - - -Compound Collider -A compound collider is a regular collider with a shape of type compound. - - -Create In Editor -In the editor you can find the options to create a compound collider in the Entity Component Physics Collider 2D/3D and the Entity Prototype script's respective section. - -ShapeConfig for a Compound Collider in the Entity Prototype script as shown in the Unity Editor -Creating a Compound Collider (Sphere + Box) via the Entity Prototype script in the Unity Editor. -When you create a collider prototype with a compound shape memory management is handled for you, i.e. the collider will have and manage its compound shape and you won't have to manually dispose anything. - -Back To Top - - -Create In Code -When creating a collider in code, simply pass a compound shape into its Create() factory method. Once the compound shape has been created as shown in the code snippet from the previous section, you can create a collider by replacing (...) with this: - -var collider = PhysicsCollider2D.Create(f, compoundShape); -f.Set(entity, collider); -In the code snippet provided above collider.Shape and compoundShape point to different buffers in the heap. If you are done using the compound shape - i.e. it was only needed for creating the collider - you can dispose it right after that. The collider will dispose its own copy in memory when destroyed/removed. - -Back To Top - - -Important Note About Memory -A collider only creates a copy of the compound shape buffer if it is used as part of its factory method Create(). - -var compoundShape = Shape2D.CreatePersistentCompound(); -compoundShape.Compound.AddShape(f, shape1); -compoundShape.Compound.AddShape(f, shape2); - -// collider1 and collider2 each create a copy of the compoundShape buffer. -// collider1 and collider2 will each dispose of their copy on destroy/remove. -var collider1 = PhysicsCollider2D.Create(f, compoundShape); -f.Set(entity1, collider1); - -var collider2 = PhysicsCollider2D.Create(f, compoundShape); -f.Set(entity2, collider2); - -// Here we dispose of the compoundShape's buffer as it is no longer needed -compoundShape.Compound.FreePersistent(f); -In contrast, should you create a collider with a regular shape, and later set its collider.Shape = someCompound it will not create a copy of the buffer, i.e. collider.Shape and someCompound will point to the same buffer. Doing this can be dangerous if you happened to have multiple compound colliders and/or compound shapes pointing towards the same buffer. If one disposes of it, it will effectively break the others' reference to the buffer. - -var compoundShape = Shape2D.CreatePersistentCompound(); -compoundShape.Compound.AddShape(f, shape1); -compoundShape.Compound.AddShape(f, shape2); - -var collider1 = PhysicsCollider2D.Create(f, default(Shape2D)); -collider1.Shape = compoundShape; -f.Set(entity1, collider1); - -var collider2 = PhysicsCollider2D.Create(f, Shape2D.CreateCircle(1)); -collider2.Shape = compoundShape; -f.Set(entity2, collider2); - -// collider1.Shape, collider2.Shape and compoundShape all point to the same buffer -// dispose of compoundShape here will break collider1 and collider2 -compoundShape.Compound.FreePersistent(f); -However, if you only do it conscientiously this will assign the shape and its memory management to the collider in question thus simplifying memory management by relinquishing the responsibilty to the collider which will dispose of it when destroyed/removed. - -var compoundShape = Shape2D.CreatePersistentCompound(); -compoundShape.Compound.AddShape(f, shape1); -compoundShape.Compound.AddShape(f, shape2); - -var collider1 = PhysicsCollider2D.Create(f, Shape2D.CreateCircle(1)); -collider1.Shape = compoundShape; -f.Set(entity1, collider1); - -// In this instance we do not need to dispose of the compoundShape buffer because -// collider1 already points to it and will take care of it on destroy/remove. -Back To Top - - -Compound Shape Query -Compound shape queries are fully supported for both broadphase and regular queries. The behaviour and performance impact is the same as performing multiple queries, except the results will be returned in the same HitCollection; - -Back To Top - - -Nested Compound Shape -Nested compound shapes are supported by the physics engines though with two limitations: - -a shape can only hold one reference to the same buffer in the heap in its hierarchy. Add an already referenced buffer will throw an error in debug mode. This is to avoid issues with cyclic references and invalid pointers on disposal. -nested compound shapes are not supported in the editor (a warning message will be shown). This is due to the Unity serializer limitations which requires a more intricate structure and drawer for the shape config. \ No newline at end of file diff --git a/data/snip.txt b/data/snip.txt deleted file mode 100644 index 5c21ed025bb069673891611379d483325655b591..0000000000000000000000000000000000000000 --- a/data/snip.txt +++ /dev/null @@ -1,518 +0,0 @@ -HTTP Requests - -CustomQuantumPlugin -Defer plugin callbacks like OnCreateGame() to retrieve any specific room configurations from a trusted backend and block the room creation momentarily. The same is possible for call room callbacks with an info object (OnJoin(),..). - -public override void OnCreateGame(ICreateGameCallInfo info) { - var request = new HttpRequest() { Url = "http://microsoft.com", Async = false, Callback = OnCreateGameContinue }; - PluginHost.HttpRequest(request, info); - - // Do not call base.OnCreateGame() to prevent the continuation -} - -private void OnCreateGameContinue(IHttpResponse response, object userState) { - // Complete the OnCreateGame() call - base.OnCreateGame((ICreateGameCallInfo)response.CallInfo); -} -Back To Top - - -CustomQuantumServer -Defer Quantum server callbacks to overwrite RuntimePlayer with data fetched from a trusted backend. - -IPluginHost.HttpRequest(request, info) can be used with info = null. - -Request has to be Async = true. - -public override bool OnDeterministicPlayerDataSet(DeterministicPluginClient client, SetPlayerData playerData) { - // Use client.ClientId as unique client id (UserId) - var request = new HttpRequest() { Url = "http://microsoft.com", Async = true, Callback = OnDeterministicPlayerDataSetContinue, UserState = playerData.Index }; - ((DeterministicPlugin)PluginHost).PluginHost.HttpRequest(request, null); - - // Return false to not conntinue with SetPlayerData request - return false; -} - -private void OnDeterministicPlayerDataSetContinue(IHttpResponse response, object userState) { - private void OnDeterministicPlayerDataSetContinue(IHttpResponse response, object userState) { - // Reponse sends player data in json for example: deserialize json into RuntimePlayer - var runtimePlayer = new RuntimePlayer(); - - SetPlayerData data = new SetPlayerData { - Data = RuntimePlayer.ToByteArray(runtimePlayer), - Index = (int)userState - }; - - // continue the interupted flow of Photon.Deterministic.Server.DeterministicServer.OnDeterministicPlayerDataSet - SetDeterministicPlayerData(data); - } -} -Back To Top - - -Save Replay / Input History Snippet -Input history is an array (number of ticks) of DeterministicTickInputSet objects which in turn store the input for each player: - -public struct DeterministicTickInputSet { - public int Tick; - public DeterministicTickInput[] Inputs; -} -Save the input inside the InputProvider class during the OnDeterministicInputConfirmed() callback. This is when the input for a player has been confirmed. Or create a custom similar data structure. - -public override void OnDeterministicSessionConfig(DeterministicPluginClient client, SessionConfig configData) -{ - _config = configData.Config; -} -public override void OnDeterministicStartSession() { - _inputProvider = new InputProvider(_config); -} -public override void OnDeterministicInputConfirmed(DeterministicPluginClient client, int tick, int playerIndex, DeterministicTickInput input) { - _inputProvider.InjectInput(input, true); -} -Use the ReplayFile data structure to create a complete replay with required config files. The serializer is a QuantumJsonSerializer that outputs JSON. - -private void SaveReplayToFile(int verifiedFrame) { - var replayFile = new ReplayFile { - DeterministicConfig = container.DeterministicConfig, - RuntimeConfig = container.RuntimeConfig, - InputHistory = _inputProvider.ExportToList(verifiedFrame), - Length = verifiedFrame - }; - - - var filepath = Path.Combine(PluginLocation, "replay.json"); - File.WriteAllBytes(filepath, _serializer.SerializeReplay(replayFile)); -} -Save the ReplayFile without knowing the highest verified tick. - -private void SaveReplayToFile() { - // This will not cut out incomplete input in the end, but we should be able to live with it - var inputSets = _inputProvider.ExportToList(int.MaxValue); - - // Find out what the highest verified tick that has a complete input set (for all players) - int maxVerifiedTick = 0; - for (int i = inputSets.Length - 1; i >= 0; i--) { - if (inputSets[i].IsComplete()) { - maxVerifiedTick = inputSets[i].Tick; - break; - } - } - - var replayFile = new ReplayFile { - DeterministicConfig = container.DeterministicConfig, - RuntimeConfig = container.RuntimeConfig, - InputHistory = inputSets, - Length = maxVerifiedTick - }; - - - var filepath = Path.Combine(PluginLocation, "replay.json"); - File.WriteAllBytes(filepath, _serializer.SerializeReplay(replayFile)); -} -Back To Top - - -Server Command Snippet -A snippet showing how to intercept commands sent by clients, possibly reject them and send commands from the server itself. - -private DeterministicCommandSerializer _cmdSerializer; - -public override bool OnDeterministicCommand(DeterministicPluginClient client, Command cmd) { - if (_cmdSerializer == null) { - _cmdSerializer = new DeterministicCommandSerializer(); - _cmdSerializer.RegisterFactories(DeterministicCommandSetup.GetCommandFactories(runtimeConfig, null)); - - _cmdSerializer.CommandSerializerStreamRead.Reading = true; - _cmdSerializer.CommandSerializerStreamWrite.Writing = true; - } - - var stream = _cmdSerializer.CommandSerializerStreamRead; - - stream.SetBuffer(cmd.Data); - - if (_cmdSerializer.ReadNext(stream, out var command)) { - // handle DeterministicCommand - - // return false if a command should be rejected from the (or any) client - if (command is TestCommand testCmd) { - return false; - } - } - - return true; -} - -public void SendDeterministicCommand(DeterministicCommand cmd) { - if (_cmdSerializer == null) { - _cmdSerializer = new DeterministicCommandSerializer(); - _cmdSerializer.RegisterFactories(DeterministicCommandSetup.GetCommandFactories(runtimeConfig, null)); - - _cmdSerializer.CommandSerializerStreamRead.Reading = true; - _cmdSerializer.CommandSerializerStreamWrite.Writing = true; - } - - var stream = _cmdSerializer.CommandSerializerStreamWrite; - - stream.Reset(stream.Capacity); - - if (_cmdSerializer.PackNext(stream, cmd)) { - - SendDeterministicCommand(new Command { - Index = 0, - Data = stream.ToArray(), - }); - - // optional: pool byte arrays and use them instead of allocating with ToArray() - // Buffer.BlockCopy(stream.Data, stream.Offset, pooledByteArray, 0, stream.BytesRequired); - } -} -Back To Top - - -Subscribing To Quantum Events And Callbacks -It is possible to, from the custom plugin, react to Quantum Events (defined and triggered in the simulation code) and Quantum Callbacks (defined and triggered from Unity). - -That can achieved that by adding events and callbacks dispatchers to the start parameters in the OnDeterministicStartSession() callback on the custom plugin: - -// given a Quantum Event named "Foo", defined in the Quantum simulation code - -public override void OnDeterministicStartSession() -{ - // Create an instance of an EventDispacther - var events = new EventDispatcher(); - // Subscribe to the Quantum event - events.Subscribe(this, EventReaction); - // Insert it in the params - startParams.EventDispatcher = events; - - // Same goes for the CallbackDispatcher - var callbacks = new CallbackDispatcher(); - callbacks.Subscribe(this, c => Log.Warn(c.Game.Session.FrameVerified.Number)); - startParams.CallbackDispatcher = callbacks; - - // the dispatchers are then injected within the paramrs object - container.StartReplay(startParams, inputProvider, "server", false, taskRunner: taskRunner); -} - -// This is just a demonstration of the event callback reaction -// It works exactly the same as reacting to events from Unity code -private void EventReaction(EventFoo e) { } -Back To Top - - -Majority Vote -Clients upload their game result to the server, the server waits until a majority vote can be issued, computes one result to publish. -A custom c# class is used to represent the result. In this example the server plugin only operates on the binary data, which is enough to check for equal results. But when the information is forwarded to a custom backend (not implemented) if the backend expects other data format then the sever plugin requires the deserialization code before sending it (reference to the game.dll for example). -Back To Top - - -Changes To Custom Quantum Plugin Class -Forces an evaluation right before the room is closed -Overrides OnRaiseEvent() to filter out the custom message, always cancels the message so it's not forwarded to other clients. -using Photon.Deterministic; -using Photon.Deterministic.Server.Interface; -using Photon.Hive.Plugin; - -namespace Quantum { - public class CustomQuantumPlugin : DeterministicPlugin { - protected CustomQuantumServer _server; - - public CustomQuantumPlugin(IServer server) : base(server) { - Assert.Check(server is CustomQuantumServer); - _server = (CustomQuantumServer)server; - } - - public override void OnCloseGame(ICloseGameCallInfo info) { - EvaluateMajorityVote(true); - _majorityVote?.Dispose(); - _majorityVote = null; - _server.Dispose(); - base.OnCloseGame(info); - } - - private MajorityVote _majorityVote = new MajorityVote(2); - - private void EvaluateMajorityVote(bool force) { - if (_majorityVote != null) { - if (force || _majorityVote.IsReady || _majorityVote.IsWaitingTimeOver) { - if (_majorityVote.Evaluate(out var results)) { - // Send data somewhere - //results[0].Data - Log.Warn($"Game result accepted with {results[0].Count}"); - _majorityVote.Dispose(); - _majorityVote = null; - } - } - } - } - - public override void OnRaiseEvent(IRaiseEventCallInfo info) { - if (info.Request.EvCode == 41) { - // Cancel the message right away, it should not be send to anyone else - info.Cancel(); - - var client = _server.GetClientForActor(info.ActorNr); - if (client == null) { - // Dismiss the message when the client has already left - return; - } - - if (info.Request.Data == null) { - // Client send no data, disconnect - _server.DisconnectClient(client, "Operation Failed"); - return; - } - - if (_majorityVote == null) { - // Vote is over - return; - } - - _majorityVote.AddVote(client.ClientId, (byte[])info.Request.Data); - EvaluateMajorityVote(false); - - // Don't process message any further - return; - } - - base.OnRaiseEvent(info); - } - } -} -Back To Top - - -Majority Vote Class -The timings around min required votes and wait time settings are depending on the actual game: How many players? Does the game have teams? Etc. - -There probably is something faster than StructuralComparisons.StructuralEqualityComparer. Although Linq offers nice collection tools it not be used on server code. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Security.Cryptography; - -namespace Quantum { - /// - /// Trying to not use Linq for performance. - /// Only compares binary data, serialzed by the client. - /// The final result should be send to backend to deserialize or deserialize on custom plugin with correct references. - /// - public class MajorityVote : IDisposable { - private bool _startWaitTimeSet; - private DateTime _startWaitTime; - private double _minWaitTimeSec; - private double _maxWaitTime; - private int _minVotesRequired; - private List _clientResults; - private HashSet _clientResultsHashset; - private MD5CryptoServiceProvider _hashProvider; - - /// - /// There is at least one result and at least MinVotesRequired. - /// The MinWaitingTime has passed. - /// - public bool IsReady { - get { - return - _clientResults.Count >= _minVotesRequired && - (DateTime.Now - _startWaitTime).TotalSeconds >= _minWaitTimeSec; - } - } - - /// - /// The MaxWaitingTime has passed a result be tried to evaluate now. - /// - public bool IsWaitingTimeOver { - get { - return - _maxWaitTime > 0 && - (DateTime.Now - _startWaitTime).TotalSeconds >= _maxWaitTime; - } - } - - /// - /// Number of client votes, indentified by their ClientId. - /// - public int Count => _clientResults.Count; - - /// - /// Create a voting machine. - /// - /// The minimal votes required to make IsReady return true - /// The min wait tim in seconds to make IsReady return true - /// The max wait time in seconds to make IsWaitingTimeOver return true, 0 = undefinately - public MajorityVote(int minVotesRequired, double minWaitTimeSec = 0, double maxWaitTime = 0) { - _minVotesRequired = Math.Max(minVotesRequired, 1); - _minWaitTimeSec = minWaitTimeSec; - _maxWaitTime = maxWaitTime; - _clientResults = new List(); - _clientResultsHashset = new HashSet(); - _hashProvider = new MD5CryptoServiceProvider(); - } - - /// - /// Disposes the hash provider object and clears internal lists. - /// - public void Dispose() { - _clientResults?.Clear(); - _clientResults = null; - - _clientResultsHashset?.Clear(); - _clientResultsHashset = null; - - _hashProvider?.Dispose(); - _hashProvider = null; - } - - /// - /// Add a vote for a ClientId. If a client already passed the vote subsequent times are ignored. - /// - /// The clients id - /// The result as byte[] array - public void AddVote(string clientId, byte[] data) { - if (_clientResultsHashset.Contains(clientId) == false) { - var hash = _hashProvider.ComputeHash(data); - var result = new ClientResult { ClientId = clientId, Result = data, Hash = hash }; - _clientResults.Add(result); - _clientResultsHashset.Add(clientId); - } - - if (_startWaitTimeSet == false && Count >= 2) { - // Only start the waitime when two votes have been cast. Two votes because one would be too easy to missuse. - _startWaitTime = DateTime.Now; - _startWaitTimeSet = true; - } - } - - /// - /// Run majority vote for the votes that have been cast. - /// - /// Summary of the results, sorted by count - /// True if there has been consensus. - public bool Evaluate(out List finalResults) { - var map = new Dictionary(ByteArrayComparer.Default); - - var majority = 0; - if (_clientResults.Count % 2 == 0) { - majority = _clientResults.Count / 2 + 1; - } - else { - majority = (int)Math.Ceiling(_clientResults.Count / (double)2); - } - - // Compare each result hash with eath other - for (int i = 0; i < _clientResults.Count; i++) { - var vote = default(FinalResult); - if (map.TryGetValue(_clientResults[i].Hash, out vote) == false) { - vote = new FinalResult { ClientIds = new List(), Result = _clientResults[i].Result }; - map.Add(_clientResults[i].Hash, vote); - } - - vote.ClientIds.Add(_clientResults[i].ClientId); - vote.Count++; - } - - // Sort - finalResults = new List(); - foreach (var v in map) { - finalResults.Add(v.Value); - } - finalResults.Sort(FinalResult.CompareByCount); - - if (finalResults.Count > 0 && finalResults[0].Count >= majority) { - return true; - } - - return false; - } - - public class FinalResult { - public byte[] Result; - public int Count; - public List ClientIds; - - public static int CompareByCount(FinalResult a, FinalResult b) { - return a.Count.CompareTo(b.Count); - } - } - - private class ClientResult { - public string ClientId; - public byte[] Result; - public byte[] Hash; - } - - private class ByteArrayComparer : IEqualityComparer { - private static ByteArrayComparer _default; - - public static ByteArrayComparer Default { - get { - if (_default == null) { - _default = new ByteArrayComparer(); - } - - return _default; - } - } - - public bool Equals(byte[] a, byte[] b) { - return StructuralComparisons.StructuralEqualityComparer.Equals(a, b); - } - - public int GetHashCode(byte[] obj) { - return StructuralComparisons.StructuralEqualityComparer.GetHashCode(obj); - } - } - } -} -Back To Top - - -Unity Test Code -Sending to the plugin using OpRaiseEvent() -Uses ByteArraySlice to send binary data to reduce further allocations -The result should be taken from a verified frame and it should be the same on each (untampered) client -using ExitGames.Client.Photon; -using Photon.Realtime; -using Quantum.Demo; -using UnityEngine; - -public class SendGameResult : MonoBehaviour { - private readonly ByteArraySlice _sendSlice = new ByteArraySlice(); - - private class GameResult { - public struct Place { - public string PlayerId; - public int Points; - } - public Place[] Ranking; - - public void Serialize(Photon.Deterministic.BitStream stream) { - foreach (var r in Ranking) { - stream.WriteString(r.PlayerId); - stream.WriteInt(r.Points); - } - } - } - - private void Update() { - // Use ByteSliceArray for optimization (non-alloc) - // Gather results from a verified frame only (otherwise prediciton can differ) - if (Input.GetKeyDown(KeyCode.Space)) { - var gameResult = new GameResult { Ranking = new GameResult.Place[] { - new GameResult.Place { PlayerId = "a", Points = 1 }, - new GameResult.Place { PlayerId = "b", Points = 2 } } - }; - - var stream = new Photon.Deterministic.BitStream(new byte[100 * 1024]); - gameResult.Serialize(stream); - - _sendSlice.Buffer = stream.Data; - _sendSlice.Count = stream.BytesRequired; - _sendSlice.Offset = 0; - - UIMain.Client.OpRaiseEvent(41, _sendSlice, RaiseEventOptions.Default, SendOptions.SendReliable); - } - } -} \ No newline at end of file diff --git a/data/static colliders.txt b/data/static colliders.txt deleted file mode 100644 index 75564d0033d4f0a209a48adcb273c11c9fb55446..0000000000000000000000000000000000000000 --- a/data/static colliders.txt +++ /dev/null @@ -1,172 +0,0 @@ -Introduction -Adding static colliders to a Scene takes three simple steps: - -Attach a Quantum Static Collider Script to a Unity GameObject; -Edit the properties to resemble the geometry you want for the static obstacle in the scene; and, -Bake the Scene via the MapData Script. -Step 1 & 2 - Add Static Colliders to GameObject in Unity Scene and adjust Settings -Step 1 & 2 - Add Static Colliders to GameObject in Unity Scene and adjust Settings. -Step 3 - Baking the Map Saves the Scene Colliders as a Quantum Asset (Map) -Step 3 - Baking the Map Saves the Scene Colliders as a Quantum Asset (Map). -Back To Top - - -Unity Collider As A Source -The Quantum static collider can also mirror the properties from a Unity collider. To do that, simply drag and drop the desired collider into the Source Collider field on the Quantum Static Collider component: - -unity-collider-source -Back To Top - - -Shapes -The 2D Physics shapes are: - -Circle -Box -Polygon -N.B.: All of them have a Height field, which allows the creation of 2.5D shapes. -The 3D Physics shapes are: - -Sphere -Box -Mesh -Back To Top - - -Configuration -Static colliders can be fitted with a PhysicsMaterial and a User Asset. The latter is available in the simulation via the collision callbacks. - - -Smooth Sphere Mesh Collider -A Static Mesh Collider 3D has an option called Smooth Sphere Mesh Collision. When toggling this option on, the physics solver will resolve sphere-mesh collisions as if the mesh was a regular flat and smooth plane. This prevents adding spin to a sphere colliding with triangle edges. - -Static Smooth Mesh Collider -Static Mesh Collider. -If the `Static Mesh Collider 3D` is marked with the `Smooth Sphere Mesh Collision` option but the mesh is not completely flat, it might result in undesirable collision responses. -Back To Top - - -Enable / Disable At Runtime -This section will present several approaches to enable and disable static colliders at runtime in simulation. - - -Physics Engine -It is possible to toggle static colliders on and off at runtime directly in the Physics Engine. - -For a static collider to be toggle-able, its Mode needs to be set at edit-time (Unity) and baked into the Map asset. The mode can be set to: - -Immutable (default): the collider cannot be enabled or disabled at runtime. -Toggleable Start On: the collider can be toggled at runtime and starts enabled. -Toggleable Start Off: the collider can be toggled at runtime and starts disabled. -Enable toggle on 3D Static Mesh Colliders -Enable toggle on a 3D Static Mesh Collider component. -Once a static collider has been marked as toggle-able and baked, it becomes possible to enable and disable the collider at runtime from the simulation (Quantum) using SetStaticColliderEnabled() in Frame.Physics3D and Frame.Physics2D for 3D and 2D static colliders respectively. - -The index to be passed as a parameter is the collider's index in the frame.Map.StaticColliders array. Collision callbacks return the index (ColliderIndex) of a static collider as part of the StaticData in their TriggerInfo or CollisionInfo. - -IMPORTANT: A disabled static mesh collider is ignored by physics queries and will not trigger collision signals. - -Back To Top - - -Manual Tracking -Although static colliders can be enabled / disabled at the physics engine level, there are various approaches to do the same manually. - - -Keep A Global Bitset For The State -If the only purpose is to keep track of which static colliders to ignore or take into account in a collision callback, the most convenient approach is to define a global BitSet which is of the same length or bigger than the frame.Map.StaticColliders array. This can be done as part of the Frame object or as a singleton component. - -singleton component StaticColliderState { - bitset[256] colliders; -} -This allows to use the bitset instance with the collider indices to set its bits. - -// loops through the bitset to initialize all bits as "On" to mark all colliders as active -public override void OnInit(Frame f) -{ - var collidersState = f.Unsafe.GetPointerSingleton(); - for (int i = 0; i < collidersState->colliders.Length; i++) { - collidersState->colliders.Set(i); - } -} - -public void OnTrigger3D(Frame frame, TriggerInfo3D info) -{ - if (info.IsStatic == false) return; - - // Use a custom asset slotted in the UserAsset field to identify toggleable colliders - var colliderAsset = frame.FindAsset(info.StaticData.Asset); - if (colliderAsset == null) return; - - var collidersState = frame.Unsafe.GetPointerSingleton(); - collidersState->colliders.Clear(info.StaticData.ColliderIndex); -} -The values can then be read using IsSet() and used to check whether a collision signal should be handled or ignored. This is particularly useful when dealing with static interactable objects, environmental barriers or implementing IKCCCallbacks3D for movement. - -Back To Top - - -Toggle With Behaviour -Static colliders are assets, i.e. they are stateless and immutable at runtime. However, there are instance where static objects should be enabled / disabled based on dynamic conditions. - -For example, pick-ups can usually be represented with a static position and a trigger collider; turning those into static colliders will avoid the over associated with dynamic entities. Unfortunately, the timer commonly to re-spawn a power-up after its pick-up cooldown requires a state. It is possible to solve this conundrum by extending the concept presented in the previous section. - -First, the state of the static colliders representing power-ups needs to be held somewhere. - -singleton component PowerUps { - [ExcludeFromPrototype] bitset[256] IsPowerUp; - [ExcludeFromPrototype] bitset[256] State; - [ExcludeFromPrototype] array[256] Timers; - FP SpawnCooldown; -} -Then a system can be created to handled the enabling and disabling of the power-ups. - -public unsafe class MyPowerUpSystem : SystemMainThread { -public override void OnInit(Frame f) -{ - var powerUps = f.Unsafe.GetPointerSingleton(); - for (int i = 0; i < powerUps->IsPowerUp.Length; i++) - { - var powerUp = f.FindAsset(f.Map.StaticColliders3D[i].StaticData.Asset); - if (powerUp == null) { - powerUps->IsPowerUp.Clear(i); - continue; - } - - powerUps->IsPowerUp.Set(i); - powerUps->State.Set(i); - powerUps->Timers[i] = FP._0; - } -} - -public override void Update(Frame f){ - var powerUps = f.Unsafe.GetPointerSingleton(); - for (int i = 0; i < powerUps->IsPowerUp.Length; i++) - { - if (powerUps->IsPowerUp.IsSet(i) == false) continue; - if (powerUps->State.IsSet(i)) continue; - - powerUps->Timers[i] -= f.DeltaTime; - if(powerUps->Timers[i] > 0) continue; - - powerUps->State.Set(i); - // Other code visualizing the spawned / re-enabled power-up - // can use frame event to trigger VFX, SFX, re-enable visual / GameObject - } - -} - -public void OnTrigger3D(Frame frame, TriggerInfo3D info) -{ - if(info.IsStatic == false) return; - - var powerUps = f.Unsafe.GetPointerSingleton(); - - if(powerUps->IsPowerUp.IsSet(info.StaticData.ColliderIndex) == false) return; - if(powerUps->State.IsSet(info.StaticData.ColliderIndex) == false) return; - - powerUps->State.Clear(info.StaticData.ColliderIndex); - powerUps->Timers[info.StaticData.ColliderIndex] = powerUps->SpawnCooldown; - - // Remember to communicate the disabled state visually, e.g. trigger a frame event to disable the GameObject in Unity -} \ No newline at end of file diff --git a/data/systems game logic.txt b/data/systems game logic.txt deleted file mode 100644 index 8d0c5a9932075f90e4ed7819581f5d1818637cb5..0000000000000000000000000000000000000000 --- a/data/systems game logic.txt +++ /dev/null @@ -1,373 +0,0 @@ -Introduction -Systems are the entry points for all gameplay logic in Quantum. - -They are implemented as normal C# classes, although a few restrictions apply for a System to be compliant with the predict/rollback model: - -Have to be stateless (gameplay data - an instance of the Frame class - will be passed as a parameter by Quantum's simulator to every system Update); -Implement and/or use only deterministic libraries and algorithms (we provide libraries for fixed point math, vector math, physics, random number generation, path finding, etc); -Reside in the Quantum namespace; -There are three base system classes one can inherit from: - -SystemMainThread: for simple gameplay implementation (init and update callbacks + signals). -SystemSignalsOnly: update-less system just to implement signals (reduces overhead by not scheduling a task for it). -SystemBase: advanced uses only, for scheduling parallel jobs into the task graph (not covered in this basic manual). -Back To Top - - -Core Systems -By default the Quantum SDK includes all Core systems in the SystemSetup. - -Core.CullingSystem2D(): Culls entities with a Transform2D component in predicted frames. -Core.CullingSystem3D(): Culls entities with a Transform3D component in predicted frames. -Core.PhysicsSystem2D(): Runs physics on all entities with a Transform2D AND a PhysicsCollider2D component. -Core.PhysicsSystem3D(): Runs physics on all entities with a Transform3D AND a PhysicsCollider3D component. -Core.NavigationSystem(): Used for all NavMesh related components. -Core.EntityPrototypeSystem(): Creates, Materializes and Initializes EntityPrototypes. -Core.PlayerConnectedSystem(): Used to trigger the ISignalOnPlayerConnected and ISignalOnPlayerDisconnected signals. -Core.DebugCommand.CreateSystem(): Used by the state inspector to send data to instantiate / remove / modify entities on the fly (Only available in the Editor!). -All systems are included by default for the user's convenience. Core systems can be selectively added / removed based on the game's required functionalities; e.g. only keep the PhysicsSystem2D or PhysicsSystem3D based on whether the game is 2D or 3D. - -Back To Top - - -Basic System -A most basic System in Quantum is a C# class that inherits from SystemMainThread. The skeleton implementation requires at least the Update callback to be defined: - -namespace Quantum -{ - public unsafe class MySystem : SystemMainThread - { - public override void Update(Frame f) - { - } - } -} -These are the callbacks that can be overridden in a System class: - -OnInit(Frame f): called only once, when the gameplay is being initialized (good place to set up game control data, etc); -Update(Frame f): used to advance the game state (game loop entry point); -OnDisabled(Frame f) and OnEnabled(Frame f): called when a system is disabled/enabled by another system; -Notice that all available callbacks include the same parameter (an instance of Frame). The Frame class is the container for all the transient and static game state data, including entities, physics, navigation and others like immutable asset objects (which will be covered in a separate chapter). - -The reason for this is that Systems must be stateless to comply with Quantum's predict/rollback model. Quantum only guarantees determinism if all (mutable) game state data is fully contained in the Frame instance. - -It is valid to create read-only constants or private methods (that should receive all need data as parameters). - -The following code snippet shows some basic examples of valid and not valid (violating the stateless requirement) in a System: - -namespace Quantum -{ - - public unsafe class MySystem : SystemMainThread - { - // this is ok - private const int _readOnlyData = 10; - // this is NOT ok (this data will not be rolled back, so it would lead to instant drifts between game clients during rollbacks) - private int _transientData = 10; - - public override void Update(Frame f) - { - // ok to use a constant to compute something here - var temporaryData = _readOnlyData + 5; - - // NOT ok to modify transient data that lives outside of the Frame object: - _transientData = 5; - } - } - -} -Back To Top - - -System Setup -Concrete System classes must be injected into Quantum's simulator during gameplay initialization. This is done in the SystemSetup.cs file : - -namespace Quantum -{ - public static class SystemSetup - { - public static SystemBase[] CreateSystems(RuntimeConfig gameConfig, SimulationConfig simulationConfig) - { - return new SystemBase[] - { - // pre-defined core systems - new Core.PhysicsSystem(), - new Core.NavMeshAgentSystem(), - new Core.EntityPrototypeSystem(), - - // user systems go here - new MySystem(), - }; - } - } -} -Notice that Quantum includes a few pre-built Systems (entry point for the physics engine updates, navmesh and entity prototype instantiations). - -To guarantee determinism, the order in which Systems are inserted will be the order in which all callbacks will be executed by the simulator on all clients. So, to control the sequence in which your updates occur, just insert your custom systems in the desired order. - -Back To Top - - -Activating And Deactivating Systems -All injected systems are active by default, but it is possible to control their status in runtime by calling these generic functions from any place in the simulation (they are available in the Frame object): - -public override void OnInit(Frame f) -{ - // deactivates MySystem, so no updates (or signals) are called in it - f.SystemDisable(); - // (re)activates MySystem - f.SystemEnable(); - // possible to query if a System is currently enabled - var enabled = f.SystemIsEnabled(); -} -Any System can deactivate (and re-activate) another System, so one common pattern is to have a main controller system that manages the active/inactive lifecycle of more specialized Systems using a simple state machine (one example is to have an in-game lobby first, with a countdown to gameplay, then normal gameplay, and finally a score state). - -To make a system start disabled by default override this property: - -public override bool StartEnabled => false; -Back To Top - - -Special System Types -Although you are likely to use the default SystemMainThread type for most of your systems, Quantum offers several alternative options for specialized systems. - -System Description -SystemMainThread Most common system type. Implements a regular Update() with all the usual features. -SystemSignalsOnly Does not have an Update() function. It is meant for systems that focus solely on implementing and receiving signals from other systems. By avoiding the Update loop, it helps you save some overhead -SystemMainThreadFilter This type of system uses a FilterStruct of type T to filter a set of entities based on it, loop through them and calls a method. N.B.: It does not support the any and without parameters, If you need a more complex option, we advise you to inherit from SystemMainThread and iterate through the filter yourself (See the Components page for more information on FilterStructs and Filters). -Back To Top - - -System Groups -Systems can be setup and processed as a group. - -The first step involves creating a class inheriting from SystemMainThreadGroup. - -namespace Quantum -{ - public class MySystemGroup : SystemMainThreadGroup - { - public MySystemGroup(string update, params SystemMainThread[] children) : base(update, children) - { - } - } -} -The MySystemGroup system can now be used in SystemSetup.cs to group systems together. System groups can be used in mixed and matched with regular systems. - -namespace Quantum { - public static class SystemSetup { - public static SystemBase[] CreateSystems(RuntimeConfig gameConfig, SimulationConfig simulationConfig) { - return new SystemBase[] { - - new MyRegularSystem(), - - new MySystemGroup("Gameplay Systems", new MyMovementSystem(), new MyOrbitScanSystem()), - }; - } - } -} -This allows to enable / disable a set of systems with a single line of code. Enabling / disabling a system group will enable / disable all systems which are part of it. N.B.: The Frame.SystemEnable() and Frame.SystemDisable() methods identify systems by type; thus if there are to be several system groups, they each need their own implementation to allow enabling / disabling multiple system groups independently. - -Back To Top - - -Entity Lifecycle API -This section uses the direct API methods for entity creation and composition. Please refer to the chapter on entity prototypes for the the data-driven approach. - -To create a new entity instance, just use this (method returns an EntityRef): - -var e = frame.Create(); -Entities do not have pre-defined components any more, to add a Transform3D and a PhysicsCollider3D to this entity, just type: - -var t = Transform3D.Create(); -frame.Set(e, t); - -var c = PhysicsCollider3D.Create(f, Shape3D.CreateSphere(1)); -frame.Set(e, c); -These two methods are also useful: - -// destroys the entity, including any component that was added to it. -frame.Destroy(e); - -// checks if an EntityRef is still valid (good for when you store it as a reference inside other components): -if (frame.Exists(e)) { - // safe to do stuff, Get/Set components, etc -} -Also possible to check dynamically if an entity contains a certain component type, and get a pointer to the component data directly from frame: - -if (frame.Has(e)) { - var t = frame.Unsafe.GetPointer(e); -} -With ComponentSet, you can do a single check if an entity has multiple components: - -var components = ComponentSet.Create(); -if (frame.Has(e, components)) { - // do something -} -Removing components dynamically is as easy as: - -frame.Remove(e); -Back To Top - - -The EntityRef Type -Quantum's rollback model maintains a variable sized frame buffer; in other words several copies of the game state data (defined from the DSL) are kept in memory blocks at separate locations. This means any pointer to either an entity, component or struct is only valid within a single Frame object (updates, etc). - -Entity refs are safe-to-keep references to entities (temporarily replacing pointers) which work across frames, as long as the entity in question still exists. Entity refs contain the following data internally: - -Entity index: entity slot, from the DSL-defined maximum number for the specific type; -Entity version number: used to render old entity refs obsolete when an entity instance is destroyed and the slot can be reused for a new one. -Back To Top - - -Filters -Quantum v2 does not have entity types. In the sparse-set ECS memory model, entities are indexes to a collection of components; the EntityRef type holds some additional information such as versioning. These collections are kept in dynamically allocated sparse sets. Therefore, instead of iterating over a collection of entities, filters are used to create a set of components the system will work on. - -public unsafe class MySystem : SystemMainThread -{ - public override void Update(Frame f) - { - var filtered = frame.Filter(); - - while (filtered.Next(out var e, out var t, out var b)) { - t.Position += FPVector3.Forward * frame.DeltaTime; - frame.Set(e, t); - } - } -} -For a comprehensive view on how filters are used, please refer to the Components page. - -Back To Top - - -Pre-Built Assets And Config Classes -Quantum contains a few pre-built data assets that are always passed into Systems through the Frame object. - -These are the most important pre-built asset objects (from Quantum's Asset DB): - -Map and NavMesh: data about the playable area, static physics colliders, navigation meshes, etc... . Custom player data can be added from a data asset slot (will be covered in the data assets chapter); -SimulationConfig: general configuration data for physics engine, navmesh system, etc. -default PhysicsMaterial and agent configs (KCC, navmesh, etc): -The following snippets show how to access current Map and NavMesh instances from the Frame object: - -// Map is the container for several static data, such as navmeshes, etc -Map map = f.Map; -var navmesh = map.NavMeshes["MyNavmesh"]; -Back To Top - - -Assets Database -All Quantum data assets are available inside Systems through the dynamic asset DataBase API. The following snippets (DSL then C# code from a System) shows how to acquire a data asset from the database and assign it to an asset_ref slot into a Character. First you declare the asset in a qtn file, and create a component that can hold it: - -asset CharacterSpec; - -component CharacterData -{ - asset_ref Spec; - // other data -} -Once the asset and component holding a reference to it are declared, you can set the reference in a system like so: - -// C# code from inside a System - -// grabing the data asset from the database, using the unique GUID (long) or path (string) -var spec = frame.FindAsset("path-to-spec"); -// assigning the asset reference assuming you have a pointer to CharacterData component -data->Spec = spec; -Data assets are explained in more detail in their own chapter (including options on how to populate it either through Unity scriptable objects - default; custom serializers or procedurally generated content). - -Back To Top - - -Signals -As explained in the previous chapter, signals are function signatures used to generate a publisher/subscriber API for inter-systems communication. - -The following example in a DSL file (from the previous chapter): - -signal OnDamage(FP damage, entity_ref entity); -Would lead to this trigger signal being generated on the Frame class (f variable), which can be called from "publisher" Systems: - -// any System can trigger the generated signal, not leading to coupling with a specific implementation -f.Signals.OnDamage(10, entity) -A "subscriber" System would implement the generated "ISignalOnDamage" interface, which would look like this: - -namespace Quantum -{ - class CallbacksSystem : SystemSignalsOnly, ISignalOnDamage - { - public void OnDamage(Frame f, FP damage, EntityRef entity) - { - // this will be called everytime any other system calls the OnDamage signal - } - - } -} -Notice signals always include the Frame object as the first parameter, as this is normally needed to do anything useful to the game state. - -Back To Top - - -Generated And Pre-Built Signals -Besides explicit signals defined directly in the DSL, Quantum also includes some pre-built ("raw" physics collision callbacks, for example) and generated ones based on the entity definitions (entity-type-specific create/destroy callbacks). - -The collision callback signals will be covered in the specific chapter about the physics engine, so here's a brief description of other pre-built signals: - -ISignalOnPlayerDataSet: called when a game client sends an instance of RuntimePlayer to server (and the data is confirmed/attached to one tick). -ISignalOnAdd, ISignalOnRemove: called when a component type T is added/removed to/from an entity. -Back To Top - - -Triggering Events -Similar to what happens to signals, the entry point for triggering events is the Frame object, and each (concrete) event will result in a specific generated function (with the event data as the parameters). - -// taking this DSL event definition as a basis -event TriggerSound -{ - FPVector2 Position; - FP Volume; -} -This can be called from a System to trigger an instance of this event (processing it from Unity will be covered on the chapter about the bootstrap project): - -// any System can trigger the generated events (FP._0_5 means fixed point value for 0.5) -f.Events.TriggerSound(FPVector2.Zero, FP._0_5); -Important to reinforce that events MUST NOT be used to implement gameplay itself (as the callbacks on the Unity side are not deterministic). Events are just a one-way fine-grained API to communicate the rendering engine of detailed game state updates, so the visuals, sound and any UI-related object can be updated on Unity. - -Back To Top - - -Extra Frame API Items -The Frame class also contains entry points for several other deterministic parts of the API that need to be treated as transient data (so rolled back when needed). The following snippet shows the most important ones: - -// RNG is a pointer. -// Next gives a random FP between 0 and 1. -// There are also bound options for both FP and int -f.RNG->Next(); - -// any property defined in the global {} scope in the DSL files is accessed through the Global pointer -var d = f.Global->DeltaTime; - -// input from a player is referenced by its index (i is a pointer to the DSL defined Input struct) -var i = f.GetPlayerInput(0); -Back To Top - - -Optimization By Scheduling -To optimize systems identified as performance hotspots a simple modulo-based entity scheduling can help. Using this only a subset of entities are updated while iterating through them each tick. - -public override void Update(Frame frame) { - foreach (var (entity, c) in f.GetComponentIterator()) { - const int schedulePeriod = 5; - if (frame.Number % entity.Index == frame.Number % schedulePeriod) { - // it is time to update this entity - } -} -Choosing a schedulePeriod of 5 will make the entity only be updated every 5th tick. Choosing 2 would mean every other tick. - -This way the total number of updates is significantly reduced. To avoid updating all entities in one tick adding entity.Index will make the load be spread over multiple frames. - -Deferring the entity update like this has requirements on the user code: - -The deferred update code has to be able to handle different delta times. -The entity lazy "responsiveness" may be visually noticeable. -Using entity.Index may add to the laziness because new information is processed sooner or later for different entities. -The quantum navigation system has this feature build-in. \ No newline at end of file diff --git a/data/unity.txt b/data/unity.txt deleted file mode 100644 index 2b206611021a0a277a954fd58abd407203447f29..0000000000000000000000000000000000000000 --- a/data/unity.txt +++ /dev/null @@ -1,56 +0,0 @@ -Extending Assets for Unity -Overview -Example -Access At Runtime -Access At Edit-time - -Overview -Quantum assets can be extended with Unity-specific data not relevant for the simulation like data for the UI (colors, texts, icons...). This is done with the use of partial classes. - -Back To Top - - -Example -Let's take the CharacterSpec asset as an example. Its ScriptableObject-based wrapper in Unity is called CharacterSpecAsset and is the type which needs to extended. - -public partial class CharacterSpecAsset { - [Header("Unity")] - public Sprtie Icon; - public Color Color; - public string DisplayName; -} -These fields can only be accessed in the View (Unity) and cannot be accessed or used in the simulation (Quantum). -The newly created partial class needs to be added to the same assembly as the original definition of CharacterSpecAsset. By default, all Unity-side Quantum code belongs to the PhotonQuantum assembly. - -To ensure the partial class belongs to the correct assembly use one of the following approaches: - -Save the class in Assets/Photon/Quantum/User directory. -Save the class in any directory that has an AssemblyDefinitionReference asset pointing to the PhotonQuantum assembly. -Delete Assets/Photon/Quantum/PhotonQuantum.asmdef. This will make Quantum a part of the main assembly. Note that this step needs to be repeated after each Quantum SDK update. -Back To Top - - -Access At Runtime -To access the extra fields at runtime, use the UnityDB.FindAsset() method. - -CharacterSpecAsset characterSpecAsset = UnityDB.FindAsset(guid); -Debug.Log(characterSpecAsset.DisplayName); -Alternatively, the code-generated GetUnityAsset() extension methods can be used: - -CharacterSpec characterSpec = frame.FindAsset(guid); -CharacterSpecAsset characterSpecAsset = characterSpec.GetUnityAsset(); -Debug.Log(characterSpecAsset.DisplayName); -Both of the approaches will result in the asset being loaded into Quantum's AssetDB using the appropriate method, as discussed here: resources, addressables and asset bundles. - -Back To Top - - -Access At Edit-time -To load an asset using its path while in the Unity Editor, the UnityEditor.AssetDataBase.LoadAssetAtPath() method can be used. - -CharacterSpecAsset characterSpecAsset = UnityEditor.AssetDatabase.LoadAssetAtPath(path); -Debug.Log(characterSpecAsset.DisplayName); -Alternatively, the asset can be loaded using its AssetGuid via the UnityDB.FindAssetForInspector() method and casting the result to the correct type. - -CharacterSpecAsset characterSpecAsset = (CharacterSpecAsset)UnityDB.FindAssetForInspector(guid); -Debug.Log(characterSpecAsset.DisplayName); diff --git a/data/web Gl.txt b/data/web Gl.txt deleted file mode 100644 index c76891d9723de3e671dd08e419c9d4f5100339b6..0000000000000000000000000000000000000000 --- a/data/web Gl.txt +++ /dev/null @@ -1,27 +0,0 @@ -WebGL -Quantum supports multiple platforms, including WebGL, which comes with its unique challenges that developers must be aware of when working with it. This page provides a comprehensive list of these considerations. - - -Unity Versions -For optimal performance with WebGL, it is recommended to use Unity versions 2021.2.8f1 or later. Older versions of Unity may result in decreased performance when running in WebGL. The minimum acceptable version for building with WebGL is 2018.4.30f1. - -Quantum WebGL is supported since Quantum version 2.1 Build 967. - -Back To Top - - -WebGL Performance -WebGL is a unique environment that presents certain limitations. In general, performance is expected to be lower compared to other platforms. Hence, it is crucial to test the performance of your application in WebGL builds and not just within the editor to ensure optimal performance. - -When the runInBackground option is disabled in the Player Settings, the application will stop running when the player switches to another tab. If the tab remains inactive for an extended period, the client will disconnect and will require reestablishing the connection once the tab is brought back into focus. - -Given the low performance of WebGL, it is recommended to build both the Quantum code project in Release mode and set Unity to IL2CPP. Debug builds of the quantum code project can be extremely slow on WebGL. - -Unity WebGL builds do not support multithreading. The simulation is automatically confined to the main thread in WebGL and the ThreadCount setting in the SimulationConfig is disregarded. -WebSockets -Browsers cannot establish direct UDP connections, so WebSockets over TCP are utilized instead. However, TCP's reliable and sequenced transfer protocol can negatively impact gameplay for players with poor network connections. To provide the best player experience, it is recommended to also offer the game as a download. - -A warning that the application is switching to WebSockets may appear in the browser, but this can be safely ignored. - -Stack Traces -To enhance WebGL performance in release builds you can turn off the stack trace of logs in Unity. Go to edit > project settings > Player > Other Settings and scroll all the way down to Stack Trace* Set the stack trace of Warning and Log to None \ No newline at end of file