diff --git a/Assets/Scripts/GenerateDemoFiles.cs b/Assets/Scripts/GenerateDemoFiles.cs index f30520b4d57f3306f0ff738de1164569a2b27c7e..6203e440bfde7e9fdfea30060404e040bd022a64 100644 --- a/Assets/Scripts/GenerateDemoFiles.cs +++ b/Assets/Scripts/GenerateDemoFiles.cs @@ -46,11 +46,11 @@ public static void GenerateDemoA() buttom = new PointFact(Vector3.zero, Vector3.up, StageStatic.stage.solution), top = new PointFact(Vector3.zero + Vector3.up * minimalSolutionHight, Vector3.up, StageStatic.stage.solution); - StageStatic.stage.solution.Add(buttom, out _); - StageStatic.stage.solution.Add(top, out _, true); + StageStatic.stage.solution.Add(buttom, out _, false, null, null); + StageStatic.stage.solution.Add(top, out _, true, null, null); LineFact target = new LineFact(buttom.Id, top.Id, StageStatic.stage.solution); - var target_Id = StageStatic.stage.solution.Add(target, out _, true); + var target_Id = StageStatic.stage.solution.Add(target, out _, true, null, null); // Set Solution StageStatic.stage.solution.ValidationSet = @@ -92,11 +92,11 @@ public static void GenerateDemoB() buttom = new PointFact(Vector3.zero, Vector3.up, StageStatic.stage.solution), top = new PointFact(Vector3.zero + Vector3.up * minimalSolutionHight, Vector3.up, StageStatic.stage.solution); - StageStatic.stage.solution.Add(buttom, out _); - StageStatic.stage.solution.Add(top, out _, true); + StageStatic.stage.solution.Add(buttom, out _, false, null, null); + StageStatic.stage.solution.Add(top, out _, true, null, null); LineFact target = new LineFact(buttom.Id, top.Id, StageStatic.stage.solution); - var target_Id = StageStatic.stage.solution.Add(target, out _, true); + var target_Id = StageStatic.stage.solution.Add(target, out _, true, null, null); // Set Solution StageStatic.stage.solution.ValidationSet = diff --git a/Assets/Scripts/IJSONsavable.cs b/Assets/Scripts/IJSONsavable.cs new file mode 100644 index 0000000000000000000000000000000000000000..83571ab2711939958e0504bac8240e5859d6b3da --- /dev/null +++ b/Assets/Scripts/IJSONsavable.cs @@ -0,0 +1,405 @@ +using System.Collections.Generic; +using System.Reflection; +using System.Linq; +using System.IO; +using System; +using Newtonsoft.Json; +using UnityEngine; +using static CommunicationEvents; + + +// I would go for static virtual methods, but C#9 does not allow me... +// static methods cannot be overwritten -> virtual +public interface IJSONsavable<T> where T : IJSONsavable<T>, new() +{ + // stand-in for non static methods + public static readonly IJSONsavable<T> Instance = new T(); + + public static readonly FieldInfo[] JsonSaveableFields = + #region one-time-initialisation + typeof(T) + .GetFields( + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.Static) + .Where((field) + => !field.GetCustomAttributes().Any((attribute) + => attribute.GetType() == typeof(JsonIgnoreAttribute)) + && field.FieldType.GetInterfaces().Any((inter) + => inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IJSONsavable<>))) + .ToArray(); + #endregion one-time-initialisation + + public static readonly FieldInfo[] JsonAutoPreProcessFields = + #region one-time-initialisation + typeof(T) + .GetFields( + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.Static) + .Where((field) + => !field.GetCustomAttributes().Any((attribute) + => attribute.GetType() == typeof(JsonIgnoreAttribute)) + && field.GetCustomAttributes().Any((attribute) + => attribute.GetType() == typeof(JSONsavable.JsonAutoPreProcessAttribute)) + && field.FieldType.GetInterfaces().Any((inter) + => inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IJSONsavable<>))) + .ToArray(); + #endregion one-time-initialisation + + public static readonly FieldInfo[] JsonAutoPostProcessFields = + #region one-time-initialisation + typeof(T) + .GetFields( + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.Static) + .Where((field) + => !field.GetCustomAttributes().Any((attribute) + => attribute.GetType() == typeof(JsonIgnoreAttribute)) + && field.GetCustomAttributes().Any((attribute) + => attribute.GetType() == typeof(JSONsavable.JsonAutoPostProcessAttribute)) + && field.FieldType.GetInterfaces().Any((inter) + => inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IJSONsavable<>))) + .ToArray(); + #endregion one-time-initialisation + + public static readonly FieldInfo[] JsonSeperateFields = + #region one-time-initialisation + typeof(T) + .GetFields( + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.Static) + .Where((field) + => field.GetCustomAttributes().Any((attribute) + => attribute.GetType() == typeof(JSONsavable.JsonSeparateAttribute)) + && field.FieldType.GetInterfaces().Any((inter) + => inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IJSONsavable<>))) + .ToArray(); + #endregion one-time-initialisation + + + // TODO: this? + public string name { get; set; } + public string path { get; set; } + protected static List<Directories> + hierarchie = new List<Directories> { Directories.misc }; + + #region OverridableMethods + + public virtual string _IJGetName(string name) => name; + public virtual List<Directories> _IJGetHierarchie(List<Directories> hierarchie_base) + { + hierarchie_base ??= new List<Directories>(); + return hierarchie_base.Concat(hierarchie).ToList(); + } + public virtual bool _IJGetRawObject(out T payload, string path) => JSONsavable.ReadFromJsonFile<T>(out payload, path); + public virtual T _IJPreProcess(T payload) => payload; + public virtual T _IJPostProcess(T payload) => payload; + + #endregion OverridableMethods + + + #region MethodTemplates + + public bool store(List<Directories> hierarchie, string name, bool use_install_folder = false, bool overwrite = true, bool deep_store = true) + => store(hierarchie, name, (T)this, use_install_folder, overwrite, deep_store); + + public static bool store(List<Directories> hierarchie, string name, T payload, bool use_install_folder = false, bool overwrite = true, bool deep_store = true) + { + var new_hierarchie = + Instance._IJGetHierarchie(hierarchie); + var new_name = + Instance._IJGetName(name); + + string path = CreatePathToFile(out bool exists, new_name, "JSON", new_hierarchie, use_install_folder); + if (exists && !overwrite) + return false; + + // store fields decorated with JsonSeparateAttribute and implementing IJSONsavable<> separately + if (deep_store + && !store_children(hierarchie, name, payload, use_install_folder: false, overwrite, deep_store: true)) + return false; + + // store this + string path_o = payload.path; + payload.path = path; + + var new_payload = + preprocess(payload); + + payload.path = path_o; + + JSONsavable.WriteToJsonFile(path, new_payload); + return true; + } + + public bool store_children(List<Directories> hierarchie, string name, bool use_install_folder = false, bool overwrite = true, bool deep_store = true) + => store_children(hierarchie, name, (T)this, use_install_folder, overwrite, deep_store); + + public static bool store_children(List<Directories> hierarchie, string name, T payload, bool use_install_folder = false, bool overwrite = true, bool deep_store = true) + { + var new_hierarchie = + Instance._IJGetHierarchie(hierarchie); + var new_name = + Instance._IJGetName(name); + + for ((int max_i, bool success) = (0, true); max_i < JsonSeperateFields.Count(); max_i++) + { + var field = JsonSeperateFields[max_i]; + dynamic save_me = field.GetValue(payload); // is S:IJSONsavable<S> + + Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType); + Type[] store_args_type = new Type[] { typeof(List<Directories>), typeof(string), field.FieldType, typeof(bool), typeof(bool), typeof(bool) }; + object[] store_args = new object[] { new_hierarchie, new_name, save_me, use_install_folder, overwrite, deep_store }; + + var method = interface_type.GetMethod("store", store_args_type); + success &= (bool)method.Invoke(null, store_args); + + // in case of no success: delete it again + if (!success) + { + delete_children(hierarchie, name, use_install_folder, JsonSeperateFields.Count() - max_i); + return false; + } + } + + return true; + } + + public static bool load_children(List<Directories> hierarchie, string name, ref T raw_payload, bool use_install_folder = false, bool deep_load = true, bool post_process = true) + { + var new_hierarchie = + Instance._IJGetHierarchie(hierarchie); + var new_name = + Instance._IJGetName(name); + + bool success = true; + for (int max_i = 0; max_i < JsonSeperateFields.Count(); max_i++) + { + var field = JsonSeperateFields[max_i]; + + Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType); + Type[] load_args_type = new Type[] { typeof(List<Directories>), typeof(string), field.FieldType.MakeByRefType(), typeof(bool), typeof(bool), typeof(bool) }; + object[] load_args = new object[] { new_hierarchie, new_name, null, use_install_folder, deep_load, post_process }; + + var method = interface_type.GetMethod("load", BindingFlags.Public | BindingFlags.Static, null, load_args_type, null); + bool success_i = (bool)method.Invoke(null, load_args); + + field.SetValue(raw_payload, success_i ? load_args[2] : Activator.CreateInstance(field.FieldType)); + success &= success_i; + } + + return success; + } + + public static bool load(List<Directories> hierarchie, string name, out T payload, bool use_install_folder = false, bool deep_load = true, bool post_process = true) + { + payload = default(T); + bool success = true; + + var new_hierarchie = + Instance._IJGetHierarchie(hierarchie); + var new_name = + Instance._IJGetName(name); + + string path = CreatePathToFile(out bool loadable, new_name, "JSON", new_hierarchie, use_install_folder); + if (!loadable) + return false; + + if (!Instance._IJGetRawObject(out T raw_payload, path)) + return false; + raw_payload.name = new_name; + + // load fields decorated with JsonSeparateAttribute and implementing IJSONsavable<> separately + if (deep_load + && !load_children(hierarchie, name, ref raw_payload, false /*use_install_folder*/)) + success = false; + + payload = post_process + ? postprocess(raw_payload) + : raw_payload; + + return success; + } + + public static T postprocess(T payload) + { + if (payload == null) + return Instance._IJPostProcess(payload); + + for (int i = 0; i < JsonAutoPostProcessFields.Count(); i++) + { + var field = JsonAutoPostProcessFields[i]; + dynamic process_me = field.GetValue(payload); // is S:IJSONsavable<S> + + Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType); + Type[] process_args_type = new Type[] { field.FieldType }; + object[] process_args = new object[] { process_me }; + + var method = interface_type.GetMethod("postprocess", process_args_type); + var processed = method.Invoke(null, process_args); + + field.SetValue(payload, processed); + } + + return Instance._IJPostProcess(payload); + } + + public static T preprocess(T payload) + { + if (payload == null) + return Instance._IJPreProcess(payload); + + for (int i = 0; i < JsonAutoPreProcessFields.Count(); i++) + { + var field = JsonAutoPreProcessFields[i]; + dynamic process_me = field.GetValue(payload); // is S:IJSONsavable<S> + + Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType); + Type[] process_args_type = new Type[] { field.FieldType }; + object[] process_args = new object[] { process_me }; + + var method = interface_type.GetMethod("preprocess", process_args_type); + var processed = method.Invoke(null, process_args); + + field.SetValue(payload, processed); + } + + return Instance._IJPreProcess(payload); + } + + public static void delete_children(List<Directories> hierarchie, string name, bool use_install_folder = false, int skip_last_children = 0) + { + var new_hierarchie = + Instance._IJGetHierarchie(hierarchie); + var new_name = + Instance._IJGetName(name); + + for (int i = 0; i < JsonSeperateFields.Count() - skip_last_children; i++) + { + var field = JsonSeperateFields[i]; + + Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType); + Type[] delete_args_type = new Type[] { typeof(List<Directories>), typeof(string), typeof(bool) }; + object[] delete_args = new object[] { new_hierarchie, new_name, use_install_folder }; + + var method = interface_type.GetMethod("delete", delete_args_type); + method.Invoke(null, delete_args); + } + } + + public static bool delete(List<Directories> hierarchie, string name, bool use_install_folder = false) + { + var new_hierarchie = + Instance._IJGetHierarchie(hierarchie); + var new_name = + Instance._IJGetName(name); + + string path = CreatePathToFile(out bool _, new_name, "JSON", new_hierarchie, use_install_folder); + if (!delete(path)) + return false; + + delete_children(hierarchie, name, use_install_folder); + return true; + } + + // does not delete children! + private static bool delete(string path) + { + if (!File.Exists(path)) + return false; + + File.Delete(path); + return true; + } + + // public bool delete() => delete(hierarchie, name); + + #endregion MethodTemplates + +} + +public static class JSONsavable +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] + public sealed class JsonSeparateAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] + public sealed class JsonAutoPostProcessAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] + public sealed class JsonAutoPreProcessAttribute : Attribute { } + + /// <summary> + /// Writes the given object instance to a Json file, recursively, including public members, excluding [JsonIgnore]. + /// <para>Object type must have a parameterless constructor.</para> + /// <para>Only public properties and variables will be written to the file. These can be any type though, even other non-abstract classes.</para> + /// </summary> + /// <param name="filePath">The file path to write the object instance to.</param> + /// <param name="objectToWrite">The object instance to write to the file.</param> + public static bool WriteToJsonFile(string filePath, object objectToWrite) + { + // This tells your serializer that multiple references are okay. + var settings = new JsonSerializerSettings + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + }; + + TextWriter writer = null; + try + { + string payload = JsonConvert.SerializeObject(objectToWrite, settings); + writer = new StreamWriter(filePath); + writer.Write(payload); + return true; + } + catch (Exception e) + { + Debug.LogError(e); + return false; + } + finally + { + if (writer != null) + writer.Close(); + } + } + + /// <summary> + /// Reads an object instance from an Json file. + /// <para>Object type must have a parameterless constructor.</para> + /// </summary> + /// <typeparam name="T">The type of object to read from the file.</typeparam> + /// <param name="filePath">The file path to read the object instance from.</param> + /// <returns>Returns a new instance of the object read from the Json file.</returns> + public static bool ReadFromJsonFile<T>(out T payload, string filePath) where T : new() + { + payload = default(T); + TextReader reader = null; + + try + { + reader = new StreamReader(filePath); + var fileContents = reader.ReadToEnd(); + payload = JsonConvert.DeserializeObject<T>(fileContents); + return true; + } + catch (Exception e) + { + Debug.LogError(e); + return false; + } + finally + { + if (reader != null) + reader.Close(); + } + } + +} \ No newline at end of file diff --git a/Assets/Scripts/IJSONsavable.cs.meta b/Assets/Scripts/IJSONsavable.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..35b3b75b81fb85c6494b0aa557624e745b972270 --- /dev/null +++ b/Assets/Scripts/IJSONsavable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 515d546c824caf241ac2fe28cb89ebf9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/InteractionEngine/FactHandling/FactManager.cs b/Assets/Scripts/InteractionEngine/FactHandling/FactManager.cs index 8426ed9d9c2c4e816bfb8fe28ed7aa2dfaa0d76c..f096094ef1261ce2f4e9973b283c520cf0a140a2 100644 --- a/Assets/Scripts/InteractionEngine/FactHandling/FactManager.cs +++ b/Assets/Scripts/InteractionEngine/FactHandling/FactManager.cs @@ -1,4 +1,5 @@ -using UnityEngine; +using System; +using UnityEngine; /// <summary> /// Initiates named <see cref="Fact"/> and adds it to <see cref="StageStatic.stage.factState"/> @@ -15,44 +16,57 @@ public static class FactManager /// <param name="fact">to be added</param> /// <param name="exists"><c>true</c> iff <paramref name="fact"/> already has a equivalent counterpart in <paramref name="fact._Facts"/></param> /// \copydetails FactManager - public static Fact AddFactIfNotFound(Fact fact, out bool exists, bool samestep) + public static Fact AddFactIfNotFound(Fact fact, out bool exists, bool samestep, Type gadget, string scroll_label) { - return StageStatic.stage.factState[StageStatic.stage.factState.Add(fact, out exists, samestep)]; + return StageStatic.stage.factState[ + StageStatic.stage.factState.Add( + fact, out exists, samestep + , gadget ?? (scroll_label == null ? GadgetManager.activeGadget.GetType() : null) + , scroll_label + )]; } /// \copybrief FactManager <summary></summary> /// <param name="hit"><c>RaycastHit</c> where and how (orientation) to spawn <see cref="PointFact"/></param> /// \copydetails FactManager - public static PointFact AddPointFact(RaycastHit hit, bool samestep = false) + public static PointFact AddPointFact(RaycastHit hit, bool samestep = false, Type gadget = null, string scroll_label = null) { - return (PointFact) AddFactIfNotFound(new PointFact(hit.point, hit.normal, StageStatic.stage.factState), out _, samestep); + return (PointFact) AddFactIfNotFound( + new PointFact(hit.point, hit.normal, StageStatic.stage.factState) + , out _, samestep, gadget, scroll_label); } /// \copybrief FactManager <summary></summary> /// <param name="point">where to spawn <see cref="PointFact"/></param> /// <param name="normal">how (orientation) to spawn <see cref="PointFact"/></param> /// \copydetails FactManager - public static PointFact AddPointFact(Vector3 point, Vector3 normal, bool samestep = false) + public static PointFact AddPointFact(Vector3 point, Vector3 normal, bool samestep = false, Type gadget = null, string scroll_label = null) { - return (PointFact) AddFactIfNotFound(new PointFact(point, normal, StageStatic.stage.factState), out _, samestep); + return (PointFact) AddFactIfNotFound( + new PointFact(point, normal, StageStatic.stage.factState) + , out _, samestep, gadget, scroll_label); } /// \copybrief FactManager <summary></summary> /// <param name="pid"><see cref="Fact.Id"/> of <see cref="PointFact"/> which lies on <paramref name="lid"/></param> /// <param name="lid"><see cref="Fact.Id"/> of <see cref="LineFact"/> on which <paramref name="pid"/> lies</param> /// \copydetails FactManager - public static OnLineFact AddOnLineFact(string pid, string lid, bool samestep = false) + public static OnLineFact AddOnLineFact(string pid, string lid, bool samestep = false, Type gadget = null, string scroll_label = null) { - return (OnLineFact)AddFactIfNotFound(new OnLineFact(pid, lid, StageStatic.stage.factState), out _, samestep); + return (OnLineFact)AddFactIfNotFound( + new OnLineFact(pid, lid, StageStatic.stage.factState) + , out _, samestep, gadget, scroll_label); } /// \copybrief FactManager <summary></summary> /// <param name="pid1"><see cref="Fact.Id"/> of first <see cref="PointFact"/> defining a <see cref="LineFact"/></param> /// <param name="pid2"><see cref="Fact.Id"/> of second <see cref="PointFact"/> defining a <see cref="LineFact"/></param> /// \copydetails FactManager - public static LineFact AddLineFact(string pid1, string pid2, bool samestep = false) + public static LineFact AddLineFact(string pid1, string pid2, bool samestep = false, Type gadget = null, string scroll_label = null) { - return (LineFact)AddFactIfNotFound(new LineFact(pid1, pid2, StageStatic.stage.factState), out _, samestep); + return (LineFact)AddFactIfNotFound( + new LineFact(pid1, pid2, StageStatic.stage.factState) + , out _, samestep, gadget, scroll_label); } /// \copybrief FactManager @@ -62,9 +76,12 @@ public static LineFact AddLineFact(string pid1, string pid2, bool samestep = fal /// <param name="pid1"><see cref="Fact.Id"/> of first <see cref="PointFact"/> defining a <see cref="RayFact"/></param> /// <param name="pid2"><see cref="Fact.Id"/> of second <see cref="PointFact"/> defining a <see cref="RayFact"/></param> /// \copydetails FactManager - public static RayFact AddRayFact(string pid1, string pid2, bool samestep = false) + public static RayFact AddRayFact(string pid1, string pid2, bool samestep = false, Type gadget = null, string scroll_label = null) { - RayFact rayFact = (RayFact)AddFactIfNotFound(new RayFact(pid1, pid2, StageStatic.stage.factState), out bool exists, samestep); + RayFact rayFact = (RayFact)AddFactIfNotFound( + new RayFact(pid1, pid2, StageStatic.stage.factState) + , out bool exists, samestep, gadget, scroll_label); + if (exists) return rayFact; @@ -79,7 +96,8 @@ void AddHitIfOnLine(RaycastHit hit) { if (Math3d.IsPointApproximatelyOnLine(rayP1.Point, rayFact.Dir, hit.transform.position)) { - AddOnLineFact(hit.transform.gameObject.GetComponent<FactObject>().URI, rayFact.Id, true); + AddOnLineFact( + hit.transform.gameObject.GetComponent<FactObject>().URI, rayFact.Id, true, gadget, scroll_label); } } @@ -90,8 +108,8 @@ void AddHitIfOnLine(RaycastHit hit) AddHitIfOnLine(hit); // for good measure - AddOnLineFact(rayFact.Pid1, rayFact.Id, true); - AddOnLineFact(rayFact.Pid2, rayFact.Id, true); + AddOnLineFact(rayFact.Pid1, rayFact.Id, true, gadget, scroll_label); + AddOnLineFact(rayFact.Pid2, rayFact.Id, true, gadget, scroll_label); return rayFact; } @@ -101,9 +119,11 @@ void AddHitIfOnLine(RaycastHit hit) /// <param name="pid2"><see cref="Fact.Id"/> of second <see cref="PointFact"/> defining a <see cref="AngleFact"/></param> /// <param name="pid3"><see cref="Fact.Id"/> of third <see cref="PointFact"/> defining a <see cref="AngleFact"/></param> /// \copydetails FactManager - public static AngleFact AddAngleFact(string pid1, string pid2, string pid3, bool samestep = false) + public static AngleFact AddAngleFact(string pid1, string pid2, string pid3, bool samestep = false, Type gadget = null, string scroll_label = null) { - return (AngleFact)AddFactIfNotFound(new AngleFact(pid1, pid2, pid3, StageStatic.stage.factState), out _, samestep); + return (AngleFact)AddFactIfNotFound( + new AngleFact(pid1, pid2, pid3, StageStatic.stage.factState) + , out _, samestep, gadget, scroll_label); } } diff --git a/Assets/Scripts/InteractionEngine/FactHandling/FactOrganizer.cs b/Assets/Scripts/InteractionEngine/FactHandling/FactOrganizer.cs index 9e72d4b53960ecfa360ea4c92a6e20960a346556..6fac2c6c4c4e50eacf77e4da49e92f37aa66d485 100644 --- a/Assets/Scripts/InteractionEngine/FactHandling/FactOrganizer.cs +++ b/Assets/Scripts/InteractionEngine/FactHandling/FactOrganizer.cs @@ -171,15 +171,37 @@ protected internal struct meta /// </summary> public bool active; + /// <summary> + /// keeps track with wich <see cref="Gadget"/> the <see cref="Fact"/> is created + /// <c>null</c> iff its not the case + /// </summary> + public Type gadget { + get => _gadget; + set => _gadget = + value == null ? null : + Gadget.GadgetTypes.Contains(value) ? value : + typeof(Gadget.UndefinedGadget); + } + private Type _gadget; + + /// <summary> + /// keeps track with wich <see cref="Scroll"/> the <see cref="Fact"/> is created + /// <c>null</c> iff its not the case + /// </summary> + public string scroll_label; + /// <summary> /// Initiator /// </summary> /// <param name="workflow_id">sets <see cref="workflow_id"/></param> /// <param name="active">sets <see cref="active"/></param> - public meta(int workflow_id, bool active = true) + public meta(int workflow_id, bool active, Type gadget, string scroll_label) { this.workflow_id = workflow_id; this.active = active; + this.scroll_label = scroll_label; + _gadget = null; //Compiler Restriction + this.gadget = gadget; } } @@ -254,7 +276,7 @@ public static T ReInitializeFactOrganizer<T> old_to_new.Add(sn.Id, add.Id); } - target.Add(add, out _, sn.samestep); + target.Add(add, out _, sn.samestep, source.MetaInf[sn.Id].gadget, source.MetaInf[sn.Id].scroll_label); } else if (old_to_new.ContainsKey(sn.Id)) // Remove @@ -406,7 +428,7 @@ private void PruneWorkflow() /// <param name="samestep">set <c>true</c> if <see cref="Fact"/> creation happens as a subsequent/ consequent step of multiple <see cref="Fact"/> creations and/or deletions, /// and you whish that these are affected by a single <see cref="undo"/>/ <see cref="redo"/> step</param> /// <returns><see cref="Fact.Id"/> of <paramref name="value"/> or <see cref="FindEquivalent(Fact, out string, out bool)">found</see> <see cref="Fact"/> iff <paramref name="exists"/>==<c>true</c></returns> - public string Add(Fact value, out bool exists, bool samestep = false) + public string Add(Fact value, out bool exists, bool samestep, Type gadget, string scroll_label) { soft_resetted = false; #pragma warning disable IDE0018 // Inlinevariablendeklaration @@ -427,7 +449,7 @@ public string Add(Fact value, out bool exists, bool samestep = false) zombie.creation = false; // this meta entry will be deleted, but will not trigger deletion Workflow[MetaInf[key].workflow_id] = zombie; // set new init location - MetaInf[key] = new meta(marker, true); + MetaInf[key] = new meta(marker, true, gadget, scroll_label); } // zombies are undead! else if (MetaInf[key].active) @@ -440,7 +462,7 @@ public string Add(Fact value, out bool exists, bool samestep = false) { key = value.Id; FactDict.Add(key, value); - MetaInf.Add(key, new meta(marker, true)); + MetaInf.Add(key, new meta(marker, true, gadget, scroll_label)); } WorkflowAdd(new stepnote(key, samestep, true, this)); @@ -457,9 +479,7 @@ public string Add(Fact value, out bool exists, bool samestep = false) /// and you whish that these are affected by a single <see cref="undo"/>/ <see cref="redo"/> step</param> /// <returns><c>true</c> iff <paramref name="value"/><see cref="Fact.Id">.Id</see> was found.</returns> public bool Remove(Fact value, bool samestep = false) - { - return this.Remove(value.Id, samestep); - } + => this.Remove(value.Id, samestep); /// \copybrief Remove(Fact, bool) /// <remarks>this will not <see cref="Fact.delete(bool)">delete</see> a <see cref="Fact"/>, but sets it <see cref="meta.active">inactive</see> for later <see cref="Fact.delete(bool)">deletion</see> when <see cref="PruneWorkflow">pruned</see>.</remarks> @@ -679,7 +699,7 @@ public void fastforward() } FactOrganizer IJSONsavable<FactOrganizer>._IJPostProcess(FactOrganizer raw_payload) - => ReInitializeFactOrganizer<FactOrganizer>(raw_payload, false, out _); + => raw_payload == null ? raw_payload : ReInitializeFactOrganizer<FactOrganizer>(raw_payload, false, out _); /// <summary> /// Call this after assigning a stored instance in an empty world, that was not drawn. @@ -763,7 +783,7 @@ public bool DynamiclySolved( { MissingElements = new List<List<string>>(); // need to work not on ref/out - List<List<string>> Solution_L = new List<List<string>>(); + List<List<string>> Solution_L = new(); int MissingElementsCount = 0; var activeList = FactDict.Values.Where(f => MetaInf[f.Id].active); @@ -812,4 +832,14 @@ public bool DynamiclySolved( return MissingElementsCount == 0; } + public IEnumerable<Type> GetUsedGadgets() + => MetaInf.Values.Where(inf => inf.active & inf.gadget != null).Select(inf => inf.gadget).Distinct(); + + public int GetNumberOfGadgets() => GetUsedGadgets().Count(); + + public IEnumerable<string> GetUsedScrolls() + => MetaInf.Values.Where(inf => inf.active && inf.scroll_label != null).Select(inf => inf.scroll_label).Distinct(); + + public int GetNumberOfScrolls() => GetUsedScrolls().Count(); + } \ No newline at end of file diff --git a/Assets/Scripts/InteractionEngine/FactHandling/SolutionOrganizer.cs b/Assets/Scripts/InteractionEngine/FactHandling/SolutionOrganizer.cs index c2882c8ded2912cb878b9fef5f88dc62088c5f27..a015611bc99efb848ebca0d50d3912823c91985d 100644 --- a/Assets/Scripts/InteractionEngine/FactHandling/SolutionOrganizer.cs +++ b/Assets/Scripts/InteractionEngine/FactHandling/SolutionOrganizer.cs @@ -75,7 +75,7 @@ public string ComparerString } } /// <summary> - /// Collection of <c>string</c> representations of *all* available <see cref="FactComparer"/> to choose from. + /// Collection of <c>Type</c>s of *all* available <see cref="FactComparer"/> to choose from. /// </summary> [JsonIgnore] public static readonly IEnumerable<Type> fact_comparer = Assembly.GetExecutingAssembly().GetTypes().Where(typeof(FactComparer).IsAssignableFrom); @@ -144,6 +144,9 @@ public List<Fact> getMasterFactsByIndex (int i) string IJSONsavable<SolutionOrganizer>._IJGetName(string name) => name + endingVal; SolutionOrganizer IJSONsavable<SolutionOrganizer>._IJPostProcess(SolutionOrganizer raw_payload) { + if (raw_payload == null) + return raw_payload; + SolutionOrganizer payload = ReInitializeFactOrganizer<SolutionOrganizer> (raw_payload, false, out Dictionary<string, string> old_to_new); diff --git a/Assets/Scripts/InteractionEngine/Gadgets/AngleTool.cs b/Assets/Scripts/InteractionEngine/Gadgets/AngleTool.cs index d39ced488e0e4d32fb93a02a1440e6f8be033dbd..e037236a891b5dd09f939c57c56a5d9a0b7b79e2 100644 --- a/Assets/Scripts/InteractionEngine/Gadgets/AngleTool.cs +++ b/Assets/Scripts/InteractionEngine/Gadgets/AngleTool.cs @@ -24,69 +24,68 @@ public class AngleTool : Gadget private Vector3 angleMiddlePoint; private float curveRadius; - new void Awake() + public override void _Awake() { - base.Awake(); UiName = "Angle Mode"; if(MaxRange == 0) MaxRange = GlobalBehaviour.GadgetLaserDistance; } //Initialize Gadget when enabled AND activated - new void OnEnable() + public override void _OnEnable() { - base.OnEnable(); - this.ResetGadget(); + ResetGadget(); } - void OnDisable() + public override void _OnDisable() { - this.ResetGadget(); + ResetGadget(); } - public override void OnHit(RaycastHit hit) + public override void _OnHit(RaycastHit hit) { - if (!GadgetCanBeUsed) return; - if (!this.isActiveAndEnabled) return; + if (!GadgetCanBeUsed) return; + if (!isActiveAndEnabled) return; + if (hit.transform.gameObject.layer == LayerMask.NameToLayer("Point")) { PointFact tempFact = (PointFact)StageStatic.stage.factState[hit.transform.GetComponent<FactObject>().URI]; //If two points were already selected and now the third point got selected - if (this.angleModeIsFirstPointSelected && this.angleModeIsSecondPointSelected) + if (angleModeIsFirstPointSelected && angleModeIsSecondPointSelected) { //Create AngleFact //Check if new Point is equal to one of the previous points -> if true -> cancel - if (!(this.angleModeFirstPointSelected.Id == tempFact.Id || this.angleModeSecondPointSelected.Id == tempFact.Id)) + if (!(angleModeFirstPointSelected.Id == tempFact.Id || angleModeSecondPointSelected.Id == tempFact.Id)) { - FactManager.AddAngleFact(((PointFact)this.angleModeFirstPointSelected).Id, ((PointFact)this.angleModeSecondPointSelected).Id, ((PointFact)tempFact).Id); + FactManager.AddAngleFact((angleModeFirstPointSelected).Id, (angleModeSecondPointSelected).Id, tempFact.Id); } ResetGadget(); } //If only one point was already selected - else if (this.angleModeIsFirstPointSelected && !this.angleModeIsSecondPointSelected) + else if (angleModeIsFirstPointSelected && !angleModeIsSecondPointSelected) { //Check if the 2 selected points are the same: If not - if (this.angleModeFirstPointSelected.Id != tempFact.Id) + if (angleModeFirstPointSelected.Id != tempFact.Id) { - this.angleModeIsSecondPointSelected = true; - this.angleModeSecondPointSelected = tempFact; + angleModeIsSecondPointSelected = true; + angleModeSecondPointSelected = tempFact; ActivateCurveDrawing(); } - else - { - this.angleModeFirstPointSelected = null; - this.angleModeIsFirstPointSelected = false; - } + //else + //{ + // angleModeFirstPointSelected = null; + // angleModeIsFirstPointSelected = false; + //} } //If no point was selected before else { //Save the first point selected - this.angleModeIsFirstPointSelected = true; - this.angleModeFirstPointSelected = tempFact; + angleModeIsFirstPointSelected = true; + angleModeFirstPointSelected = tempFact; } } //No point was hit @@ -100,33 +99,33 @@ public override void OnHit(RaycastHit hit) void Update() { - if (!this.isActiveAndEnabled) return; - if (this.curveDrawingActivated) - UpdateCurveDrawing(this.Cursor.transform.position); + if (!isActiveAndEnabled) return; + if (curveDrawingActivated) + UpdateCurveDrawing(Cursor.transform.position); } private void ResetGadget() { - this.angleModeIsFirstPointSelected = false; - this.angleModeFirstPointSelected = null; - this.angleModeIsSecondPointSelected = false; - this.angleModeSecondPointSelected = null; + angleModeIsFirstPointSelected = false; + angleModeFirstPointSelected = null; + angleModeIsSecondPointSelected = false; + angleModeSecondPointSelected = null; DeactivateCurveDrawing(); } //Expect a LineFact here, where Line.Pid2 will be the Basis-Point of the angle public void ActivateCurveDrawing() { - this.lineRenderer.enabled = true; + lineRenderer.enabled = true; //In AngleMode with 3 Points we want to draw nearly a rectangle so we add a startPoint and an Endpoint to this preview - this.lineRenderer.positionCount = curveDrawingVertexCount + 2; - this.lineRenderer.material = this.anglePreviewMaterial; + lineRenderer.positionCount = curveDrawingVertexCount + 2; + lineRenderer.material = anglePreviewMaterial; lineRenderer.startWidth = 0.05f; lineRenderer.endWidth = 0.05f; //Set CurveDrawing activated - this.curveDrawingActivated = true; + curveDrawingActivated = true; //curveEndPoint is a point on the Line selected, with some distance from point2 curveEndPoint = angleModeSecondPointSelected.Point + 0.3f * (angleModeFirstPointSelected.Point - angleModeSecondPointSelected.Point).magnitude * (angleModeFirstPointSelected.Point - angleModeSecondPointSelected.Point).normalized; @@ -150,7 +149,7 @@ public void UpdateCurveDrawing(Vector3 currentPosition) angleModeSecondPointSelected.Point }; - for (float ratio = 0; ratio <= 1; ratio += 1.0f / this.curveDrawingVertexCount) + for (float ratio = 0; ratio <= 1; ratio += 1.0f / curveDrawingVertexCount) { var tangentLineVertex1 = Vector3.Lerp(startPoint, curveMiddlePoint, ratio); var tangentLineVertex2 = Vector3.Lerp(curveMiddlePoint, curveEndPoint, ratio); @@ -168,10 +167,10 @@ public void UpdateCurveDrawing(Vector3 currentPosition) public void DeactivateCurveDrawing() { - this.lineRenderer.positionCount = 0; - this.linePositions = new List<Vector3>(); - this.curveDrawingActivated = false; - this.lineRenderer.enabled = false; + lineRenderer.positionCount = 0; + linePositions = new List<Vector3>(); + curveDrawingActivated = false; + lineRenderer.enabled = false; } } diff --git a/Assets/Scripts/InteractionEngine/Gadgets/ExtraGadget.cs b/Assets/Scripts/InteractionEngine/Gadgets/ExtraGadget.cs index 944e8f4cd9c037d7a541b0306126d6b854ecafe1..70acd17f9783ff6e9561ef4c662243edcda8a035 100644 --- a/Assets/Scripts/InteractionEngine/Gadgets/ExtraGadget.cs +++ b/Assets/Scripts/InteractionEngine/Gadgets/ExtraGadget.cs @@ -2,21 +2,18 @@ public class ExtraGadget : Gadget { - new void Awake() + public override void _Awake() { - base.Awake(); UiName = "Extra Mode"; MaxRange = GlobalBehaviour.GadgetPhysicalDistance; } - new void OnEnable() - { - base.OnEnable(); - } + public override void _OnDisable() { } - public override void OnHit(RaycastHit hit) - { + public override void _OnEnable() { } + public override void _OnHit(RaycastHit hit) + { + throw new System.NotImplementedException(); } - } diff --git a/Assets/Scripts/InteractionEngine/Gadgets/Gadget.cs b/Assets/Scripts/InteractionEngine/Gadgets/Gadget.cs index 194e6829cdc1a3d0e1b14e97fec7c4f6804df463..f7ca3e68ccb00ceaa0c22320a8627c469057d211 100644 --- a/Assets/Scripts/InteractionEngine/Gadgets/Gadget.cs +++ b/Assets/Scripts/InteractionEngine/Gadgets/Gadget.cs @@ -1,10 +1,14 @@ -using UnityEngine; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; /// <summary> /// Base class for all Gadgets to derive from. /// A Gadget is a tool for the player (and level editor) to interact with the GameWorld. /// </summary> -public class Gadget : MonoBehaviour +public abstract class Gadget: MonoBehaviour { /// <summary> Position in tool belt. </summary> /// <remarks>Set in Inspector or <see cref="Awake"/></remarks> @@ -19,30 +23,77 @@ public class Gadget : MonoBehaviour /// <summary>Which sprite to use</summary> /// <remarks>Set in Inspector</remarks> public Sprite Sprite; - /// <summary>Layers to ignore for thid gadget by default.</summary> + /// <summary>Layers to ignore for this gadget by default.</summary> /// <remarks>Set in Inspector</remarks> public LayerMask ignoreLayerMask; /// <summary>Which cursor to use</summary> /// <remarks>When not set in Inspector, will be searching for any <see cref="WorldCursor"/>.</remarks> public WorldCursor Cursor; - protected void Awake() + /// <summary> + /// Collection of <c>Type</c>s of *all* available <see cref="Gadget"/>s to choose from. + /// </summary> + public static readonly IEnumerable<Type> GadgetTypes = Assembly.GetExecutingAssembly().GetTypes().Where(typeof(Gadget).IsAssignableFrom); + + + void Awake() { if (Cursor == null) Cursor = GameObject.FindObjectOfType<WorldCursor>(); - CommunicationEvents.TriggerEvent.AddListener(OnHit); + CommunicationEvents.TriggerEvent.AddListener(_OnHit); + + _Awake(); } - protected void OnEnable() + void OnEnable() { this.Cursor.setLayerMask(~this.ignoreLayerMask.value); Cursor.MaxRange = MaxRange; + + _OnEnable(); } + void OnDisable() + { + _OnDisable(); + } + + public void OnHit (RaycastHit hit) + { + _OnHit(hit); + } + + public abstract void _Awake(); + public abstract void _OnEnable(); + public abstract void _OnDisable(); + /// <summary> /// Called when <see cref="CommunicationEvents.TriggerEvent"/> is invoked, a.k.a. when Player clicks in GameWorld. /// </summary> /// <param name="hit">the position where it was clicked</param> - public virtual void OnHit(RaycastHit hit) { } + public abstract void _OnHit(RaycastHit hit); + + public class UndefinedGadget : Gadget + { + public override void _Awake() + { + throw new NotImplementedException(); + } + + public override void _OnDisable() + { + throw new NotImplementedException(); + } + + public override void _OnEnable() + { + throw new NotImplementedException(); + } + + public override void _OnHit(RaycastHit hit) + { + throw new NotImplementedException(); + } + } } diff --git a/Assets/Scripts/InteractionEngine/Gadgets/LineTool.cs b/Assets/Scripts/InteractionEngine/Gadgets/LineTool.cs index a11985f3ed0d852997753c885244aba6d72f18dd..3945dd8ee3e779a397b2ffe99d3c330139352dee 100644 --- a/Assets/Scripts/InteractionEngine/Gadgets/LineTool.cs +++ b/Assets/Scripts/InteractionEngine/Gadgets/LineTool.cs @@ -15,27 +15,25 @@ public class LineTool : Gadget private List<Vector3> linePositions = new List<Vector3>(); public Material linePreviewMaterial; - new void Awake() + public override void _Awake() { - base.Awake(); UiName = "Line Mode"; if (MaxRange == 0) MaxRange = GlobalBehaviour.GadgetLaserDistance; } //Initialize Gadget when enabled AND activated - new void OnEnable() + public override void _OnEnable() { - base.OnEnable(); this.ResetGadget(); } - void OnDisable() + public override void _OnDisable() { this.ResetGadget(); } - public override void OnHit(RaycastHit hit) + public override void _OnHit(RaycastHit hit) { if (!GadgetCanBeUsed) return; if (!this.isActiveAndEnabled) return; diff --git a/Assets/Scripts/InteractionEngine/Gadgets/LotTool.cs b/Assets/Scripts/InteractionEngine/Gadgets/LotTool.cs index eeef723c9a94363071001077e72ff0b1aa25b5c4..67b1aa3ca2c1fdca71ccdf8498ce6975df5017b9 100644 --- a/Assets/Scripts/InteractionEngine/Gadgets/LotTool.cs +++ b/Assets/Scripts/InteractionEngine/Gadgets/LotTool.cs @@ -20,31 +20,26 @@ public class LotTool : Gadget private List<Vector3> linePositions = new List<Vector3>(); public Material linePreviewMaterial; - new void Awake() + public override void _Awake() { - base.Awake(); this.UiName = "Lot Mode"; if (MaxRange == 0) MaxRange = GlobalBehaviour.GadgetLaserDistance; } //Initialize Gadget when enabled AND activated - new void OnEnable() + public override void _OnEnable() { - base.OnEnable(); this.ResetGadget(); } - void OnDisable() + public override void _OnDisable() { this.ResetGadget(); } - public override void OnHit(RaycastHit hit) + public override void _OnHit(RaycastHit hit) { - - - void CreateRayAndAngles(string pidIntersectionPoint, string pidLotPoint, bool samestep) { FactManager.AddRayFact(pidIntersectionPoint, pidLotPoint, samestep); diff --git a/Assets/Scripts/InteractionEngine/Gadgets/Pendulum.cs b/Assets/Scripts/InteractionEngine/Gadgets/Pendulum.cs index f27caf4d834292f2308239ddaadce06a268dcbce..411a16673d5fce28bfce5fadb55dd09254bc2d02 100644 --- a/Assets/Scripts/InteractionEngine/Gadgets/Pendulum.cs +++ b/Assets/Scripts/InteractionEngine/Gadgets/Pendulum.cs @@ -13,27 +13,25 @@ public class Pendulum : Gadget private List<Vector3> linePositions = new List<Vector3>(); public Material linePreviewMaterial; - new void Awake() + public override void _Awake() { - base.Awake(); this.UiName = "Pendulum"; if (MaxRange == 0) MaxRange = GlobalBehaviour.GadgetLaserDistance; } - new void OnEnable() + public override void _OnEnable() { - base.OnEnable(); this.ResetGadget(); ActivateLineDrawing(); } - void OnDisable() + public override void _OnDisable() { this.ResetGadget(); } - public override void OnHit(RaycastHit hit) + public override void _OnHit(RaycastHit hit) { if (!GadgetCanBeUsed) return; @@ -44,8 +42,7 @@ public override void OnHit(RaycastHit hit) PointFact tempFact = StageStatic.stage.factState[hit.transform.GetComponent<FactObject>().URI] as PointFact; //Raycast downwoard - RaycastHit ground; - if(Physics.Raycast(tempFact.Point, Vector3.down, out ground, Mathf.Infinity, this.LayerPendulumHits.value)) + if(Physics.Raycast(tempFact.Point, Vector3.down, out RaycastHit ground, Mathf.Infinity, this.LayerPendulumHits.value)) { FactManager.AddPointFact(ground); } @@ -89,8 +86,7 @@ private void UpdateLineDrawing() this.linePositions[0] = this.Cursor.transform.position; //Raycast downwoard - RaycastHit ground; - if (Physics.Raycast(this.linePositions[0], Vector3.down, out ground, Mathf.Infinity, this.LayerPendulumHits.value)) + if (Physics.Raycast(this.linePositions[0], Vector3.down, out RaycastHit ground, Mathf.Infinity, this.LayerPendulumHits.value)) this.linePositions[1] = ground.point; else this.linePositions[1] = this.linePositions[0]; diff --git a/Assets/Scripts/InteractionEngine/Gadgets/Pointer.cs b/Assets/Scripts/InteractionEngine/Gadgets/Pointer.cs index 73dfcdcdd5c0c98b4c79bb356f0d56db5177ee9b..45d9f02fa26bc072cd89a23c9b00ff2a7be78f1d 100644 --- a/Assets/Scripts/InteractionEngine/Gadgets/Pointer.cs +++ b/Assets/Scripts/InteractionEngine/Gadgets/Pointer.cs @@ -5,21 +5,19 @@ public class Pointer : Gadget { - new void Awake() + public override void _Awake() { - base.Awake(); this.UiName = "Point Mode"; if (MaxRange == 0) MaxRange = GlobalBehaviour.GadgetLaserDistance; } - public override void OnHit(RaycastHit hit) + public override void _OnHit(RaycastHit hit) { - //Debug.Log("pointer1"); if (!GadgetCanBeUsed) return; - + if (!this.isActiveAndEnabled) return; - + var pid = FactManager.AddPointFact(hit).Id; if (hit.transform.gameObject.layer == LayerMask.NameToLayer("Ray")) @@ -27,5 +25,8 @@ public override void OnHit(RaycastHit hit) FactManager.AddOnLineFact(pid, hit.transform.GetComponent<FactObject>().URI, true); } } - + + public override void _OnEnable() { } + + public override void _OnDisable() { } } diff --git a/Assets/Scripts/InteractionEngine/Gadgets/PoleTool.cs b/Assets/Scripts/InteractionEngine/Gadgets/PoleTool.cs index 003d18e1819d90ad69d8beafbe383a7f1180179c..a95d945a08c2165b0d29b637bc3fbcc7243eb668 100644 --- a/Assets/Scripts/InteractionEngine/Gadgets/PoleTool.cs +++ b/Assets/Scripts/InteractionEngine/Gadgets/PoleTool.cs @@ -15,9 +15,8 @@ public class PoleTool : Gadget public float poleHeight = 1f; public float maxHeight; - new void Awake() + public override void _Awake() { - base.Awake(); UiName = "PoleTool"; if (MaxRange == 0) MaxRange = GlobalBehaviour.GadgetPhysicalDistance; @@ -25,19 +24,18 @@ public class PoleTool : Gadget maxHeight = 0.1f; } - new void OnEnable() + public override void _OnEnable() { - base.OnEnable(); this.ResetGadget(); ActivateLineDrawing(); } - void OnDisable() + public override void _OnDisable() { this.ResetGadget(); } - public override void OnHit(RaycastHit hit) + public override void _OnHit(RaycastHit hit) { if (!GadgetCanBeUsed) return; diff --git a/Assets/Scripts/InteractionEngine/Gadgets/Remover.cs b/Assets/Scripts/InteractionEngine/Gadgets/Remover.cs index 47016f20e86581879d6b19431db06f62e9cee2d0..69438a411b22e1e405e8b648c7b3b11f43d463d7 100644 --- a/Assets/Scripts/InteractionEngine/Gadgets/Remover.cs +++ b/Assets/Scripts/InteractionEngine/Gadgets/Remover.cs @@ -5,15 +5,18 @@ public class Remover : Gadget { - new void Awake() + public override void _Awake() { - base.Awake(); this.UiName = "Remove Mode"; if (MaxRange == 0) MaxRange = GlobalBehaviour.GadgetLaserDistance; } - public override void OnHit(RaycastHit hit) + public override void _OnDisable() { } + + public override void _OnEnable() { } + + public override void _OnHit(RaycastHit hit) { if (!GadgetCanBeUsed) return; diff --git a/Assets/Scripts/InteractionEngine/Gadgets/Tape.cs b/Assets/Scripts/InteractionEngine/Gadgets/Tape.cs index 933e75dba3b5009c049ff3d9e7e2da468009e726..273f96090c9fe7efb9dd3cf55c0bc8628d248fc1 100644 --- a/Assets/Scripts/InteractionEngine/Gadgets/Tape.cs +++ b/Assets/Scripts/InteractionEngine/Gadgets/Tape.cs @@ -16,29 +16,25 @@ public class Tape : Gadget private List<Vector3> linePositions = new List<Vector3>(); public Material linePreviewMaterial; - new void Awake() + public override void _Awake() { - base.Awake(); this.UiName = "Distance Mode"; if (MaxRange == 0) MaxRange = GlobalBehaviour.GadgetPhysicalDistance; - //test - //MaxRange = 100; } - //Initialize Gadget when enabled AND activated - new void OnEnable() + //Initialize Gadget when enabled + public override void _OnEnable() { - base.OnEnable(); this.ResetGadget(); } - void OnDisable() + public override void _OnDisable() { this.ResetGadget(); } - public override void OnHit(RaycastHit hit) + public override void _OnHit(RaycastHit hit) { if (!GadgetCanBeUsed) return; if (!this.isActiveAndEnabled) return; @@ -142,4 +138,5 @@ private void DeactivateLineDrawing() this.linePositions = new List<Vector3>(); this.lineRenderer.enabled = false; } + } diff --git a/Assets/Scripts/InventoryStuff/DisplayScrolls.cs b/Assets/Scripts/InventoryStuff/DisplayScrolls.cs index fbd9abf21f1faa7448ff70b8ce88481b46114a79..ae6a58960e4f9fcd1a446c3ac362f91aef3b1825 100644 --- a/Assets/Scripts/InventoryStuff/DisplayScrolls.cs +++ b/Assets/Scripts/InventoryStuff/DisplayScrolls.cs @@ -10,13 +10,11 @@ public class DisplayScrolls : MonoBehaviour public string preferredStartScrollName; public int tryScrollListTimes = 2; - public List<Scroll> scrolls; + static public List<Scroll> AvailableScrolls; public GameObject[] ScrollButtons; public GameObject ScrollPrefab; public GameObject DetailScreen; - - public int x_Start; public int y_Start; public int X_Pacece_Between_Items; @@ -24,23 +22,11 @@ public class DisplayScrolls : MonoBehaviour public int number_of_Column; - // Update is called once per frame - void Update() - { - - } - - public Vector3 GetPosition(int i) { return new Vector3(x_Start + (X_Pacece_Between_Items * (i % number_of_Column)), y_Start + (-y_Pacece_Between_Items * (i / number_of_Column)), 0f); } - /* [System.Serializable] - class ScrollArrayWrapper { - public Scroll[] Scrolls; - };*/ - // Start is called before the first frame update void Start() { @@ -106,22 +92,20 @@ IEnumerator getScrollsfromServer() void BuildScrolls(string jsonString) { - var scrolls = Scroll.FromJSON(jsonString); - this.scrolls = scrolls; - ScrollButtons = new GameObject[this.scrolls.Count]; + AvailableScrolls = Scroll.FromJSON(jsonString); + ScrollButtons = new GameObject[AvailableScrolls.Count]; //Build Selection-GUI of Scrolls - for (int i = 0; i < this.scrolls.Count; i++) + for (int i = 0; i < AvailableScrolls.Count; i++) { - var obj = Instantiate(ScrollPrefab, Vector3.zero, Quaternion.identity, transform); obj.GetComponent<RectTransform>().localPosition = GetPosition(i); - obj.GetComponent<ScrollClickedScript>().scroll = this.scrolls[i]; + obj.GetComponent<ScrollClickedScript>().scroll = AvailableScrolls[i]; obj.GetComponent<ScrollClickedScript>().DetailScreen = this.DetailScreen; - obj.transform.GetChild(0).gameObject.GetComponent<TextMeshProUGUI>().text = this.scrolls[i].label; + obj.transform.GetChild(0).gameObject.GetComponent<TextMeshProUGUI>().text = AvailableScrolls[i].label; ScrollButtons[i] = obj; } - Scroll preferredStartScroll = this.scrolls.Find(x => x.label.Equals(preferredStartScrollName)); + Scroll preferredStartScroll = AvailableScrolls.Find(x => x.label.Equals(preferredStartScrollName)); if (preferredStartScroll != null) this.DetailScreen.GetComponent<ScrollDetails>().setScroll(preferredStartScroll); } diff --git a/Assets/Scripts/InventoryStuff/ScrollDetails.cs b/Assets/Scripts/InventoryStuff/ScrollDetails.cs index 355b6b1c20de5a3411b7df3e61a808d956b67075..2536c3004f3ad5adf2d1d75dfee5e906e5405eb9 100644 --- a/Assets/Scripts/InventoryStuff/ScrollDetails.cs +++ b/Assets/Scripts/InventoryStuff/ScrollDetails.cs @@ -10,7 +10,7 @@ public class ScrollDetails : MonoBehaviour { public WorldCursor cursor; public GameObject parameterDisplayPrefab; - public Scroll scroll; + public Scroll ActiveScroll; public int x_Start; public int y_Start; @@ -44,7 +44,7 @@ public void setScroll(Scroll s) Transform originalScroll = gameObject.transform.GetChild(1).transform; Transform originalScrollView = originalScroll.GetChild(1); Transform originalViewport = originalScrollView.GetChild(0); - this.scroll = s; + this.ActiveScroll = s; originalScroll.GetChild(0).GetComponent<TextMeshProUGUI>().text = s.description; //Clear all current ScrollFacts @@ -117,11 +117,7 @@ IEnumerator sendView(string endpoint) using UnityWebRequest www = UnityWebRequest.Put(ServerAdress + endpoint, body); www.method = UnityWebRequest.kHttpVerbPOST; www.SetRequestHeader("Content-Type", "application/json"); - var async = www.SendWebRequest(); - while (!async.isDone) { - //Non blocking wait for one frame, for letting the game do other things - yield return null; - } + yield return www.SendWebRequest(); if (www.result == UnityWebRequest.Result.ConnectionError || www.result == UnityWebRequest.Result.ProtocolError) @@ -144,17 +140,19 @@ private string prepareScrollAssignments() for (int i = 0; i < ParameterDisplays.Count; i++) { - Scroll.ScrollAssignment listEntry = new Scroll.ScrollAssignment(); tempFact = ParameterDisplays[i].GetComponentInChildren<DropHandling>().currentFact; if (tempFact != null) { - listEntry.fact = new Scroll.UriReference(this.scroll.requiredFacts[i].@ref.uri); - listEntry.assignment = new JSONManager.OMS(tempFact.Id); + Scroll.ScrollAssignment listEntry = new Scroll.ScrollAssignment + { + fact = new Scroll.UriReference(ActiveScroll.requiredFacts[i].@ref.uri), + assignment = new JSONManager.OMS(tempFact.Id) + }; assignmentList.Add(listEntry); } } - Scroll.FilledScroll filledScroll = new Scroll.FilledScroll(this.scroll.@ref, assignmentList); + Scroll.FilledScroll filledScroll = new Scroll.FilledScroll(ActiveScroll.@ref, assignmentList); return Scroll.ToJSON(filledScroll); } @@ -169,7 +167,7 @@ private void readPushout(List<Scroll.ScrollFact> pushoutFacts) Fact newFact = ParsingDictionary.parseFactDictionary[pushoutFacts[i].getType()].Invoke(pushoutFacts[i]); if (newFact != null) { - PushoutFactEvent.Invoke(FactManager.AddFactIfNotFound(newFact, out bool exists, samestep)); + PushoutFactEvent.Invoke(FactManager.AddFactIfNotFound(newFact, out bool exists, samestep, null, ActiveScroll.label)); } else { Debug.Log("Parsing on pushout-fact returned null -> One of the dependent facts does not exist"); diff --git a/Assets/Scripts/JSONManager.cs b/Assets/Scripts/JSONManager.cs index afb7adfd623d55f05fbb5bd4a2c4fcde32932c7d..a0df7e51260ac9f07b61918e3b9140f322e680f8 100644 --- a/Assets/Scripts/JSONManager.cs +++ b/Assets/Scripts/JSONManager.cs @@ -1,244 +1,21 @@ using System.Collections.Generic; -using System.Reflection; -using System.IO; -using System; -using System.Linq; using Newtonsoft.Json; using JsonSubTypes; -using System.Collections; -using UnityEngine; -using Newtonsoft.Json.Linq; -using static CommunicationEvents; -// I would go for static virtual methods, but C#9 does not allow me... -// static methods cannot be overwritten -> virtual -public interface IJSONsavable<T> where T : IJSONsavable<T>, new() -{ - // stand-in for non static methods - public static readonly IJSONsavable<T> Instance = new T(); - public static readonly FieldInfo[] JsonSeperateFields = - typeof(T) - .GetFields( - BindingFlags.Instance | - BindingFlags.Public | - BindingFlags.NonPublic | - BindingFlags.Static ) - .Where((field) - => field.GetCustomAttributes().Any((attribute) - => attribute.GetType() == typeof(JSONManager.JsonSeparateAttribute)) - && field.FieldType.GetInterfaces().Any((inter) - => inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IJSONsavable<>))) - .ToArray(); - - - // TODO: this? - public string name { get; set; } - public string path { get; set; } - protected static List<Directories> - hierarchie = new List<Directories> { Directories.misc }; - - #region OverridableMethods - - public virtual string _IJGetName(string name) => name; - public virtual List<Directories> _IJGetHierarchie(List<Directories> hierarchie_base) - { - hierarchie_base ??= new List<Directories>(); - return hierarchie_base.Concat(hierarchie).ToList(); - } - public virtual bool _IJGetRawObject(out T payload, string path) => JSONManager.ReadFromJsonFile<T>(out payload, path); - public virtual T _IJPreProcess(T payload) => payload; - public virtual T _IJPostProcess(T payload) => payload; - - #endregion OverridableMethods - - - #region MethodTemplates - - public bool store(List<Directories> hierarchie, string name, bool use_install_folder = false, bool overwrite = true, bool deep_store = true) - => store(hierarchie, name, (T) this, use_install_folder, overwrite, deep_store); - - public static bool store(List<Directories> hierarchie, string name, T payload, bool use_install_folder = false, bool overwrite = true, bool deep_store = true) - { - var new_hierarchie = - Instance._IJGetHierarchie(hierarchie); - var new_name = - Instance._IJGetName(name); - - string path = CreatePathToFile(out bool exists, new_name, "JSON", new_hierarchie, use_install_folder); - if (exists && !overwrite) - return false; - - // store fields decorated with JsonSeparateAttribute and implementing IJSONsavable<> separately - if (deep_store - && !store_children(hierarchie, name, payload, use_install_folder: false, overwrite, deep_store: true)) - return false; - - // store this - string path_o = payload.path; - payload.path = path; - - var new_payload = - Instance._IJPreProcess(payload); - - payload.path = path_o; - - JSONManager.WriteToJsonFile(path, new_payload); - return true; - } - - public bool store_children(List<Directories> hierarchie, string name, bool use_install_folder = false, bool overwrite = true, bool deep_store = true) - => store_children(hierarchie, name, (T) this, use_install_folder, overwrite, deep_store); - - public static bool store_children(List<Directories> hierarchie, string name, T payload, bool use_install_folder = false, bool overwrite = true, bool deep_store = true) - { - var new_hierarchie = - Instance._IJGetHierarchie(hierarchie); - var new_name = - Instance._IJGetName(name); - - for ((int max_i, bool success) = (0, true); max_i < JsonSeperateFields.Count(); max_i++) - { - var field = JsonSeperateFields[max_i]; - dynamic save_me = field.GetValue(payload); // is S:IJSONsavable<S> - - Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType); - Type[] store_args_type = new Type[] { typeof(List<Directories>), typeof(string), field.FieldType, typeof(bool), typeof(bool), typeof(bool) }; - object[] store_args = new object[] { new_hierarchie, new_name, save_me, use_install_folder, overwrite, deep_store }; - - var method = interface_type.GetMethod("store", store_args_type); - success &= (bool)method.Invoke(null, store_args); - - // in case of no success: delete it again - if (!success) - { - delete_children(hierarchie, name, use_install_folder, JsonSeperateFields.Count() - max_i); - return false; - } - } - - return true; - } - - public static bool load_children(List<Directories> hierarchie, string name, ref T raw_payload, bool use_install_folder = false, bool deep_load = true, bool post_process = true) - { - var new_hierarchie = - Instance._IJGetHierarchie(hierarchie); - var new_name = - Instance._IJGetName(name); - - bool success = true; - for (int max_i = 0; max_i < JsonSeperateFields.Count(); max_i++) - { - var field = JsonSeperateFields[max_i]; - - Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType); - Type[] load_args_type = new Type[] { typeof(List<Directories>), typeof(string), field.FieldType.MakeByRefType(), typeof(bool), typeof(bool), typeof(bool) }; - object[] load_args = new object[] { new_hierarchie, new_name, null, use_install_folder, deep_load, post_process }; - - var method = interface_type.GetMethod("load", BindingFlags.Public | BindingFlags.Static, null, load_args_type, null); - bool success_i = (bool)method.Invoke(null, load_args); - - field.SetValue(raw_payload, success_i ? load_args[2] : Activator.CreateInstance(field.FieldType)); - success &= success_i; - } - - return success; - } - - public static bool load(List<Directories> hierarchie, string name, out T payload, bool use_install_folder = false, bool deep_load = true, bool post_process = true) - { - payload = default(T); - bool success = true; - - var new_hierarchie = - Instance._IJGetHierarchie(hierarchie); - var new_name = - Instance._IJGetName(name); - - string path = CreatePathToFile(out bool loadable, new_name, "JSON", new_hierarchie, use_install_folder); - if (!loadable) - return false; - - if (!Instance._IJGetRawObject(out T raw_payload, path)) - return false; - raw_payload.name = new_name; - - // load fields decorated with JsonSeparateAttribute and implementing IJSONsavable<> separately - if (deep_load - && !load_children(hierarchie, name, ref raw_payload, false /*use_install_folder*/)) - success = false; - - payload = post_process - ? Instance._IJPostProcess(raw_payload) - : raw_payload; - - return success; - } - - public static void delete_children(List<Directories> hierarchie, string name, bool use_install_folder = false, int skip_last_children = 0) - { - var new_hierarchie = - Instance._IJGetHierarchie(hierarchie); - var new_name = - Instance._IJGetName(name); - - for(int i = 0; i < JsonSeperateFields.Count() - skip_last_children; i++) - { - var field = JsonSeperateFields[i]; - - Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType); - Type[] delete_args_type = new Type[] { typeof(List<Directories>), typeof(string), typeof(bool) }; - object[] delete_args = new object[] { new_hierarchie, new_name, use_install_folder }; - - var method = interface_type.GetMethod("delete", delete_args_type); - method.Invoke(null, delete_args); - } - } - - public static bool delete(List<Directories> hierarchie, string name, bool use_install_folder = false) - { - var new_hierarchie = - Instance._IJGetHierarchie(hierarchie); - var new_name = - Instance._IJGetName(name); - - string path = CreatePathToFile(out bool _, new_name, "JSON", new_hierarchie, use_install_folder); - if (!delete(path)) - return false; - - delete_children(hierarchie, name, use_install_folder); - return true; - } - - // does not delete children! - private static bool delete(string path) - { - if (!File.Exists(path)) - return false; - - File.Delete(path); - return true; - } - - // public bool delete() => delete(hierarchie, name); - - #endregion MethodTemplates - -} public class MMTURICollection { - public string Point = "http://mathhub.info/MitM/core/geometry?3DGeometry?point"; - public string Tuple = "http://gl.mathhub.info/MMT/LFX/Sigma?Symbols?Tuple"; - public string LineType = "http://mathhub.info/MitM/core/geometry?Geometry/Common?line_type"; - public string LineOf = "http://mathhub.info/MitM/core/geometry?Geometry/Common?lineOf"; - public string OnLine = "http://mathhub.info/MitM/core/geometry?Geometry/Common?onLine"; - public string Ded = "http://mathhub.info/MitM/Foundation?Logic?ded"; - public string Eq = "http://mathhub.info/MitM/Foundation?Logic?eq"; - public string Metric = "http://mathhub.info/MitM/core/geometry?Geometry/Common?metric"; - public string Angle = "http://mathhub.info/MitM/core/geometry?Geometry/Common?angle_between"; - public string Sketch = "http://mathhub.info/MitM/Foundation?InformalProofs?proofsketch"; - public string RealLit = "http://mathhub.info/MitM/Foundation?RealLiterals?real_lit"; + public string Tuple = "http://gl.mathhub.info/MMT/LFX/Sigma?Symbols?Tuple"; + public string Point = "http://mathhub.info/MitM/core/geometry?3DGeometry?point"; + public string LineType = "http://mathhub.info/MitM/core/geometry?Geometry/Common?line_type"; + public string LineOf = "http://mathhub.info/MitM/core/geometry?Geometry/Common?lineOf"; + public string OnLine = "http://mathhub.info/MitM/core/geometry?Geometry/Common?onLine"; + public string Metric = "http://mathhub.info/MitM/core/geometry?Geometry/Common?metric"; + public string Angle = "http://mathhub.info/MitM/core/geometry?Geometry/Common?angle_between"; + public string Ded = "http://mathhub.info/MitM/Foundation?Logic?ded"; + public string Eq = "http://mathhub.info/MitM/Foundation?Logic?eq"; + public string Sketch = "http://mathhub.info/MitM/Foundation?InformalProofs?proofsketch"; + public string RealLit = "http://mathhub.info/MitM/Foundation?RealLiterals?real_lit"; } public static class JSONManager @@ -371,79 +148,4 @@ public MMTValueDeclaration(string label, MMTTerm lhs, MMTTerm valueTp, MMTTerm v this.value = value; } } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] - public sealed class JsonSeparateAttribute : Attribute - { - public JsonSeparateAttribute() { } - } - - // TODO? /// <para>If there are public properties/variables that you do not want written to the file, decorate them with the [JsonIgnore] attribute.</para> - - /// <summary> - /// Writes the given object instance to a Json file, recursively to set depth, including all members. - /// <para>Object type must have a parameterless constructor.</para> - /// <para>Only All properties and variables will be written to the file. These can be any type though, even other non-abstract classes.</para> - /// </summary> - /// <param name="filePath">The file path to write the object instance to.</param> - /// <param name="objectToWrite">The object instance to write to the file.</param> - /// <param name="max_depth">The depth recursion will occur. Default = 0.</param> - public static bool WriteToJsonFile(string filePath, object objectToWrite, int max_depth = 0) - { - // This tells your serializer that multiple references are okay. - var settings = new JsonSerializerSettings - { - ReferenceLoopHandling = ReferenceLoopHandling.Ignore - }; - - TextWriter writer = null; - try - { - string payload = JsonConvert.SerializeObject(objectToWrite, settings); - writer = new StreamWriter(filePath); - writer.Write(payload); - return true; - } - catch (Exception e) - { - Debug.LogError(e); - return false; - } - finally - { - if (writer != null) - writer.Close(); - } - } - - /// <summary> - /// Reads an object instance from an Json file. - /// <para>Object type must have a parameterless constructor.</para> - /// </summary> - /// <typeparam name="T">The type of object to read from the file.</typeparam> - /// <param name="filePath">The file path to read the object instance from.</param> - /// <returns>Returns a new instance of the object read from the Json file.</returns> - public static bool ReadFromJsonFile<T>(out T payload, string filePath) where T : new() - { - payload = default(T); - TextReader reader = null; - - try - { - reader = new StreamReader(filePath); - var fileContents = reader.ReadToEnd(); - payload = JsonConvert.DeserializeObject<T>(fileContents); - return true; - } - catch (Exception e) - { - Debug.LogError(e); - return false; - } - finally - { - if (reader != null) - reader.Close(); - } - } } diff --git a/Assets/Scripts/Loading/Stage.cs b/Assets/Scripts/Loading/Stage.cs index 4da3ed348565e1148a3080cfd34c0d420a35d483..04f8469c4b6ad3b0702b5e2546c742f1d923fe55 100644 --- a/Assets/Scripts/Loading/Stage.cs +++ b/Assets/Scripts/Loading/Stage.cs @@ -5,17 +5,18 @@ using UnityEngine; using Newtonsoft.Json; using static CommunicationEvents; -using System.Reflection; -//using static IJSONsavable<Stage>; public class Stage: IJSONsavable<Stage> { + #region metadata /// <summary> Which category this <see cref="Stage"/> should be displayed in. </summary> public string category = null; + /// <summary> Where to display this <see cref="Stage"/> within a <see cref="category"/> relative to others. </summary> public int number = -1; + /// <summary> /// The name this <see cref="Stage"/> will be displayed with. /// Also defines names of save files of <see cref="player_record_list">stage progress</see>, <see cref="solution"/> @@ -32,15 +33,37 @@ public class Stage: IJSONsavable<Stage> /// <summary> Wether this <see cref="Stage"/> is located in installation folder or user data (a.k.a. !local).</summary> public bool use_install_folder = false; - [JsonIgnore, JSONManager.JsonSeparate] + #endregion metadata + + /// <summary> + /// Defining when this <see cref="Stage.player_record"/> is considered as solved. + /// <seealso cref="FactOrganizer.DynamiclySolved(SolutionOrganizer, out List{List{string}}, out List{List{string}})"/> + /// </summary> + [JSONsavable.JsonAutoPreProcess, JSONsavable.JsonAutoPostProcess] + public SolutionOrganizer solution = null; + + [JsonIgnore, JSONsavable.JsonSeparate] public SaveGame savegame = null; + //public List<PlayerRecord> solution_approches = new(); + + #region makros/shortcuts + /// <summary> /// <see langword="true"/> iff there is at least one element in <see cref="player_record_list"/> where <see cref="PlayerRecord.solved"/> == <see langword="true"/>. /// </summary> [JsonIgnore] //TODO? update color if changed - public bool completed_once { get { return player_record_list != null && player_record_list.Values.Where(s => s.solved == true).Any(); } } + public bool completed_once { get { return player_record_list != null && player_record_list.Values.Any(s => s.solved == true); } } + + /// <summary> Current Stage progress.</summary> + [JsonIgnore] + public PlayerRecord player_record { + get => savegame?.player_record; + set => (savegame ??= new()) + .player_record = value; + } + /// <summary> /// A list containing all saved player progress. <br/> /// - Key: name of file @@ -53,12 +76,6 @@ public Dictionary<string, PlayerRecord> player_record_list { .player_record_list = value; } - /// <summary> - /// Defining when this <see cref="Stage.player_record"/> is considered as solved. - /// <seealso cref="FactOrganizer.DynamiclySolved(SolutionOrganizer, out List{List{string}}, out List{List{string}})"/> - /// </summary> - public SolutionOrganizer solution = null; - /// <summary> /// A wrapper returning (or setting) <see cref="player_record.factState"/>. <br/> /// When <see cref="player_record"/> == <see langword="null"/>: @@ -71,13 +88,9 @@ public FactOrganizer factState { set => (player_record ??= new(record_name)) .factState = value; } - /// <summary> Current Stage progress.</summary> - [JsonIgnore] - public PlayerRecord player_record { - get => savegame?.player_record; - set => (savegame ??= new()) - .player_record = value; - } + + #endregion makros/shortcuts + /// <summary> Returns a name for <see cref="player_record.name"/> which needs to be uniquified once put into <see cref="player_record_list"/> (e.g. by <see cref="push_record(double, bool) adding '_i'"/>).</summary> private string record_name { get { return name + "_save"; } } @@ -122,15 +135,6 @@ public Stage(string category, int number, string name, string description, strin } - Stage IJSONsavable<Stage>._IJPostProcess(Stage payload) - { - payload.solution = IJSONsavable<SolutionOrganizer>.Instance._IJPostProcess(payload.solution ?? new()); - // // savegame is saved separately - // payload.savegame = IJSONsavable<SaveGame>.Instance._IJPostProcess(payload.savegame ?? new()); - - return payload; - } - /// <summary> /// Sets members which are primitives. /// </summary> @@ -344,7 +348,7 @@ public static bool ShallowLoad(out Stage set, string path) if (!IJSONsavable<Stage>.Instance._IJGetRawObject(out set, path)) return false; - set = IJSONsavable<Stage>.Instance._IJPostProcess(set); + set = IJSONsavable<Stage>.postprocess(set); set.path = path; IJSONsavable<Stage>.load_children(null /*hierarchie*/, set.name, ref set, post_process: false); @@ -446,8 +450,10 @@ public class SaveGame : IJSONsavable<SaveGame> public string name { get; set; } public string path { get; set; } + [JSONsavable.JsonAutoPreProcess, JSONsavable.JsonAutoPostProcess] public PlayerRecord player_record = new(); - public Dictionary<string, PlayerRecord> player_record_list = new(); + + public Dictionary<string, PlayerRecord> player_record_list = new(); //entries are "PostProcess"ed when accessed/Cloned static SaveGame() { @@ -456,16 +462,6 @@ static SaveGame() public SaveGame() { } string IJSONsavable<SaveGame>._IJGetName(string name) => name + "_save"; - SaveGame IJSONsavable<SaveGame>._IJPostProcess(SaveGame payload) - { - payload.player_record = IJSONsavable<PlayerRecord>.Instance._IJPostProcess(payload.player_record ?? new()); - - // // player_record_list entries are "PostProcess"ed when accessed/Cloned - //payload.player_record_list = new (payload.player_record_list.Select((v) => new KeyValuePair<string, PlayerRecord> - // (v.Key, IJSONsavable<PlayerRecord>.Instance._IJPostProcess(v.Value)))); - - return payload; - } } @@ -482,11 +478,11 @@ public class PlayerRecord: IJSONsavable<PlayerRecord> public double seconds = 0; /// <summary> Stage progress. </summary> + [JSONsavable.JsonAutoPreProcess, JSONsavable.JsonAutoPostProcess] public FactOrganizer factState = new(); /// <summary> save game file name </summary> public string name { get; set; } = null; - - public string path { get; set; } + public string path { get; set; } = null; static PlayerRecord(/*static!*/) { @@ -507,12 +503,6 @@ public PlayerRecord(string name) { factState = new FactOrganizer(); } - PlayerRecord IJSONsavable<PlayerRecord>._IJPostProcess(PlayerRecord payload) - { - payload.factState = IJSONsavable<FactOrganizer>.Instance._IJPostProcess(payload.factState); - return payload; - } - /// <summary> /// Copies a specified <see cref="PlayerRecord"/> /// </summary> @@ -529,4 +519,13 @@ public PlayerRecord Clone() return ret; } +} + + +public class SolutionApproach: IJSONsavable<SolutionApproach> +{ + public string name { get; set; } = null; + public string path { get; set; } = null; + + //public PlayerRecord } \ No newline at end of file diff --git a/Assets/Scripts/StageStatic.cs b/Assets/Scripts/StageStatic.cs index 1af549119cdc99b3c5dfe155c9aeddcbbc3b6937..17087d00893376758995d1a93c5a580ff0df35f9 100644 --- a/Assets/Scripts/StageStatic.cs +++ b/Assets/Scripts/StageStatic.cs @@ -218,7 +218,7 @@ public static StageErrorStruct Validate(string category, int id, string name, st return new StageErrorStruct( category.Length == 0, ContainsNumber(category, id, true), - name.Length == 0 || ContainsKey(name, true) || ContainsKey(name, false), + name.Length == 0 || ContainsKey(name, local), false, !Worlds.Contains(scene), local == false, diff --git a/Assets/Scripts/UI/MainMenue/PageLoader/CreateLoader.cs b/Assets/Scripts/UI/MainMenue/PageLoader/CreateLoader.cs index 3341ff9f1174316b4da28e9dd09f34404910a7ce..2eb0a497aa7bdfc6d8418ffa5b77836b4805f92b 100644 --- a/Assets/Scripts/UI/MainMenue/PageLoader/CreateLoader.cs +++ b/Assets/Scripts/UI/MainMenue/PageLoader/CreateLoader.cs @@ -84,7 +84,7 @@ protected void Error(StageStatic.StageErrorStruct error) { throw new System.NotImplementedException("Id must be unique within a category"); // technichal a lie } - if (error.name) // for savegame identification (could be chained with local/category/id) + if (error.name) // for savegame identification (could be chained with local/category/id => beware StageStatic.GetStage(name, local)) { throw new System.NotImplementedException("Name must be unique"); } diff --git a/GlobalSuppressions2.cs b/GlobalSuppressions2.cs new file mode 100644 index 0000000000000000000000000000000000000000..496b77e3fcf2119ba29c360ed0c3bf213256f33d --- /dev/null +++ b/GlobalSuppressions2.cs @@ -0,0 +1,9 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0034:Simplify 'default' expression", Justification = "Readabilty", Scope = "member", Target = "~M:IJSONsavable`1.load(System.Collections.Generic.List{CommunicationEvents.Directories},System.String,`0@,System.Boolean,System.Boolean,System.Boolean)~System.Boolean")] +[assembly: SuppressMessage("Style", "IDE0034:Simplify 'default' expression", Justification = "<Pending>", Scope = "member", Target = "~M:JSONsavable.ReadFromJsonFile``1(``0@,System.String)~System.Boolean")]