diff --git a/CMJS.cs b/CMJS.cs index d0bd93e..47f8915 100644 --- a/CMJS.cs +++ b/CMJS.cs @@ -1,8 +1,16 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using Beatmap.Base; +using Beatmap.Base.Customs; +using Beatmap.Containers; +using Beatmap.Enums; +using Beatmap.V2; +using Beatmap.V2.Customs; +using Beatmap.V3; +using Beatmap.V3.Customs; using HarmonyLib; using UnityEngine; using UnityEngine.SceneManagement; @@ -11,11 +19,13 @@ [Plugin("CM JS")] public class CMJS { - private NotesContainer notesContainer; - private ObstaclesContainer wallsContainer; - private EventsContainer eventsContainer; - private CustomEventsContainer customEventsContainer; - private BPMChangesContainer bpmChangesContainer; + private NoteGridContainer notesContainer; + private ChainGridContainer chainsContainer; + private ArcGridContainer arcsContainer; + private ObstacleGridContainer wallsContainer; + private EventGridContainer eventsContainer; + private CustomEventGridContainer customEventsContainer; + private BPMChangeGridContainer BpmEventsContainer; private List checks = new List() { new VisionBlocks(), @@ -56,14 +66,16 @@ private void SceneLoaded(Scene arg0, LoadSceneMode arg1) { if (arg0.buildIndex == 3) // Mapper scene { - notesContainer = UnityEngine.Object.FindObjectOfType(); - wallsContainer = UnityEngine.Object.FindObjectOfType(); - eventsContainer = UnityEngine.Object.FindObjectOfType(); - customEventsContainer = UnityEngine.Object.FindObjectOfType(); - bpmChangesContainer = UnityEngine.Object.FindObjectOfType(); + notesContainer = UnityEngine.Object.FindObjectOfType(); + arcsContainer = UnityEngine.Object.FindObjectOfType(); + chainsContainer = UnityEngine.Object.FindObjectOfType(); + wallsContainer = UnityEngine.Object.FindObjectOfType(); + eventsContainer = UnityEngine.Object.FindObjectOfType(); + customEventsContainer = UnityEngine.Object.FindObjectOfType(); + BpmEventsContainer = UnityEngine.Object.FindObjectOfType(); var mapEditorUI = UnityEngine.Object.FindObjectOfType(); - atsc = BeatmapObjectContainerCollection.GetCollectionForType(BeatmapObject.ObjectType.Note).AudioTimeSyncController; + atsc = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.Note).AudioTimeSyncController; // Add button to UI ui.AddButton(mapEditorUI); @@ -72,18 +84,14 @@ private void SceneLoaded(Scene arg0, LoadSceneMode arg1) public void CheckErrors(Check check) { - var allNotes = notesContainer.LoadedObjects.Cast().OrderBy(it => it.Time).ToList(); - var allWalls = wallsContainer.LoadedObjects.Cast().OrderBy(it => it.Time).ToList(); - var allEvents = eventsContainer.LoadedObjects.Cast().OrderBy(it => it.Time).ToList(); - var allCustomEvents = customEventsContainer.LoadedObjects.Cast().OrderBy(it => it.Time).ToList(); - var allBpmChanges = bpmChangesContainer.LoadedObjects.Cast().OrderBy(it => it.Time).ToList(); + bool isV3 = Settings.Instance.Load_MapV3; if (errors != null) { // Remove error outline from old errors foreach (var block in errors.all) { - if (BeatmapObjectContainerCollection.GetCollectionForType(BeatmapObject.ObjectType.Note).LoadedContainers.TryGetValue(block.note, out BeatmapObjectContainer container)) + if (BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.Note).LoadedContainers.TryGetValue(block.note, out ObjectContainer container)) { container.OutlineVisible = SelectionController.IsObjectSelected(container.ObjectData); container.SetOutlineColor(SelectionController.SelectedColor, false); @@ -98,21 +106,42 @@ public void CheckErrors(Check check) switch (it) { case UITextInput textInput: - return check.Params[idx].Parse(textInput.InputField.text); + return new KeyValuePair(check.Params[idx].name, check.Params[idx].Parse(textInput.InputField.text)); case UIDropdown dropdown: - return check.Params[idx].Parse(dropdown.Dropdown.value.ToString()); + return new KeyValuePair(check.Params[idx].name, check.Params[idx].Parse(dropdown.Dropdown.value.ToString())); case Toggle toggle: - return check.Params[idx].Parse(toggle.isOn.ToString()); + return new KeyValuePair(check.Params[idx].name, check.Params[idx].Parse(toggle.isOn.ToString())); default: - return new ParamValue(null); // IDK + return new KeyValuePair(check.Params[idx].name, new ParamValue(null)); // IDK } }).ToArray(); - errors = check.PerformCheck(allNotes, allEvents, allWalls, allCustomEvents, allBpmChanges, vals).Commit(); + + if (isV3) + { + // TODO: since containers has multiple different object, check events and notes + var allNotes = notesContainer.LoadedObjects.Where(it => it is V3ColorNote).Cast().OrderBy(it => it.JsonTime).ToList(); + var allBombs = notesContainer.LoadedObjects.Where(it => it is V3BombNote).Cast().OrderBy(it => it.JsonTime).ToList(); + var allArcs = arcsContainer.LoadedObjects.Cast().OrderBy(it => it.JsonTime).ToList(); + var allChains = chainsContainer.LoadedObjects.Cast().OrderBy(it => it.JsonTime).ToList(); + var allWalls = wallsContainer.LoadedObjects.Cast().OrderBy(it => it.JsonTime).ToList(); + var allEvents = eventsContainer.LoadedObjects.Cast().OrderBy(it => it.JsonTime).ToList(); + var allCustomEvents = customEventsContainer.LoadedObjects.Cast().OrderBy(it => it.JsonTime).ToList(); + var allBpmEvents = BpmEventsContainer.LoadedObjects.Cast().OrderBy(it => it.JsonTime).ToList(); + errors = check.PerformCheck(allNotes, allBombs, allArcs, allChains, allEvents, allWalls, allCustomEvents, allBpmEvents, vals).Commit(); + } else + { + var allNotes = notesContainer.LoadedObjects.Cast().OrderBy(it => it.JsonTime).ToList(); + var allWalls = wallsContainer.LoadedObjects.Cast().OrderBy(it => it.JsonTime).ToList(); + var allEvents = eventsContainer.LoadedObjects.Cast().OrderBy(it => it.JsonTime).ToList(); + var allCustomEvents = customEventsContainer.LoadedObjects.Cast().OrderBy(it => it.JsonTime).ToList(); + var allBpmEvents = BpmEventsContainer.LoadedObjects.Cast().OrderBy(it => it.JsonTime).ToList(); + errors = check.PerformCheck(allNotes, new List(), new List(), new List(), allEvents, allWalls, allCustomEvents, allBpmEvents, vals).Commit(); + } // Highlight blocks in loaded containers in case we don't scrub far enough with MoveToTimeInBeats to load them foreach (var block in errors.errors) { - if (BeatmapObjectContainerCollection.GetCollectionForType(BeatmapObject.ObjectType.Note).LoadedContainers.TryGetValue(block.note, out BeatmapObjectContainer container)) + if (BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.Note).LoadedContainers.TryGetValue(block.note, out ObjectContainer container)) { container.SetOutlineColor(Color.red); } @@ -120,7 +149,7 @@ public void CheckErrors(Check check) foreach (var block in errors.warnings) { - if (BeatmapObjectContainerCollection.GetCollectionForType(BeatmapObject.ObjectType.Note).LoadedContainers.TryGetValue(block.note, out BeatmapObjectContainer container)) + if (BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.Note).LoadedContainers.TryGetValue(block.note, out ObjectContainer container)) { container.SetOutlineColor(Color.yellow); } @@ -164,7 +193,7 @@ public void NextBlock(int offset = 1) index += errors.all.Count; } - float? time = errors.all[index]?.note.Time; + float? time = errors.all[index]?.note.SongBpmTime; if (time != null) { atsc.MoveToTimeInBeats(time ?? 0); @@ -179,7 +208,7 @@ public void NextBlock(int offset = 1) } [ObjectLoaded] - private void ObjectLoaded(BeatmapObjectContainer container) + private void ObjectLoaded(ObjectContainer container) { if (container.ObjectData == null || errors == null) return; diff --git a/Checks/Check.cs b/Checks/Check.cs index afb423c..c7c2efc 100644 --- a/Checks/Check.cs +++ b/Checks/Check.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using Beatmap.Base; +using Beatmap.Base.Customs; public abstract class Check { @@ -18,16 +20,16 @@ protected Check() : this("") } - protected virtual CheckResult PerformCheck(List notes, List events, List walls, List customEvents, List bpmChanges) + protected virtual CheckResult PerformCheck(List notes, List bombs, List arcs, List chains, List events, List walls, List customEvents, List BpmEvents) { throw new ArgumentException("Wrong number of parameters"); } - public virtual CheckResult PerformCheck(List notes, List events, List walls, List customEvents, List bpmChanges, params IParamValue[] vals) + public virtual CheckResult PerformCheck(List notes, List bombs, List arcs, List chains, List events, List walls, List customEvents, List BpmEvents, params KeyValuePair[] vals) { if (vals.Length == 0 && Params.Count == 0) { - return PerformCheck(notes, events, walls, customEvents, bpmChanges); + return PerformCheck(notes, bombs, arcs, chains, events, walls, customEvents, BpmEvents); } throw new ArgumentException("Wrong number of parameters"); } diff --git a/Checks/CheckResult.cs b/Checks/CheckResult.cs index a32f559..bfd20cc 100644 --- a/Checks/CheckResult.cs +++ b/Checks/CheckResult.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; using System.Linq; +using Beatmap.Base; public class CheckResult { public class Problem { - public BeatmapNote note; + public BaseNote note; public string reason; - public Problem(BeatmapNote note, string reason) + public Problem(BaseNote note, string reason) { this.note = note; this.reason = reason; @@ -30,24 +31,24 @@ public void Clear() all = null; } - public void Add(BeatmapNote note, string reason = "") + public void Add(BaseNote note, string reason = "") { AddError(note, reason); } - public void AddError(BeatmapNote note, string reason = "") + public void AddError(BaseNote note, string reason = "") { errors.Add(new Problem(note, reason)); } - public void AddWarning(BeatmapNote note, string reason = "") + public void AddWarning(BaseNote note, string reason = "") { warnings.Add(new Problem(note, reason)); } public CheckResult Commit() { - all = errors.Union(warnings).OrderBy(it => it.note.Time).ToList(); + all = errors.Union(warnings).OrderBy(it => it.note.JsonTime).ToList(); return this; } } diff --git a/Checks/ExternalJS.cs b/Checks/ExternalJS.cs index dd01aae..9dcad4e 100644 --- a/Checks/ExternalJS.cs +++ b/Checks/ExternalJS.cs @@ -1,71 +1,57 @@ -using Jint; -using Jint.Native; -using Jint.Native.Array; -using Jint.Native.Object; -using Jint.Runtime; -using Jint.Runtime.Interop; -using SimpleJSON; -using System; +using System; using System.Collections.Generic; using System.Dynamic; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Threading; +using Beatmap.Base; +using Beatmap.Base.Customs; +using Beatmap.Enums; using Esprima; +using Jint; +using Jint.Native; +using Jint.Native.Array; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Interop; +using SimpleJSON; using UnityEngine; -class ExternalJS : Check +internal class ExternalJS : Check { - private Engine engine; - private readonly IConstraint timeConstraint = new TimeConstraint2(TimeSpan.FromSeconds(30L)); + private const bool DebugMode = false; private readonly string fileName; + private readonly Constraint timeConstraint = new TimeConstraint2(TimeSpan.FromSeconds(30L)); + private Engine engine; private bool valid; - private const bool DebugMode = false; - private static void TimeLog(string msg) { - if (!DebugMode) return; - - var time = DateTime.Now; - Debug.Log($"[{time}.{time.Millisecond}] [CM-JS] {msg}"); + public ExternalJS(string fileName) + { + this.fileName = fileName; + LoadJS(); } - private class TimeConstraint2 : IConstraint + private static void TimeLog(string msg) { - private readonly TimeSpan _timeout; - private CancellationTokenSource cts; - - public TimeConstraint2(TimeSpan timeout) => _timeout = timeout; - - public void Check() - { - if (!cts.IsCancellationRequested) - return; - throw new TimeoutException(); - } + if (!DebugMode) return; - public void Reset() - { - cts?.Dispose(); - cts = new CancellationTokenSource(this._timeout); - } + var time = DateTime.Now; + Debug.Log($"[{time}.{time.Millisecond}] [CM-JS] {msg}"); } public Func Bind(Func func, T arg) { - return (file) => func(arg, file); + return file => func(arg, file); } private static void LogIt(object o) { if (o is ExpandoObject ex) - { - Debug.Log(JSONWraper.dictToJSON(ex)); - } + Debug.Log(JSONWrapper.dictToJSON(ex)); else - { Debug.Log(o); - } } private static void Alert(string o) @@ -76,20 +62,15 @@ private static void Alert(string o) }, PersistentUI.DialogBoxPresetType.Ok); } - private JsValue require(string folder, string file) { - if (!file.EndsWith(".js")) - { - file += ".js"; - } - string fullPath = Path.Combine(folder, file); - string jsSource = File.ReadAllText(fullPath); - string newFolder = new FileInfo(fullPath).DirectoryName; + private JsValue require(string folder, string file) + { + if (!file.EndsWith(".js")) file += ".js"; + var fullPath = Path.Combine(folder, file); + var jsSource = File.ReadAllText(fullPath); + var newFolder = new FileInfo(fullPath).DirectoryName; try { - var e = new Engine(options => - { - options.Constraint(timeConstraint).LimitRecursion(200); - }) + var e = new Engine(options => { options.Constraint(timeConstraint).LimitRecursion(200); }) .SetValue("log", new Action(LogIt)) .SetValue("alert", new Action(Alert)) .SetValue("require", new Func(Bind(require, newFolder))) @@ -97,26 +78,16 @@ private JsValue require(string folder, string file) { var res = e.Evaluate(jsSource); - if (res.IsUndefined()) - { - res = e.GetValue("exports"); - } + if (res.IsUndefined()) res = e.GetValue("exports"); return res; } catch (JavaScriptException jse) { Debug.Log(jse); - Debug.Log("LINE: " + jse.LineNumber); - Debug.Log("COLUMN: " + jse.Column); } - return null; - } - public ExternalJS(string fileName) - { - this.fileName = fileName; - LoadJS(); + return null; } public override void Reload() @@ -126,10 +97,7 @@ public override void Reload() private void LoadJS() { - engine = new Engine(options => - { - options.Constraint(timeConstraint).LimitRecursion(200); - }); + engine = new Engine(options => { options.Constraint(timeConstraint).LimitRecursion(200); }); var assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var streamReader = new StreamReader(Path.Combine(assemblyFolder, fileName)); @@ -157,47 +125,37 @@ private void LoadJS() { var ps = JSON.Parse(@params.AsString()).AsObject; foreach (var p in ps) - { if (p.Value.IsBoolean) - { Params.Add(new BoolParam(p.Key, p.Value.AsBool)); - } else if (p.Value.IsString) - { Params.Add(new StringParam(p.Key, p.Value.Value)); - } else if (p.Value.IsNumber) - { Params.Add(new FloatParam(p.Key, p.Value.AsFloat)); - } else if (p.Value.IsArray) - { Params.Add(new ListParam(p.Key, p.Value.AsArray.Children.Select(it => it.Value).ToList())); - } - } } var nameObj = engine.GetValue(exports, "name"); if (nameObj.IsString()) { var name = nameObj.AsString(); - Name = "ExternalJS: " + name; + Name = "extJS: " + name; } else { - Name = $"ExternalJS: {fileName}"; + Name = $"extJS: {fileName}"; } valid = true; } catch (JavaScriptException jse) { - Name = $"ExternalJS: [{fileName}]"; + Name = $"extJS: [{fileName}]"; Debug.LogWarning($"Error loading {fileName}\n{jse.Message}"); } catch (ParserException jse) { - Name = $"ExternalJS: [{fileName}]"; + Name = $"extJS: [{fileName}]"; Debug.LogWarning($"Error loading {fileName}\n{jse.Message}"); } } @@ -207,7 +165,7 @@ public override void OnSelected() if (!valid) LoadJS(); } - private BeatmapNote FromDynamic(dynamic note, List notes) + private BaseNote FromDynamic(dynamic note, List notes) { float _time = Convert.ChangeType(note._time, typeof(float)); int _lineIndex = Convert.ChangeType(note._lineIndex, typeof(int)); @@ -215,108 +173,113 @@ private BeatmapNote FromDynamic(dynamic note, List notes) int _type = Convert.ChangeType(note._type, typeof(int)); int _cutDirection = Convert.ChangeType(note._cutDirection, typeof(int)); - return notes.Find(it => - { - return Mathf.Approximately(_time, it.Time) && - _lineIndex == it.LineIndex && - _lineLayer == it.LineLayer && - _type == it.Type && - _cutDirection == it.CutDirection; - }); + return notes.Find(it => Mathf.Approximately(_time, it.JsonTime) && + _lineIndex == it.PosX && + _lineLayer == it.PosY && + _type == it.Type && + _cutDirection == it.CutDirection); } - class MapData { - public float currentBPM { get; private set; } - public float songBPM { get; private set; } - public float NJS { get; private set; } - public float offset { get; private set; } - - public MapData(float currentBPM, float songBPM, float NJS, float offset) - { - this.currentBPM = currentBPM; - this.songBPM = songBPM; - this.NJS = NJS; - this.offset = offset; - } - } - - public override CheckResult PerformCheck(List notes, List events, List walls, List customEvents, List bpmChanges, params IParamValue[] vals) + public override CheckResult PerformCheck(List notes, List bombs, List arcs, + List chains, List events, List walls, List customEvents, + List BpmEvents, params KeyValuePair[] vals) { result.Clear(); - var atsc = BeatmapObjectContainerCollection.GetCollectionForType(BeatmapObject.ObjectType.Note).AudioTimeSyncController; + var rArgs = new ReceivedArguments(vals); + var atsc = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.Note).AudioTimeSyncController; var currentBeat = atsc.CurrentBeat; - var collection = BeatmapObjectContainerCollection.GetCollectionForType(BeatmapObject.ObjectType.BpmChange); - var lastBPMChange = collection.FindLastBpm(atsc.CurrentBeat); - var currentBPM = lastBPMChange?.Bpm ?? atsc.Song.BeatsPerMinute; + var collection = + BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.BpmChange); + var lastBpmEvent = collection.FindLastBpm(atsc.CurrentBeat); + var currentBPM = lastBpmEvent?.Bpm ?? atsc.Song.BeatsPerMinute; var originalNotes = notes.Select(it => new Note(engine, it)).ToArray(); + var originalBombs = bombs.Select(it => new BombNote(engine, it)).ToArray(); + var originalArcs = arcs.Select(it => new Arc(engine, it)).ToArray(); + var originalChains = chains.Select(it => new Chain(engine, it)).ToArray(); var originalWalls = walls.Select(it => new Wall(engine, it)).ToArray(); var originalEvents = events.Select(it => new Event(engine, it)).ToArray(); var originalCustomEvents = customEvents.Select(it => new CustomEvent(engine, it)).ToArray(); - var originalBpmChanges = bpmChanges.Select(it => new BpmChange(engine, it)).ToArray(); + var originalBpmEvents = BpmEvents.Select(it => new BpmEvent(engine, it)).ToArray(); try { - var valsToString = vals.Select(paramValue => - { - switch (paramValue) - { - case ParamValue pvf: - return pvf.value.ToString(); - case ParamValue pvs: - return $"\"{pvs.value}\""; - case ParamValue pvb: - return pvb.value ? "true" : "false"; - default: - return "null"; - } - }); - - var valsCombined = string.Join(",", valsToString); - TimeLog("Init"); var tmp = engine - .SetValue("notes", originalNotes) - .SetValue("walls", originalWalls) - .SetValue("events", originalEvents) - .SetValue("customEvents", originalCustomEvents) - .SetValue("bpmChanges", originalBpmChanges) - .SetValue("data", new MapData( - currentBPM, - atsc.Song.BeatsPerMinute, - BeatSaberSongContainer.Instance.DifficultyData.NoteJumpMovementSpeed, - BeatSaberSongContainer.Instance.DifficultyData.NoteJumpStartBeatOffset - )) - .SetValue("cursor", currentBeat) - .SetValue("minTime", 0.24f) - .SetValue("maxTime", 0.75f) - .SetValue("addError", new Action((dynamic note, string str) => - { - var obj = FromDynamic(note, notes); + .SetValue("notes", originalNotes) + .SetValue("bombs", originalBombs) + .SetValue("arcs", originalArcs) + .SetValue("chains", originalChains) + .SetValue("walls", originalWalls) + .SetValue("events", originalEvents) + .SetValue("customEvents", originalCustomEvents) + .SetValue("BpmEvents", originalBpmEvents) + .SetValue("data", new MapData( + currentBPM, + atsc.Song.BeatsPerMinute, + BeatSaberSongContainer.Instance.DifficultyData.NoteJumpMovementSpeed, + BeatSaberSongContainer.Instance.DifficultyData.NoteJumpStartBeatOffset, + BeatSaberSongContainer.Instance.DifficultyData.ParentBeatmapSet.BeatmapCharacteristicName, + BeatSaberSongContainer.Instance.DifficultyData.Difficulty, + BeatSaberSongContainer.Instance.DifficultyData.ParentBeatmapSet.BeatmapCharacteristicName == + "360Degree" || + BeatSaberSongContainer.Instance.DifficultyData.ParentBeatmapSet.BeatmapCharacteristicName == + "90Degree" + ? BeatSaberSongContainer.Instance.Song.AllDirectionsEnvironmentName + : BeatSaberSongContainer.Instance.Song.EnvironmentName, + BeatSaberSongContainer.Instance.Map.Version + )) + .SetValue("args", rArgs) + .SetValue("cursor", currentBeat) + .SetValue("minTime", 0.24f) + .SetValue("maxTime", 0.75f) + .SetValue("addError", new Action((dynamic note, string str) => + { + var obj = FromDynamic(note, notes); - if (obj != null) - result.Add(obj, str ?? ""); - })) - .SetValue("addWarning", new Action((dynamic note, string str) => - { - var obj = FromDynamic(note, notes); + if (obj != null) + result.Add(obj, str ?? ""); + })) + .SetValue("addWarning", new Action((dynamic note, string str) => + { + var obj = FromDynamic(note, notes); - if (obj != null) - result.AddWarning(obj, str ?? ""); - })); + if (obj != null) + result.AddWarning(obj, str ?? ""); + })); TimeLog("Run"); - tmp.Execute("global.params = [" + valsCombined + "];" + - "var output = module.exports.run ? module.exports.run(cursor, notes, events, walls, {}, global, data, customEvents, bpmChanges) : module.exports.performCheck({notes: notes}" + (vals.Length > 0 ? ", " + valsCombined : "") + ");" + - "if (output && output.notes) { notes = output.notes; };" + - "if (output && output.events) { events = output.events; };" + - "if (output && output.customEvents) { customEvents = output.customEvents; };" + - "if (output && output.bpmChanges) { bpmChanges = output.bpmChanges; };" + - "if (output && output.walls) { walls = output.walls; };"); + tmp.Execute("global.params = args;" + + "var output = module.exports.run ? module.exports.run(cursor, notes, events, walls, {}, global, data, customEvents, BpmEvents, bombs, arcs, chains) : module.exports.performCheck({notes: notes}" + + (vals.Length > 0 + ? ", " + + string.Join(",", vals.Select(paramValue => + { + switch (paramValue.Value) + { + case ParamValue pvf: + return pvf.value.ToString(CultureInfo.InvariantCulture); + case ParamValue pvs: + return $"\"{pvs.value}\""; + case ParamValue pvb: + return pvb.value ? "true" : "false"; + default: + return "null"; + } + })) + : "") + ");" + + "if (output && output.notes) { notes = output.notes; };" + + "if (output && output.bombs) { bombs = output.bombs; };" + + "if (output && output.arcs) { arcs = output.arcs; };" + + "if (output && output.chains) { chains = output.chains; };" + + "if (output && output.events) { events = output.events; };" + + "if (output && output.customEvents) { customEvents = output.customEvents; };" + + "if (output && output.BpmEvents) { BpmEvents = output.BpmEvents; };" + + "if (output && output.walls) { walls = output.walls; };"); } catch (JavaScriptException jse) { @@ -327,11 +290,22 @@ public override CheckResult PerformCheck(List notes, List SelectionController.DeselectAll(); var actions = new List(); - actions.AddRange(Reconcile(originalNotes, engine.GetValue("notes").AsArray(), notes, i => new Note(engine, i), BeatmapObject.ObjectType.Note)); - actions.AddRange(Reconcile(originalWalls, engine.GetValue("walls").AsArray(), walls, i => new Wall(engine, i), BeatmapObject.ObjectType.Obstacle)); - actions.AddRange(Reconcile(originalEvents, engine.GetValue("events").AsArray(), events, i => new Event(engine, i), BeatmapObject.ObjectType.Event)); - actions.AddRange(Reconcile(originalCustomEvents, engine.GetValue("customEvents").AsArray(), customEvents, i => new CustomEvent(engine, i), BeatmapObject.ObjectType.CustomEvent)); - actions.AddRange(Reconcile(originalBpmChanges, engine.GetValue("bpmChanges").AsArray(), bpmChanges, i => new BpmChange(engine, i), BeatmapObject.ObjectType.BpmChange)); + actions.AddRange(Reconcile(originalNotes, engine.GetValue("notes").AsArray(), notes, i => new Note(engine, i), + ObjectType.Note)); + actions.AddRange(Reconcile(originalBombs, engine.GetValue("bombs").AsArray(), bombs, + i => new BombNote(engine, i), ObjectType.Note)); + actions.AddRange(Reconcile(originalArcs, engine.GetValue("arcs").AsArray(), arcs, i => new Arc(engine, i), + ObjectType.Arc)); + actions.AddRange(Reconcile(originalChains, engine.GetValue("chains").AsArray(), chains, + i => new Chain(engine, i), ObjectType.Chain)); + actions.AddRange(Reconcile(originalWalls, engine.GetValue("walls").AsArray(), walls, i => new Wall(engine, i), + ObjectType.Obstacle)); + actions.AddRange(Reconcile(originalEvents, engine.GetValue("events").AsArray(), events, + i => new Event(engine, i), ObjectType.Event)); + actions.AddRange(Reconcile(originalCustomEvents, engine.GetValue("customEvents").AsArray(), customEvents, + i => new CustomEvent(engine, i), ObjectType.CustomEvent)); + actions.AddRange(Reconcile(originalBpmEvents, engine.GetValue("BpmEvents").AsArray(), BpmEvents, + i => new BpmEvent(engine, i), ObjectType.BpmChange)); SelectionController.SelectionChangedEvent?.Invoke(); @@ -348,14 +322,14 @@ public override CheckResult PerformCheck(List notes, List return result; } - private List Reconcile(IEnumerable original, ArrayInstance noteArr, List notes, Func inst, BeatmapObject.ObjectType type) where U : Wrapper where T : BeatmapObject + private List Reconcile(IEnumerable original, ArrayInstance noteArr, List notes, + Func inst, ObjectType type) where U : Wrapper where T : BaseObject { TimeLog("Reconcile " + original.GetType()); var beatmapActions = new List(); var outputNotes = new List(); foreach (var test in noteArr) - { if (test is U a) { outputNotes.Add(a); @@ -378,7 +352,6 @@ private List Reconcile(IEnumerable original, ArrayInstan Debug.Log("Something else???"); Debug.Log(test.GetType()); } - } var lookup = original.ToDictionary(x => x.wrapped, x => x); @@ -415,24 +388,118 @@ private List Reconcile(IEnumerable original, ArrayInstan TimeLog("Update selection"); foreach (var note in outputNotes.Where(note => note.selected)) - { SelectionController.Select(note.wrapped, true, false, false); - } TimeLog("Create actions"); - foreach (var note in toAction) { + foreach (var note in toAction) if (note.original != null) + beatmapActions.Add(new BeatmapObjectModifiedAction(note.wrapped, note.wrapped, note.original, + "Script edited object")); + else + beatmapActions.Add(new BeatmapObjectPlacementAction(note.wrapped, Enumerable.Empty(), + "Script spawned object")); + + collection.RefreshPool(); + return beatmapActions; + } + + private class TimeConstraint2 : Constraint + { + private readonly TimeSpan _timeout; + private CancellationTokenSource cts; + + public TimeConstraint2(TimeSpan timeout) + { + _timeout = timeout; + } + + public override void Check() + { + if (!cts.IsCancellationRequested) + return; + throw new TimeoutException(); + } + + public override void Reset() + { + cts?.Dispose(); + cts = new CancellationTokenSource(_timeout); + } + } + + private class MapData + { + public MapData(float currentBPM, float songBPM, float NJS, float offset, string characteristic, + string difficulty, string environment, string version) + { + this.currentBPM = currentBPM; + this.songBPM = songBPM; + this.NJS = NJS; + this.offset = offset; + this.characteristic = characteristic; + this.difficulty = difficulty; + this.environment = environment; + this.version = version; + } + + public float currentBPM { get; } + public float songBPM { get; } + public float NJS { get; } + public float offset { get; } + public string characteristic { get; } + public string difficulty { get; } + public string environment { get; } + public string version { get; } + } + + private class ReceivedArguments + { + // looks dangerous, probably better with JsValue + private readonly List> keyMap; + + public ReceivedArguments(IEnumerable> p) + { + keyMap = p.Select(kvp => { - beatmapActions.Add(new BeatmapObjectModifiedAction(note.wrapped, note.wrapped, note.original, "Script edited object")); + switch (kvp.Value) + { + case ParamValue pvf: + return new KeyValuePair(kvp.Key, pvf.value); + case ParamValue pvs: + return new KeyValuePair(kvp.Key, pvs.value); + case ParamValue pvb: + return new KeyValuePair(kvp.Key, pvb.value); + default: + return new KeyValuePair(kvp.Key, null); + } + }).ToList(); + } + + private void SetValue(KeyValuePair kvp, object value) + { + // var newValue = JSONWrapper.castObjToJSON(value); + var index = keyMap.FindIndex(r => r.Key == kvp.Key); + if (index != -1) keyMap[index] = new KeyValuePair(kvp.Key, value); + } + + public object this[string idx] + { + get + { + var result = int.TryParse(idx, out var idxI) + ? keyMap.ElementAtOrDefault(idxI) + : keyMap.FirstOrDefault(p => p.Key == idx); + return result.Equals(default(KeyValuePair)) ? null : result.Value; } - else + set { - beatmapActions.Add(new BeatmapObjectPlacementAction(note.wrapped, Enumerable.Empty(), "Script spawned object")); + var result = int.TryParse(idx, out var idxI) + ? keyMap.ElementAtOrDefault(idxI) + : keyMap.FirstOrDefault(p => p.Key == idx); + if (result.Equals(default(KeyValuePair))) return; + SetValue(result, value); } } - - collection.RefreshPool(); - return beatmapActions; } } diff --git a/Checks/StackedNotes.cs b/Checks/StackedNotes.cs index 2330d64..29d31bb 100644 --- a/Checks/StackedNotes.cs +++ b/Checks/StackedNotes.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using Beatmap.Base; +using Beatmap.Base.Customs; class StackedNotes : Check { @@ -7,7 +9,7 @@ public StackedNotes() : base("Stacked Notes") { } - protected override CheckResult PerformCheck(List notes, List events, List walls, List customEvents, List bpmChanges) + protected override CheckResult PerformCheck(List notes, List bombs, List arcs, List chains, List events, List walls, List customEvents, List bpmEvents) { result.Clear(); @@ -18,12 +20,12 @@ protected override CheckResult PerformCheck(List notes, List 0.1) + if (noteB.JsonTime - noteA.JsonTime > 0.1) { break; } - if (noteA.LineIndex == noteB.LineIndex && noteA.LineLayer == noteB.LineLayer) + if (noteA.PosX == noteB.PosX && noteA.PosY == noteB.PosY) { result.Add(noteA); result.Add(noteB); diff --git a/Checks/VisionBlocks.cs b/Checks/VisionBlocks.cs index 7676b2b..8c41907 100644 --- a/Checks/VisionBlocks.cs +++ b/Checks/VisionBlocks.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using Beatmap.Base; +using Beatmap.Base.Customs; class VisionBlocks : Check { @@ -9,24 +11,26 @@ public VisionBlocks() : base("Vision Blocks") Params.Add(new FloatParam("Max Time", 0.75f)); } - public override CheckResult PerformCheck(List notes, List events, List walls, List customEvents, List bpmChanges, params IParamValue[] vals) + public override CheckResult PerformCheck(List notes, List bombs, List arcs, + List chains, List events, List walls, List customEvents, + List BpmEvents, params KeyValuePair[] vals) { if (vals.Length > 1) { - return PerformCheck(notes, ((ParamValue) vals[0]).value, ((ParamValue) vals[1]).value); + return PerformCheck(notes, ((ParamValue) vals[0].Value).value, ((ParamValue) vals[1].Value).value); } throw new ArgumentException("Wrong number of parameters"); } - public CheckResult PerformCheck(List notes, float minTime, float maxTime) + public CheckResult PerformCheck(List notes, float minTime, float maxTime) { result.Clear(); float visionBlockLeft = -1f; float visionBlockRight = -1f; - BeatmapNote visionBlockLeftNote = null; - BeatmapNote visionBlockRightNote = null; + BaseNote visionBlockLeftNote = null; + BaseNote visionBlockRightNote = null; if (notes.Count > 0) { visionBlockLeftNote = notes[0]; @@ -34,46 +38,46 @@ public CheckResult PerformCheck(List notes, float minTime, float ma } foreach (var note in notes) { - if (note.Time - visionBlockLeft <= maxTime) + if (note.JsonTime - visionBlockLeft <= maxTime) { - if (note.LineIndex < 2 && note.Time - visionBlockLeft > minTime) + if (note.PosX < 2 && note.JsonTime - visionBlockLeft > minTime) { result.Add(visionBlockLeftNote, "Blocks vision of upcoming note on the left"); result.AddWarning(note, "Is blocked"); } - if (note.LineLayer == 1 && note.LineIndex == 1) + if (note.PosY == 1 && note.PosX == 1) { result.Add(visionBlockLeftNote, "Blocks vision of upcoming note on the left"); result.AddWarning(note, "Is blocked"); } } - if (note.Time - visionBlockRight <= maxTime) + if (note.JsonTime - visionBlockRight <= maxTime) { - if (note.LineIndex > 1 && note.Time - visionBlockRight > minTime) + if (note.PosX > 1 && note.JsonTime - visionBlockRight > minTime) { result.Add(visionBlockRightNote, "Blocks vision of upcoming note on the right"); result.AddWarning(note, "Is blocked"); } - if (note.LineLayer == 1 && note.LineIndex == 2 && note.Time - visionBlockLeft <= maxTime) + if (note.PosY == 1 && note.PosX == 2 && note.JsonTime - visionBlockLeft <= maxTime) { result.Add(visionBlockRightNote, "Blocks vision of upcoming note on the right"); result.AddWarning(note, "Is blocked"); } } - if (note.Type != 3 && note.LineLayer == 1) + if (note.Type != 3 && note.PosY == 1) { - if (note.LineIndex == 1) + if (note.PosX == 1) { - visionBlockLeft = note.Time; + visionBlockLeft = note.JsonTime; visionBlockLeftNote = note; } - else if (note.LineIndex == 2) + else if (note.PosX == 2) { - visionBlockRight = note.Time; + visionBlockRight = note.JsonTime; visionBlockRightNote = note; } } diff --git a/ErrorChecker.csproj b/ErrorChecker.csproj index 996f9e7..cb4dada 100644 --- a/ErrorChecker.csproj +++ b/ErrorChecker.csproj @@ -1,9 +1,9 @@  - - - - + + + + Debug @@ -41,38 +41,181 @@ 4 - - packages\Costura.Fody.4.1.0\lib\net40\Costura.dll - True + + packages\Costura.Fody.5.8.0-alpha0098\lib\netstandard1.0\Costura.dll - - packages\Esprima.2.0.3\lib\net461\Esprima.dll - True + + packages\Esprima.3.0.0-beta-9\lib\net462\Esprima.dll - - packages\Jint.3.0.0-beta-2034\lib\net461\Jint.dll - True + + packages\Jint.3.0.0-beta-2044\lib\net462\Jint.dll $(ChromapperDir)\ChroMapper_Data\Managed\Main.dll False + + packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll + True + True + $(ChromapperDir)\ChroMapper_Data\Managed\Plugins.dll False + + $(ChromapperDir)\ChroMapper_Data\Managed\LiteNetLib.dll + False + $(ChromapperDir)\ChroMapper_Data\Managed\0Harmony.dll False + + packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll + True + True + + + packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + + packages\System.Console.4.3.1\lib\net46\System.Console.dll + True + True + + + packages\System.Diagnostics.DiagnosticSource.7.0.0-rc.1.22426.10\lib\net462\System.Diagnostics.DiagnosticSource.dll + + + packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll + True + True + + + packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll + True + True + + + packages\System.IO.4.3.0\lib\net462\System.IO.dll + True + True + + + packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + True + + + + packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll + True + True + + + packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + True + True + + + packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + True + True + + + packages\System.Linq.4.3.0\lib\net463\System.Linq.dll + True + True + + + packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll + True + True + + + packages\System.Memory.4.5.5\lib\net461\System.Memory.dll + + + packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll + True + True + + + packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll + True + True + + + + packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll + True + True + + + packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll + True + True + + + packages\System.Runtime.CompilerServices.Unsafe.7.0.0-preview.2.22152.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll + + + packages\System.Runtime.Extensions.4.3.1\lib\net462\System.Runtime.Extensions.dll + True + True + + + packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll + True + True + + + packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + True + True + + + packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net463\System.Security.Cryptography.Algorithms.dll + True + True + + + packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + True + + + packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + True + + + packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net461\System.Security.Cryptography.X509Certificates.dll + True + True + + + packages\System.Text.RegularExpressions.4.3.1\lib\net463\System.Text.RegularExpressions.dll + True + True + - + + packages\System.Xml.ReaderWriter.4.3.1\lib\net46\System.Xml.ReaderWriter.dll + True + True + $(ChromapperDir)\ChroMapper_Data\Managed\Unity.TextMeshPro.dll @@ -115,46 +258,55 @@ - - - + + + + + + + + - - + + CodeSigning.pfx - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - + + + + + + + + + + - - - "C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64\SignTool" sign /t http://timestamp.digicert.com /fd sha256 /sha1 494ABC44B6FB828C0F185DB80A88C043C4DE77C4 CM-JS.dll del "$(ChromapperDir)\Plugins\CM-JS\CM-JS.dll" copy CM-JS.dll "$(ChromapperDir)\Plugins\CM-JS\" - - + + + + + + + \ No newline at end of file diff --git a/FodyWeavers.xsd b/FodyWeavers.xsd new file mode 100644 index 0000000..05e92c1 --- /dev/null +++ b/FodyWeavers.xsd @@ -0,0 +1,141 @@ + + + + + + + + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of unmanaged 32 bit assembly names to include, delimited with line breaks. + + + + + A list of unmanaged 64 bit assembly names to include, delimited with line breaks. + + + + + The order of preloaded assemblies, delimited with line breaks. + + + + + + This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. + + + + + Controls if .pdbs for reference assemblies are also embedded. + + + + + Controls if runtime assemblies are also embedded. + + + + + Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. + + + + + Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. + + + + + As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. + + + + + Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. + + + + + Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + A list of unmanaged 32 bit assembly names to include, delimited with |. + + + + + A list of unmanaged 64 bit assembly names to include, delimited with |. + + + + + The order of preloaded assemblies, delimited with |. + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/JintPatch.cs b/JintPatch.cs index 42fc839..fd672c3 100644 --- a/JintPatch.cs +++ b/JintPatch.cs @@ -22,7 +22,7 @@ public static bool Prefix(ObjectInstance __instance, JsValue property, ref bool { if (__instance is ObjectWrapper wrapper) { - if (wrapper.Target is JSONWraper jsonWraper) + if (wrapper.Target is JSONWrapper jsonWraper) { jsonWraper.DeleteProperty(property); __result = true; @@ -42,7 +42,7 @@ public static bool Prefix(ObjectInstance __instance, ref List __result) { if (__instance is ObjectWrapper wrapper) { - if (wrapper.Target is JSONWraper jsonWraper) + if (wrapper.Target is JSONWrapper jsonWraper) { __result = new List(); foreach (var wrappedKey in jsonWraper.wrapped.Keys) diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index dc181cb..2c5949f 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.3.0")] -[assembly: AssemblyFileVersion("1.2.3.0")] +[assembly: AssemblyVersion("1.3.0.0")] +[assembly: AssemblyFileVersion("1.3.0.0")] diff --git a/README.md b/README.md index 23dc03f..fee68d9 100644 --- a/README.md +++ b/README.md @@ -7,49 +7,82 @@ Simple plugin that adds an error checker to [ChroMapper](https://github.com/Caed Download the latest release, place the entire folder in your `ChroMapper/Plugins` directory ### Built-in plugins -* Vision Blocks - * Copy of the logic in MMA2's error checker - * Finds blocks that are obscured by blocks in the center two positions -* Stacked Blocks - * Finds blocks that may be overlapping - * Looks for notes less than 0.1 beats of each other in the same position + +- Vision Blocks + - Copy of the logic in MMA2's error checker + - Finds blocks that are obscured by blocks in the center two positions +- Stacked Blocks + - Finds blocks that may be overlapping + - Looks for notes less than 0.1 beats of each other in the same position ### JS Checks You can add more error check types directly by placing javascript files in the same directory as the plugin. -The parser uses [Jint 3 (beta)](https://github.com/sebastienros/jint/issues/343) and [esprima](https://github.com/sebastienros/esprima-dotnet) which can parse ES6 but not all features are supported, for instance esprima can parse `class` but Jint will through an exception so you'll have to rewrite these as old-style functions and prototypes +The parser uses [Jint 3 (beta)](https://github.com/sebastienros/jint/issues/343) and [esprima](https://github.com/sebastienros/esprima-dotnet) which can parse ES6 but not all features are supported, for instance esprima can parse `class` but Jint will throw an exception so you'll have to rewrite these as old-style functions and prototypes -As this is essentially alpha the interface for these checks may change but for now you just need to create a block similar to: -``` +The script made for beatmap v2 can be used for beatmap v3 and vice versa. However, `customData` require special handling by the script to assign correct key for specific version. + +#### Module + +This part of the code defines the script to be shown and used in editor. This is currently designed to be compatible with MM scripts, you just need to add the name and params. As this is essentially alpha, the interface for these checks may change but for now you just need to create a block similar to: + +```js module.exports = { name: "My error check", - params: {"Min Time": 0.24}, - run: function(cursor, notes, events, walls, _, global, data, customEvents, bpmChanges) + params: { "Min Time": 0.24 }, + run: (cursor, notes, events, walls, _, global, data, customEvents, bpmChanges, bombs, arcs, chains) => {}, + errorCheck: true }; ``` -* **name** appears in the drop-down ingame. -* **params** is an object, for each key a text field will be show to the user where they can enter a value which will be passed to you as a float -* **run** is a function that will be called when the user runs your check, this is currently designed to be compatible with MM scripts, you just need to add the name and params. - * The first parameter to the function is the position the user is looking at in the map - * Next is an array of notes ordered by time (I can't guarantee what order notes that happen at the same time will be in for now) which can be modified. You can directly modify the values of notes in the array with code like `notes[0]._time = 10;` -OR if you want to generate a fresh array you need to provide your new array in object returned from your function (see below) - * Lighting events are provided next and work the same as notes - * Obstacles are last of the map objects and work the same as notes - * In MM scripts the parameter here was called save? I don't know what it did but this parameter is just an empty object and has no use. Provided for compatibility. - * The global parameter can be used to persist data between runs of your script, it will be unchanged on future invocations. It also includes a `params` array which contains the values set for your params. (I _assume_ this will be returned in order) - * Data has information about the map, currently the list of data is as follows: - * currentBPM - The bpm at the cursor accounting for BPM changes - * songBPM - The bpm of the song - * NJS - The note jump speed set for the song - * offset - How far into the song the user starts - * Custom events are custom objects used for [noodling](https://github.com/Aeroluna/NoodleExtensions/blob/master/Documentation/AnimationDocs.md#custom-events) - * BPM Changes may be useful for working out what the bpm is at a point in a map or programatically creating slides - -Two functions will be defined before calling `performCheck` -* **addError**(note, reason) - Pass back the problem note object, all it's properties (except `_customData`) must match the original passed note for it to be marked properly and you can provide a reason as the second parameter -* **addWarning**(note, reason) - The same as `addError` except the note will only be highlighted yellow +- `name` string appears in the drop-down list. +- `params` is an object, for each key a text field will be show to the user where they can enter a value which will be passed to `run` function + - The object is only allowed to have set of certain type of value: + - `"stringKey": "string"` + - `"numberKey": 0.0` + - `"booleanKey": true` + - `"arrayKey": ["ary"]` (only array of string is allowed) +- `run` is a function that will be called when the user runs your check. +- `errorCheck` is an optional boolean property to display error message, default to `true`. + +#### Run Function + +The function uses the data from current active beatmap. You can directly modify the value of beatmap objects in the array with code like `notes[0]._time = 10;` **OR** if you want to generate a fresh array you need to provide your new array in object returned from your function (see below). + +```js +function run(cursor, notes, events, walls, _, global, data, customEvents, bpmChanges, bombs, arcs, chains) {} +``` + + - `cursor` is the current beat time of grid cursor in editor. + - `notes` is an array of objects (v3 will not include bomb note). + - `events` is an array of events (v3 will not include boost and rotation event). + - `walls` is an array of obstacles. + - `_` In MM scripts the parameter here was called save? I don't know what it did but this parameter is just an empty object and has no use. Provided for compatibility. + - `globals` can be used to persist data between runs of your script, it will be unchanged on future invocations. + - `params` is included as an object which contains the values set for your params. + - The value set for params is `number`, `string` and `boolean`. + - It can be accessed via string index `global.params.key` or number index `global.params[0]` (based on the order of `module.exports.params`). + - Property assignment is not possible and re-assignment while allowed is not recommended, assign the param value to new variable is recommended if called often. + - `data` has read-only information about the map, currently the list of data is as follows: + - `currentBPM` is the BPM at the cursor accounting for BPM changes. + - `songBPM` is the BPM of the song. + - `NJS` is the note jump speed on current difficulty. + - `offset` is the offset of NJS on current difficulty. + - `characteristic` is current beatmap characteristics. + - `difficulty` is current beatmap difficulty. + - `environment` is current beatmap environment. + - `version` is current beatmap version. + - `customEvents` is an array of custom objects used for [noodling](https://github.com/Aeroluna/NoodleExtensions/blob/master/Documentation/AnimationDocs.md#custom-events) + - `bpmChanges` may be useful for working out what the bpm is at a point in a map or programatically creating slides. + - `bombs` **V3 ONLY** is an array of bomb notes. + - `arcs` **V3 ONLY** is an array of arcs. + - `chains` **V3 ONLY** is an array of chains. + +Two functions will be defined before calling `run` or `performCheck` + +- `addError(note, reason)` pass back the problem note object, all it's properties (except `customData`) must match the original passed note for it to be marked properly and you can provide a reason as the second parameter. +- `addWarning(note, reason)` is the same as `addError` except the note will only be highlighted yellow. [There are example scripts in Examples](Examples) diff --git a/Wrappers/Beatmap/Arc.cs b/Wrappers/Beatmap/Arc.cs new file mode 100644 index 0000000..0bd7465 --- /dev/null +++ b/Wrappers/Beatmap/Arc.cs @@ -0,0 +1,176 @@ +using Beatmap.Base; +using Beatmap.Enums; +using Beatmap.Helper; +using Jint; +using Jint.Native.Object; + +internal class Arc : VanillaWrapper +{ + public Arc(Engine engine, BaseArc arc) : base(engine, arc) + { + spawned = true; + } + + public Arc(Engine engine, ObjectInstance o) : base(engine, BeatmapFactory.Arc( + (float)GetJsValue(o, "b"), + (int)GetJsValue(o, "x"), + (int)GetJsValue(o, "y"), + (int)GetJsValue(o, "c"), + (int)GetJsValue(o, "d"), + 0, + (float)GetJsValue(o, "mu"), + (float)GetJsValue(o, "tb"), + (int)GetJsValue(o, "tx"), + (int)GetJsValue(o, "ty"), + (int)GetJsValue(o, "tc"), + (float)GetJsValue(o, "tmu"), + (int)GetJsValue(o, "m"), + GetCustomData(o, new[] { "customData", "_customData" }) + ), false, GetJsBool(o, "selected")) + { + spawned = false; + + DeleteObject(); + } + + public float b + { + get => wrapped.JsonTime; + set + { + DeleteObject(); + wrapped.JsonTime = value; + } + } + + public int x + { + get => wrapped.PosX; + set + { + DeleteObject(); + wrapped.PosX = value; + } + } + + public int y + { + get => wrapped.PosY; + set + { + DeleteObject(); + wrapped.PosY = value; + } + } + + public int c + { + get => wrapped.Color; + set + { + DeleteObject(); + wrapped.Color = value; + } + } + + public int d + { + get => wrapped.CutDirection; + set + { + DeleteObject(); + wrapped.CutDirection = value; + } + } + + public float mu + { + get => wrapped.HeadControlPointLengthMultiplier; + set + { + DeleteObject(); + wrapped.HeadControlPointLengthMultiplier = value; + } + } + + public float tb + { + get => wrapped.TailJsonTime; + set + { + DeleteObject(); + wrapped.TailJsonTime = value; + } + } + + public int tx + { + get => wrapped.TailPosX; + set + { + DeleteObject(); + wrapped.TailPosX = value; + } + } + + public int ty + { + get => wrapped.TailPosY; + set + { + DeleteObject(); + wrapped.TailPosY = value; + } + } + + public int tc + { + get => wrapped.TailCutDirection; + set + { + DeleteObject(); + wrapped.TailCutDirection = value; + } + } + + public float tmu + { + get => wrapped.TailControlPointLengthMultiplier; + set + { + DeleteObject(); + wrapped.TailControlPointLengthMultiplier = value; + } + } + + public int m + { + get => wrapped.MidAnchorMode; + set + { + DeleteObject(); + wrapped.MidAnchorMode = value; + } + } + + public override bool SpawnObject(BeatmapObjectContainerCollection collection) + { + if (spawned) return false; + + collection.SpawnObject(wrapped, false, false); + + spawned = true; + return true; + } + + internal override bool DeleteObject() + { + if (!spawned) return false; + + var collection = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.Arc); + collection.DeleteObject(wrapped, false); + + spawned = false; + return true; + } +} diff --git a/Wrappers/Beatmap/BombNote.cs b/Wrappers/Beatmap/BombNote.cs new file mode 100644 index 0000000..7ed33c6 --- /dev/null +++ b/Wrappers/Beatmap/BombNote.cs @@ -0,0 +1,76 @@ +using Beatmap.Base; +using Beatmap.Enums; +using Beatmap.Helper; +using Jint; +using Jint.Native.Object; + +internal class BombNote : VanillaWrapper +{ + public BombNote(Engine engine, BaseNote bomb) : base(engine, bomb) + { + spawned = true; + } + + public BombNote(Engine engine, ObjectInstance o) : base(engine, BeatmapFactory.Bomb( + (float)GetJsValue(o, "b"), + (int)GetJsValue(o, "x"), + (int)GetJsValue(o, "y"), + GetCustomData(o, new[] { "customData", "_customData" }) + ), false, GetJsBool(o, "selected")) + { + spawned = false; + + DeleteObject(); + } + + public float b + { + get => wrapped.JsonTime; + set + { + DeleteObject(); + wrapped.JsonTime = value; + } + } + + public int x + { + get => wrapped.PosX; + set + { + DeleteObject(); + wrapped.PosX = value; + } + } + + public int y + { + get => wrapped.PosY; + set + { + DeleteObject(); + wrapped.PosY = value; + } + } + + public override bool SpawnObject(BeatmapObjectContainerCollection collection) + { + if (spawned) return false; + + collection.SpawnObject(wrapped, false, false); + + spawned = true; + return true; + } + + internal override bool DeleteObject() + { + if (!spawned) return false; + + var collection = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.Note); + collection.DeleteObject(wrapped, false); + + spawned = false; + return true; + } +} diff --git a/Wrappers/BpmChange.cs b/Wrappers/Beatmap/BpmEvent.cs similarity index 53% rename from Wrappers/BpmChange.cs rename to Wrappers/Beatmap/BpmEvent.cs index ee14f90..4257bc8 100644 --- a/Wrappers/BpmChange.cs +++ b/Wrappers/Beatmap/BpmEvent.cs @@ -1,62 +1,64 @@ -using Jint; +using Beatmap.Base; +using Beatmap.Enums; +using Beatmap.Helper; +using Jint; using Jint.Native.Object; -class BpmChange : Wrapper +internal class BpmEvent : Wrapper { - public float _time { - get => wrapped.Time; - set { - DeleteObject(); - wrapped.Time = value; - } + public BpmEvent(Engine engine, BaseBpmEvent bpmEvent) : base(engine, bpmEvent) + { + spawned = true; } - public float _BPM + public BpmEvent(Engine engine, ObjectInstance o) : base(engine, BeatmapFactory.BpmEvent( + (float)GetJsValue(o, new[] { "b", "_time" }), + (float)GetJsValue(o, new[] { "m", "_BPM" }) + ), false, GetJsBool(o, "selected")) { - get => wrapped.Bpm; + spawned = false; + + DeleteObject(); + } + + public float _time + { + get => wrapped.JsonTime; set { DeleteObject(); - wrapped.Bpm = value; + wrapped.JsonTime = value; } } - public float _beatsPerBar + public float _BPM { - get => wrapped.BeatsPerBar; + get => wrapped.FloatValue; set { DeleteObject(); - wrapped.BeatsPerBar = value; + wrapped.FloatValue = value; } } - public float _metronomeOffset + public float b { - get => wrapped.MetronomeOffset; + get => wrapped.JsonTime; set { DeleteObject(); - wrapped.MetronomeOffset = value; + wrapped.JsonTime = value; } } - public BpmChange(Engine engine, BeatmapBPMChange bpmChange) : base(engine, bpmChange) + public float m { - spawned = true; - } - - public BpmChange(Engine engine, ObjectInstance o) : base(engine, new BeatmapBPMChange( - (float) GetJsValue(o, "_BPM"), - (float) GetJsValue(o, "_time") - ) { - BeatsPerBar = (float) GetJsValue(o, "_beatsPerBar"), - MetronomeOffset = (float) GetJsValue(o, "_metronomeOffset") - }, false, GetJsBool(o, "selected")) - { - spawned = false; - - DeleteObject(); + get => wrapped.Bpm; + set + { + DeleteObject(); + wrapped.Bpm = value; + } } public override bool SpawnObject(BeatmapObjectContainerCollection collection) @@ -73,7 +75,7 @@ internal override bool DeleteObject() { if (!spawned) return false; - var collection = BeatmapObjectContainerCollection.GetCollectionForType(BeatmapObject.ObjectType.BpmChange); + var collection = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.BpmChange); collection.DeleteObject(wrapped, false); spawned = false; diff --git a/Wrappers/Beatmap/Chain.cs b/Wrappers/Beatmap/Chain.cs new file mode 100644 index 0000000..aabfb1a --- /dev/null +++ b/Wrappers/Beatmap/Chain.cs @@ -0,0 +1,154 @@ +using Beatmap.Base; +using Beatmap.Enums; +using Beatmap.Helper; +using Jint; +using Jint.Native.Object; + +internal class Chain : VanillaWrapper +{ + public Chain(Engine engine, BaseChain chain) : base(engine, chain) + { + spawned = true; + } + + public Chain(Engine engine, ObjectInstance o) : base(engine, BeatmapFactory.Chain( + (int)GetJsValue(o, "b"), + (int)GetJsValue(o, "x"), + (int)GetJsValue(o, "y"), + (int)GetJsValue(o, "c"), + (int)GetJsValue(o, "d"), + 0, + (float)GetJsValue(o, "tb"), + (int)GetJsValue(o, "tx"), + (int)GetJsValue(o, "ty"), + (int)GetJsValue(o, "sc"), + (float)GetJsValue(o, "s"), + GetCustomData(o, new[] { "customData", "_customData" }) + ), false, GetJsBool(o, "selected")) + { + spawned = false; + + DeleteObject(); + } + + public float b + { + get => wrapped.JsonTime; + set + { + DeleteObject(); + wrapped.JsonTime = value; + } + } + + public int x + { + get => wrapped.PosX; + set + { + DeleteObject(); + wrapped.PosX = value; + } + } + + public int y + { + get => wrapped.PosY; + set + { + DeleteObject(); + wrapped.PosY = value; + } + } + + public int c + { + get => wrapped.Color; + set + { + DeleteObject(); + wrapped.Color = value; + } + } + + public int d + { + get => wrapped.CutDirection; + set + { + DeleteObject(); + wrapped.CutDirection = value; + } + } + + public float tb + { + get => wrapped.TailJsonTime; + set + { + DeleteObject(); + wrapped.TailJsonTime = value; + } + } + + public int tx + { + get => wrapped.TailPosX; + set + { + DeleteObject(); + wrapped.TailPosX = value; + } + } + + public int ty + { + get => wrapped.TailPosY; + set + { + DeleteObject(); + wrapped.TailPosY = value; + } + } + + public int sc + { + get => wrapped.SliceCount; + set + { + DeleteObject(); + wrapped.SliceCount = value; + } + } + + public float s + { + get => wrapped.Squish; + set + { + DeleteObject(); + wrapped.Squish = value; + } + } + + public override bool SpawnObject(BeatmapObjectContainerCollection collection) + { + if (spawned) return false; + + collection.SpawnObject(wrapped, false, false); + + spawned = true; + return true; + } + + internal override bool DeleteObject() + { + if (!spawned) return false; + + var collection = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.Chain); + collection.DeleteObject(wrapped, false); + + spawned = false; + return true; + } +} diff --git a/Wrappers/Beatmap/CustomEvent.cs b/Wrappers/Beatmap/CustomEvent.cs new file mode 100644 index 0000000..d2ea0eb --- /dev/null +++ b/Wrappers/Beatmap/CustomEvent.cs @@ -0,0 +1,126 @@ +using System; +using Beatmap.Base.Customs; +using Beatmap.Enums; +using Beatmap.Helper; +using Jint; +using Jint.Native.Object; + +internal class CustomEvent : Wrapper +{ + private Lazy customData; + private Action reconcile; + + public CustomEvent(Engine engine, BaseCustomEvent customEvent) : base(engine, customEvent) + { + spawned = true; + InitWrapper(); + } + + public CustomEvent(Engine engine, ObjectInstance o) : base(engine, BeatmapFactory.CustomEvent( + (float)GetJsValue(o, new[] { "b", "_time" }), + GetJsString(o, new[] { "t", "_type" }), + GetCustomData(o, new[] { "d", "_data" }) + ), false, GetJsBool(o, "selected")) + { + spawned = false; + + DeleteObject(); + InitWrapper(); + } + + public float _time + { + get => wrapped.JsonTime; + set + { + DeleteObject(); + wrapped.JsonTime = value; + } + } + + public string _type + { + get => wrapped.Type; + set + { + DeleteObject(); + wrapped.Type = value; + } + } + + public object _data + { + get => wrapped.Data == null ? null : customData.Value; + set + { + DeleteObject(); + wrapped.Data = JSONWrapper.castObjToJSON(value); + InitWrapper(); + } + } + + public float b + { + get => wrapped.JsonTime; + set + { + DeleteObject(); + wrapped.JsonTime = value; + } + } + + public string t + { + get => wrapped.Type; + set + { + DeleteObject(); + wrapped.Type = value; + } + } + + public object d + { + get => wrapped.Data == null ? null : customData.Value; + set + { + DeleteObject(); + wrapped.Data = JSONWrapper.castObjToJSON(value); + InitWrapper(); + } + } + + public override bool SpawnObject(BeatmapObjectContainerCollection collection) + { + if (spawned) return false; + + collection.SpawnObject(wrapped, false, false); + + spawned = true; + return true; + } + + internal override bool DeleteObject() + { + if (!spawned) return false; + + var collection = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.CustomEvent); + collection.DeleteObject(wrapped, false); + + spawned = false; + return true; + } + + private void InitWrapper() + { + reconcile = null; + customData = new Lazy(() => + new JSONWrapper(engine, ref reconcile, wrapped.Data, DeleteObject) + ); + } + + internal override void Reconcile() + { + reconcile?.Invoke(); + } +} diff --git a/Wrappers/Beatmap/Event.cs b/Wrappers/Beatmap/Event.cs new file mode 100644 index 0000000..27dca41 --- /dev/null +++ b/Wrappers/Beatmap/Event.cs @@ -0,0 +1,127 @@ +using Beatmap.Base; +using Beatmap.Enums; +using Beatmap.Helper; +using Jint; +using Jint.Native.Object; + +internal class Event : VanillaWrapper +{ + public Event(Engine engine, BaseEvent mapEvent) : base(engine, mapEvent) + { + spawned = true; + } + + public Event(Engine engine, ObjectInstance o) : base(engine, BeatmapFactory.Event( + (float)GetJsValue(o, new[] { "b", "_time" }), + (int)GetJsValue(o, new[] { "et", "_type" }), + (int)GetJsValue(o, new[] { "i", "_value" }), + (float)GetJsValue(o, new[] { "f", "_floatValue" }), + GetCustomData(o, new[] { "customData", "_customData" }) + ), false, GetJsBool(o, "selected")) + { + spawned = false; + + DeleteObject(); + } + + public float _time + { + get => wrapped.JsonTime; + set + { + DeleteObject(); + wrapped.JsonTime = value; + } + } + + public int _type + { + get => wrapped.Type; + set + { + DeleteObject(); + wrapped.Type = value; + } + } + + public int _value + { + get => wrapped.Value; + set + { + DeleteObject(); + wrapped.Value = value; + } + } + + public float _floatValue + { + get => wrapped.FloatValue; + set + { + DeleteObject(); + wrapped.FloatValue = value; + } + } + + public float b + { + get => wrapped.JsonTime; + set + { + DeleteObject(); + wrapped.JsonTime = value; + } + } + + public int et + { + get => wrapped.Type; + set + { + DeleteObject(); + wrapped.Type = value; + } + } + + public int i + { + get => wrapped.Value; + set + { + DeleteObject(); + wrapped.Value = value; + } + } + + public float f + { + get => wrapped.FloatValue; + set + { + DeleteObject(); + wrapped.FloatValue = value; + } + } + + public override bool SpawnObject(BeatmapObjectContainerCollection collection) + { + if (spawned) return false; + + collection.SpawnObject(wrapped, false, false); + + spawned = true; + return true; + } + + internal override bool DeleteObject() + { + if (!spawned) return false; + + var collection = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.Event); + collection.DeleteObject(wrapped, false); + + spawned = false; + return true; + } +} diff --git a/Wrappers/Beatmap/Note.cs b/Wrappers/Beatmap/Note.cs new file mode 100644 index 0000000..9a02e94 --- /dev/null +++ b/Wrappers/Beatmap/Note.cs @@ -0,0 +1,159 @@ +using Beatmap.Base; +using Beatmap.Enums; +using Beatmap.Helper; +using Jint; +using Jint.Native.Object; + +internal class Note : VanillaWrapper +{ + public Note(Engine engine, BaseNote note) : base(engine, note) + { + spawned = true; + } + + public Note(Engine engine, ObjectInstance o) : base(engine, BeatmapFactory.Note( + (float)GetJsValue(o, new[] { "b", "_time" }), + (int)GetJsValue(o, new[] { "x", "_lineIndex" }), + (int)GetJsValue(o, new[] { "y", "_lineLayer" }), + (int)GetJsValue(o, new[] { "c", "_type" }), + (int)GetJsValue(o, new[] { "d", "_cutDirection" }), + (int)(GetJsValueOptional(o, "a") ?? 0), + GetCustomData(o, new[] { "customData", "_customData" }) + ), false, GetJsBool(o, "selected")) + { + spawned = false; + + DeleteObject(); + } + + public float _time + { + get => wrapped.JsonTime; + set + { + DeleteObject(); + wrapped.JsonTime = value; + } + } + + public int _lineIndex + { + get => wrapped.PosX; + set + { + DeleteObject(); + wrapped.PosX = value; + } + } + + public int _lineLayer + { + get => wrapped.PosY; + set + { + DeleteObject(); + wrapped.PosY = value; + } + } + + public int _cutDirection + { + get => wrapped.CutDirection; + set + { + DeleteObject(); + wrapped.CutDirection = value; + } + } + + public int _type + { + get => wrapped.Color; + set + { + DeleteObject(); + wrapped.Color = value; + } + } + + public float b + { + get => wrapped.JsonTime; + set + { + DeleteObject(); + wrapped.JsonTime = value; + } + } + + public int x + { + get => wrapped.PosX; + set + { + DeleteObject(); + wrapped.PosX = value; + } + } + + public int y + { + get => wrapped.PosY; + set + { + DeleteObject(); + wrapped.PosY = value; + } + } + + public int c + { + get => wrapped.Color; + set + { + DeleteObject(); + wrapped.Color = value; + } + } + + public int d + { + get => wrapped.CutDirection; + set + { + DeleteObject(); + wrapped.CutDirection = value; + } + } + + public int a + { + get => wrapped.AngleOffset; + set + { + DeleteObject(); + wrapped.AngleOffset = value; + } + } + + public override bool SpawnObject(BeatmapObjectContainerCollection collection) + { + if (spawned) return false; + + collection.SpawnObject(wrapped, false, false); + + spawned = true; + return true; + } + + internal override bool DeleteObject() + { + if (!spawned) return false; + + var collection = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.Note); + collection.DeleteObject(wrapped, false); + + spawned = false; + return true; + } +} diff --git a/Wrappers/Beatmap/Wall.cs b/Wrappers/Beatmap/Wall.cs new file mode 100644 index 0000000..985baf0 --- /dev/null +++ b/Wrappers/Beatmap/Wall.cs @@ -0,0 +1,159 @@ +using Beatmap.Base; +using Beatmap.Enums; +using Beatmap.Helper; +using Jint; +using Jint.Native.Object; + +internal class Wall : VanillaWrapper +{ + public Wall(Engine engine, BaseObstacle wall) : base(engine, wall) + { + spawned = true; + } + + public Wall(Engine engine, ObjectInstance o) : base(engine, BeatmapFactory.Obstacle( + (float)GetJsValue(o, new[] { "b", "_time" }), + (int)GetJsValue(o, new[] { "x", "_lineIndex" }), + (int)(GetJsExist(o, "y") ? GetJsValue(o, "y") : GetJsValue(o, "_type") == 0 ? 0 : 2), + (int)(GetJsExist(o, "_type") ? GetJsValue(o, "_type") : 0), + (float)GetJsValue(o, new[] { "d", "_duration" }), + (int)GetJsValue(o, new[] { "w", "_width" }), + (int)(GetJsExist(o, "h") ? GetJsValue(o, "h") : GetJsValue(o, "_type") == 0 ? 5 : 3), + GetCustomData(o, new[] { "customData", "_customData" })), false, GetJsBool(o, "selected")) + { + spawned = false; + + DeleteObject(); + } + + public float _time + { + get => wrapped.JsonTime; + set + { + DeleteObject(); + wrapped.JsonTime = value; + } + } + + public int _lineIndex + { + get => wrapped.PosX; + set + { + DeleteObject(); + wrapped.PosX = value; + } + } + + public int _type + { + get => wrapped.Type; + set + { + DeleteObject(); + wrapped.Type = value; + } + } + + public float _duration + { + get => wrapped.Duration; + set + { + DeleteObject(); + wrapped.Duration = value; + } + } + + public int _width + { + get => wrapped.Width; + set + { + DeleteObject(); + wrapped.Width = value; + } + } + + public float b + { + get => wrapped.JsonTime; + set + { + DeleteObject(); + wrapped.JsonTime = value; + } + } + + public int x + { + get => wrapped.PosX; + set + { + DeleteObject(); + wrapped.PosX = value; + } + } + + public int y + { + get => wrapped.PosY; + set + { + DeleteObject(); + wrapped.PosY = value; + } + } + + public float d + { + get => wrapped.Duration; + set + { + DeleteObject(); + wrapped.Duration = value; + } + } + + public int w + { + get => wrapped.Width; + set + { + DeleteObject(); + wrapped.Width = value; + } + } + + public int h + { + get => wrapped.Height; + set + { + DeleteObject(); + wrapped.Height = value; + } + } + + public override bool SpawnObject(BeatmapObjectContainerCollection collection) + { + if (spawned) return false; + + collection.SpawnObject(wrapped, false, false); + + spawned = true; + return true; + } + + internal override bool DeleteObject() + { + if (!spawned) return false; + + var collection = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.Obstacle); + collection.DeleteObject(wrapped, false); + + spawned = false; + return true; + } +} diff --git a/Wrappers/CustomEvent.cs b/Wrappers/CustomEvent.cs deleted file mode 100644 index 023ed93..0000000 --- a/Wrappers/CustomEvent.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using Jint; -using Jint.Native.Object; - -class CustomEvent : Wrapper -{ - public float _time { - get => wrapped.Time; - set { - DeleteObject(); - wrapped.Time = value; - } - } - - public string _type - { - get => wrapped.Type; - set - { - DeleteObject(); - wrapped.Type = value; - } - } - - private Lazy customData; - private Action reconcile; - public object _data - { - get => wrapped.CustomData == null ? null : customData.Value; - set - { - DeleteObject(); - wrapped.CustomData = JSONWraper.castObjToJSON(value); - InitWrapper(); - } - } - - public CustomEvent(Engine engine, BeatmapCustomEvent customEvent) : base(engine, customEvent) - { - spawned = true; - InitWrapper(); - } - - public CustomEvent(Engine engine, ObjectInstance o) : base(engine, new BeatmapCustomEvent( - (float) GetJsValue(o, "_time"), - GetJsString(o, "_type"), - GetCustomData(o, "_data") - ), false, GetJsBool(o, "selected")) - { - spawned = false; - - DeleteObject(); - InitWrapper(); - } - - public override bool SpawnObject(BeatmapObjectContainerCollection collection) - { - if (spawned) return false; - - collection.SpawnObject(wrapped, false, false); - - spawned = true; - return true; - } - - internal override bool DeleteObject() - { - if (!spawned) return false; - - var collection = BeatmapObjectContainerCollection.GetCollectionForType(BeatmapObject.ObjectType.Event); - collection.DeleteObject(wrapped, false); - - spawned = false; - return true; - } - - private void InitWrapper() - { - reconcile = null; - customData = new Lazy(() => - new JSONWraper(engine, ref reconcile, wrapped.CustomData, DeleteObject) - ); - } - - internal override void Reconcile() - { - reconcile?.Invoke(); - } -} diff --git a/Wrappers/Event.cs b/Wrappers/Event.cs deleted file mode 100644 index 6222252..0000000 --- a/Wrappers/Event.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Jint; -using Jint.Native.Object; - -class Event : VanillaWrapper -{ - public float _time { - get => wrapped.Time; - set { - DeleteObject(); - wrapped.Time = value; - } - } - - public int _type - { - get => wrapped.Type; - set - { - DeleteObject(); - wrapped.Type = value; - } - } - - public int _value - { - get => wrapped.Value; - set - { - DeleteObject(); - wrapped.Value = value; - } - } - - public Event(Engine engine, MapEvent mapEvent) : base(engine, mapEvent) - { - spawned = true; - } - - public Event(Engine engine, ObjectInstance o) : base(engine, new MapEvent( - (float) GetJsValue(o, "_time"), - (int) GetJsValue(o, "_type"), - (int) GetJsValue(o, "_value"), - GetCustomData(o) - ), false, GetJsBool(o, "selected")) - { - spawned = false; - - DeleteObject(); - } - - public override bool SpawnObject(BeatmapObjectContainerCollection collection) - { - if (spawned) return false; - - if (wrapped.CustomData != null && wrapped.CustomData["_lightGradient"] != null) - { - wrapped.LightGradient = new MapEvent.ChromaGradient(wrapped.CustomData["_lightGradient"]); - } - - collection.SpawnObject(wrapped, false, false); - - spawned = true; - return true; - } - - internal override bool DeleteObject() - { - if (!spawned) return false; - - var collection = BeatmapObjectContainerCollection.GetCollectionForType(BeatmapObject.ObjectType.Event); - collection.DeleteObject(wrapped, false); - - spawned = false; - return true; - } -} diff --git a/Wrappers/JSONWrapper.cs b/Wrappers/JSONWrapper.cs index e2b3d5b..559ae96 100644 --- a/Wrappers/JSONWrapper.cs +++ b/Wrappers/JSONWrapper.cs @@ -8,18 +8,18 @@ using SimpleJSON; using UnityEngine; -class JSONWraper +class JSONWrapper { private readonly Engine engine; internal readonly JSONNode wrapped; private readonly Func deleteObj; private readonly Dictionary observe = new Dictionary(); - private readonly Dictionary children = new Dictionary(); + private readonly Dictionary children = new Dictionary(); private Action checkObserved; private bool cleanObserved = true; - public JSONWraper(Engine engine, ref Action parent, JSONNode wrapped, Func deleteObj) + public JSONWrapper(Engine engine, ref Action parent, JSONNode wrapped, Func deleteObj) { this.engine = engine; this.wrapped = wrapped; @@ -103,7 +103,7 @@ public static JSONNode castObjToJSON(object o) { switch (o) { - case JSONWraper w: + case JSONWrapper w: return w.wrapped; case JsValue v: return castJSToJSON(v); @@ -178,10 +178,10 @@ public void DeleteProperty(JsValue key) } } - private JSONWraper GetChild(string key) + private JSONWrapper GetChild(string key) { if (!children.ContainsKey(key)) - children.Add(key, new JSONWraper(engine, ref checkObserved, wrapped[key], deleteObj)); + children.Add(key, new JSONWrapper(engine, ref checkObserved, wrapped[key], deleteObj)); return children[key]; } diff --git a/Wrappers/Note.cs b/Wrappers/Note.cs deleted file mode 100644 index e2f8e2e..0000000 --- a/Wrappers/Note.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Jint; -using Jint.Native.Object; - -class Note : VanillaWrapper -{ - public float _time { - get => wrapped.Time; - set { - DeleteObject(); - wrapped.Time = value; - } - } - - public int _lineIndex - { - get => wrapped.LineIndex; - set - { - DeleteObject(); - wrapped.LineIndex = value; - } - } - - public int _lineLayer - { - get => wrapped.LineLayer; - set - { - DeleteObject(); - wrapped.LineLayer = value; - } - } - - public int _cutDirection - { - get => wrapped.CutDirection; - set - { - DeleteObject(); - wrapped.CutDirection = value; - } - } - - public int _type - { - get => wrapped.Type; - set - { - DeleteObject(); - wrapped.Type = value; - } - } - - public Note(Engine engine, BeatmapNote note) : base(engine, note) - { - spawned = true; - } - - public Note(Engine engine, ObjectInstance o) : base(engine, new BeatmapNote( - (float) GetJsValue(o, "_time"), - (int) GetJsValue(o, "_lineIndex"), - (int) GetJsValue(o, "_lineLayer"), - (int) GetJsValue(o, "_type"), - (int) GetJsValue(o, "_cutDirection"), - GetCustomData(o) - ), false, GetJsBool(o, "selected")) - { - spawned = false; - - DeleteObject(); - } - - public override bool SpawnObject(BeatmapObjectContainerCollection collection) - { - if (spawned) return false; - - collection.SpawnObject(wrapped, false, false); - - spawned = true; - return true; - } - - internal override bool DeleteObject() - { - if (!spawned) return false; - - var collection = BeatmapObjectContainerCollection.GetCollectionForType(BeatmapObject.ObjectType.Note); - collection.DeleteObject(wrapped, false); - - spawned = false; - return true; - } -} diff --git a/Wrappers/VanillaWrapper.cs b/Wrappers/VanillaWrapper.cs index f9c1de5..72bde93 100644 --- a/Wrappers/VanillaWrapper.cs +++ b/Wrappers/VanillaWrapper.cs @@ -1,18 +1,32 @@ using System; +using Beatmap.Base; using Jint; -abstract class VanillaWrapper : Wrapper where T : BeatmapObject +abstract class VanillaWrapper : Wrapper where T : BaseObject { - private Lazy customData; + private Lazy pCustomData; private Action reconcile; + public object customData + { + get => wrapped.CustomData == null ? null : pCustomData.Value; + set + { + DeleteObject(); + wrapped.CustomData = JSONWrapper.castObjToJSON(value); + wrapped.RefreshCustom(); + InitWrapper(); + } + } + public object _customData { - get => wrapped.CustomData == null ? null : customData.Value; + get => wrapped.CustomData == null ? null : pCustomData.Value; set { DeleteObject(); - wrapped.CustomData = JSONWraper.castObjToJSON(value); + wrapped.CustomData = JSONWrapper.castObjToJSON(value); + wrapped.RefreshCustom(); InitWrapper(); } } @@ -25,8 +39,8 @@ protected VanillaWrapper(Engine engine, T wrapped, bool hasOriginal = true, bool private void InitWrapper() { reconcile = null; - customData = new Lazy(() => - new JSONWraper(engine, ref reconcile, wrapped.CustomData, DeleteObject) + pCustomData = new Lazy(() => + new JSONWrapper(engine, ref reconcile, wrapped.CustomData, DeleteObject) ); } @@ -38,5 +52,7 @@ internal override void Reconcile() { wrapped.CustomData = null; } + + wrapped.RefreshCustom(); } } diff --git a/Wrappers/Wall.cs b/Wrappers/Wall.cs deleted file mode 100644 index 1a0a876..0000000 --- a/Wrappers/Wall.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Jint; -using Jint.Native.Object; - -class Wall : VanillaWrapper -{ - public float _time { - get => wrapped.Time; - set { - DeleteObject(); - wrapped.Time = value; - } - } - - public int _lineIndex - { - get => wrapped.LineIndex; - set - { - DeleteObject(); - wrapped.LineIndex = value; - } - } - - public int _type - { - get => wrapped.Type; - set - { - DeleteObject(); - wrapped.Type = value; - } - } - - public float _duration - { - get => wrapped.Duration; - set - { - DeleteObject(); - wrapped.Duration = value; - } - } - - public int _width - { - get => wrapped.Width; - set - { - DeleteObject(); - wrapped.Width = value; - } - } - - public Wall(Engine engine, BeatmapObstacle wall) : base(engine, wall) - { - spawned = true; - } - - public Wall(Engine engine, ObjectInstance o) : base(engine, new BeatmapObstacle( - (float)GetJsValue(o, "_time"), - (int)GetJsValue(o, "_lineIndex"), - (int)GetJsValue(o, "_type"), - (float)GetJsValue(o, "_duration"), - (int)GetJsValue(o, "_width"), - GetCustomData(o) - ), false, GetJsBool(o, "selected")) - { - spawned = false; - - DeleteObject(); - } - - public override bool SpawnObject(BeatmapObjectContainerCollection collection) - { - if (spawned) return false; - - collection.SpawnObject(wrapped, false, false); - - spawned = true; - return true; - } - - internal override bool DeleteObject() - { - if (!spawned) return false; - - var collection = BeatmapObjectContainerCollection.GetCollectionForType(BeatmapObject.ObjectType.Obstacle); - collection.DeleteObject(wrapped, false); - - spawned = false; - return true; - } -} diff --git a/Wrappers/Wrapper.cs b/Wrappers/Wrapper.cs index 81082be..3e55459 100644 --- a/Wrappers/Wrapper.cs +++ b/Wrappers/Wrapper.cs @@ -1,8 +1,11 @@ -using Jint; +using System.Collections.Generic; +using Beatmap.Base; +using Beatmap.Helper; +using Jint; using Jint.Native.Object; using SimpleJSON; -abstract class Wrapper where T : BeatmapObject +abstract class Wrapper where T : BaseObject { protected readonly Engine engine; protected bool spawned; @@ -22,7 +25,7 @@ public Wrapper(Engine engine, T wrapped, bool hasOriginal = true, bool? selected { this.engine = engine; this.wrapped = wrapped; - if (hasOriginal) original = BeatmapObject.GenerateCopy(wrapped); + if (hasOriginal) original = BeatmapFactory.Clone(wrapped); _selected = selected.GetValueOrDefault(SelectionController.IsObjectSelected(wrapped)); } @@ -32,17 +35,58 @@ protected static double GetJsValue(ObjectInstance o, string key) return (double)value.ToObject(); } + protected static double? GetJsValue(ObjectInstance o, IEnumerable key) + { + foreach (var k in key) + { + if (o.TryGetValue(k, out var value)) + { + return (double)value.ToObject(); + } + } + + return null; + } + + protected static double? GetJsValueOptional(ObjectInstance o, string key) + { + if (o.TryGetValue(key, out var value)) + { + return (double)value.ToObject(); + } + + return null; + } + + protected static bool GetJsExist(ObjectInstance o, string key) + { + return o.TryGetValue(key, out _); + } + protected static string GetJsString(ObjectInstance o, string key) { o.TryGetValue(key, out var value); return (string)value.ToObject(); } + protected static string GetJsString(ObjectInstance o, IEnumerable key) + { + foreach (var k in key) + { + if (o.TryGetValue(k, out var value)) + { + return (string)value.ToObject(); + } + } + + return null; + } + protected static bool? GetJsBool(ObjectInstance o, string key) { if (o.TryGetValue(key, out var value)) { - return (bool) value.ToObject(); + return (bool)value.ToObject(); } return null; @@ -64,6 +108,27 @@ protected static JSONNode GetCustomData(ObjectInstance o, string key = "_customD return JSON.Parse(customData.AsString()); } + protected static JSONNode GetCustomData(ObjectInstance o, IEnumerable key) + { + foreach(var k in key) + { + var engine = new Engine(); + + var customData = engine + .SetValue("data", o) + .Evaluate($"JSON.stringify(data.{k});"); + + if (customData.IsUndefined()) + { + continue; + } + + return JSON.Parse(customData.AsString()); + } + + return null; + } + public abstract bool SpawnObject(BeatmapObjectContainerCollection collection); internal abstract bool DeleteObject(); diff --git a/app.config b/app.config new file mode 100644 index 0000000..1e70fd6 --- /dev/null +++ b/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages.config b/packages.config index 0b42177..ac6721f 100644 --- a/packages.config +++ b/packages.config @@ -1,10 +1,60 @@  - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file