diff --git a/src/Editor.cs b/src/Editor.cs index 59197af6..bc212aa8 100644 --- a/src/Editor.cs +++ b/src/Editor.cs @@ -435,7 +435,7 @@ private void Start(int frameid) Camera.SetFrameCenter(Timeline.GetFrame(frameid).CalculateCenter()); game.UpdateCursor(); - switch (Settings.PlaybackZoomType) + switch (TrackRecorder.Recording ? 0 : Settings.PlaybackZoomType) { case 0://default UseUserZoom = false; diff --git a/src/Game/GameTrigger.cs b/src/Game/GameTrigger.cs new file mode 100644 index 00000000..65573da2 --- /dev/null +++ b/src/Game/GameTrigger.cs @@ -0,0 +1,73 @@ +// Author: +// Noah Ablaseau +// +// Copyright (c) 2017 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; + +namespace linerider.Game +{ + public class GameTrigger + { + public const int TriggerTypes = 1; + public int Start; + public int End; + public TriggerType TriggerType; + public float ZoomTarget = 4; + + public bool CompareTo(GameTrigger other) + { + if (other == null) + return false; + return TriggerType == other.TriggerType && + Start == other.Start && + End == other.End && + ZoomTarget == other.ZoomTarget; + } + public bool ActivateZoom(int hitdelta, ref float currentzoom) + { + bool handled = false; + if (TriggerType == TriggerType.Zoom) + { + int zoomframes = End - Start; + if (currentzoom != ZoomTarget) + { + if (hitdelta >= 0 && hitdelta < zoomframes) + { + var diff = ZoomTarget - currentzoom; + currentzoom = currentzoom + (diff / (zoomframes - hitdelta)); + handled = true; + } + else + { + currentzoom = ZoomTarget; + } + } + } + return handled; + } + public GameTrigger Clone() + { + return new GameTrigger() + { + Start = Start, + End = End, + TriggerType = TriggerType, + ZoomTarget = ZoomTarget + }; + } + } +} \ No newline at end of file diff --git a/src/Game/HitTestManager.cs b/src/Game/HitTestManager.cs index a80e4744..765479a6 100644 --- a/src/Game/HitTestManager.cs +++ b/src/Game/HitTestManager.cs @@ -170,6 +170,13 @@ public bool IsHitBy(int id, int frame) return false; } + public bool HasUniqueCollisions(int frame) + { + if (frame >= _unique_frame_collisions.Count) + throw new IndexOutOfRangeException("Frame does not have hit test calculations"); + return _unique_frame_collisions[frame].Count != 0; + } + public void Reset() { if (_currentframe != -1) diff --git a/src/Game/Lines/LineTrigger.cs b/src/Game/Lines/LineTrigger.cs index 3311902a..c398acc9 100644 --- a/src/Game/Lines/LineTrigger.cs +++ b/src/Game/Lines/LineTrigger.cs @@ -25,6 +25,7 @@ public class LineTrigger public bool ZoomTrigger = false; public float ZoomTarget = 4; public int ZoomFrames = 40; + public int LineID = -1; public LineTrigger() { } diff --git a/src/Game/Physics/Rider.cs b/src/Game/Physics/Rider.cs index 9454d188..48e33cbe 100644 --- a/src/Game/Physics/Rider.cs +++ b/src/Game/Physics/Rider.cs @@ -172,7 +172,6 @@ private unsafe static void ProcessLines( ISimulationGrid grid, SimulationPoint[] body, ref RectLRTB physinfo, - ref int activetriggers, LinkedList collisions = null) { int bodylen = body.Length; @@ -199,16 +198,8 @@ private unsafe static void ProcessLines( if (line.Interact(ref body[i])) { collisions?.AddLast(line.ID); - if (line.Trigger != null) - { - if (activetriggers != line.ID) - { - activetriggers = line.ID; - } - } } } - } } } @@ -377,13 +368,11 @@ public static DoubleRect GetBounds(Rider r) } public Rider Simulate(Track track, int maxiteration = 6, LinkedList collisions = null) { - int trig = 0; - return Simulate(track.Grid, track.Bones, ref trig, collisions, maxiteration); + return Simulate(track.Grid, track.Bones, collisions, maxiteration); } public Rider Simulate( ISimulationGrid grid, Bone[] bones, - ref int activetriggers, LinkedList collisions, int maxiteration = 6, bool stepscarf = true, @@ -401,7 +390,7 @@ public Rider Simulate( for (int i = 0; i < maxiteration; i++) { ProcessBones(bones, body, ref dead, ref rState); - ProcessLines(grid, body, ref phys, ref activetriggers, collisions); + ProcessLines(grid, body, ref phys, collisions); } } if (maxiteration == 6) @@ -502,8 +491,7 @@ public List Diagnose( { return breaks; } - int trig = 0; - ProcessLines(grid, body, ref phys, ref trig); + ProcessLines(grid, body, ref phys); } } if (maxiteration == 6) diff --git a/src/Game/Physics/SimulationGridStatic.cs b/src/Game/Physics/SimulationGridStatic.cs index 79652d07..f6033631 100644 --- a/src/Game/Physics/SimulationGridStatic.cs +++ b/src/Game/Physics/SimulationGridStatic.cs @@ -60,7 +60,7 @@ public static List GetGridPositions(Vector2d linestart, Vector2d l var gridend = CellInfo(lineend.X, lineend.Y); ret.Add(cell); - if ((diff.X == 0 && diff.Y == 0) || (cell.X == gridend.X && cell.Y == gridend.Y)) + if (cell.X == gridend.X && cell.Y == gridend.Y) return ret; int p1X = Math.Min(cell.X, gridend.X), diff --git a/src/Game/Timeline.Engine.cs b/src/Game/Timeline.Engine.cs index 6b9278fa..600a5ea8 100644 --- a/src/Game/Timeline.Engine.cs +++ b/src/Game/Timeline.Engine.cs @@ -121,11 +121,9 @@ private bool CheckInteraction(int frame) { // even though its this frame that may need changing, we have to // regenerate it using the previos frame. - int trig = 0; var newsimulated = _frames[frame - 1].Rider.Simulate( _track.Grid, _track.Bones, - ref trig, null, 6, false); diff --git a/src/Game/Timeline.cs b/src/Game/Timeline.cs index 8817eada..41f68dd4 100644 --- a/src/Game/Timeline.cs +++ b/src/Game/Timeline.cs @@ -34,8 +34,6 @@ public partial class Timeline struct frameinfo { public Rider Rider; - public int TriggerLineID; - public int TriggerHitFrame; public float Zoom; } private HitTestManager _hittest = new HitTestManager(); @@ -95,7 +93,7 @@ public void Restart(Rider state, float zoom) { _hittest.Reset(); _frames.Clear(); - _frames.Add(new frameinfo() { Rider = state, TriggerHitFrame = -1, TriggerLineID = -1, Zoom = zoom }); + _frames.Add(new frameinfo() { Rider = state, Zoom = zoom }); using (changesync.AcquireWrite()) { _first_invalid_frame = _frames.Count; @@ -143,11 +141,9 @@ public Rider GetFrame(int frame, int iteration = 6) bool isiteration = iteration != 6 && frame > 0; if (iteration != 6 && frame > 0) { - int trig = 0; return GetFrame(frame - 1).Simulate( _track.Grid, _track.Bones, - ref trig, null, iteration, frameid: frame); @@ -193,21 +189,23 @@ public Rider[] GetFrames(int framestart, int count) return ret; } } - public void TriggerChanged(GameLine line) + public bool IsFrameUniqueCollision(int frame) { - int framehit; - using (_framesync.AcquireWrite()) - { - framehit = _hittest.GetHitFrame(line.ID); - } - if (framehit != -1) + GetFrame(frame); + return _hittest.HasUniqueCollisions(frame); + } + + public void TriggerChanged(GameTrigger oldtrigger, GameTrigger newtrigger) + { + using (changesync.AcquireWrite()) { - using (changesync.AcquireWrite()) - { - _first_invalid_frame = Math.Min(framehit, _first_invalid_frame); - } + var earliest = Math.Min(oldtrigger.Start, newtrigger.Start); + _first_invalid_frame = Math.Max( + 1, + Math.Min(earliest, _first_invalid_frame)); } } + private void UnsafeEnsureFrameValid(int frame) { int start; @@ -225,6 +223,27 @@ private void UnsafeEnsureFrameValid(int frame) ThreadUnsafeRunFrames(start, count); } } + private bool GetTriggersForFrame(GameTrigger[] triggers, int frame) + { + bool foundtrigger = false; + for (int i = 0; i < triggers.Length; i++) + { + triggers[i] = null; + } + foreach (var trigger in _track.Triggers) + { + if (trigger.Start <= frame && trigger.End >= frame) + { + int index = (int)trigger.TriggerType; + var prev = triggers[index]; + if (!(prev?.Start > trigger.Start)) + triggers[index] = trigger; + + foundtrigger = true; + } + } + return foundtrigger; + } /// /// The meat of the recompute engine, updating hit test and the /// cached frames. @@ -249,31 +268,23 @@ private void ThreadUnsafeRunFrames(int start, int count) using (var sync = changesync.AcquireRead()) { _hittest.MarkFirstInvalid(start); + GameTrigger[] triggers = new GameTrigger[GameTrigger.TriggerTypes]; for (int i = 0; i < count; i++) { int currentframe = start + i; var collisions = new LinkedList(); - var oldtrigid = current.TriggerLineID; - var zoom = current.Rider = current.Rider.Simulate( _savedcells, bones, - ref current.TriggerLineID, collisions, frameid: currentframe); - if (current.TriggerLineID != oldtrigid) - { - current.TriggerHitFrame = currentframe; - } - if (current.TriggerLineID != -1) + if (GetTriggersForFrame(triggers, currentframe)) { - var std = (StandardLine)_track.LineLookup[current.TriggerLineID]; - Debug.Assert(std.Trigger != null, "Trigger line proc on line with no trigger"); - var delta = currentframe - current.TriggerHitFrame; - if (!std.Trigger.Activate(delta, ref current.Zoom)) + var zoomtrigger = triggers[(int)TriggerType.Zoom]; + if (zoomtrigger != null) { - current.TriggerLineID = -1; - current.TriggerHitFrame = -1; + var delta = currentframe - zoomtrigger.Start; + zoomtrigger.ActivateZoom(delta, ref current.Zoom); } } steps[i] = current; diff --git a/src/Game/Track.cs b/src/Game/Track.cs index 7c902f8f..8f52b0aa 100644 --- a/src/Game/Track.cs +++ b/src/Game/Track.cs @@ -35,6 +35,7 @@ public class Track public SimulationGrid Grid = new SimulationGrid(); public LinkedList Lines = new LinkedList(); public Dictionary LineLookup = new Dictionary(); + public List Triggers = new List(); public string Name = Constants.DefaultTrackName; public string Filename = null; diff --git a/src/Game/TrackReader.cs b/src/Game/TrackReader.cs index 25ddcaf2..b889e7d4 100644 --- a/src/Game/TrackReader.cs +++ b/src/Game/TrackReader.cs @@ -115,8 +115,7 @@ public IEnumerable GetLinesInRect(DoubleRect rect, bool precise) /// public Rider TickBasic(Rider state, int maxiteration = 6) { - int trig = 0; - return state.Simulate(_track.Grid, _track.Bones, ref trig, null, maxiteration); + return state.Simulate(_track.Grid, _track.Bones, null, maxiteration); } public string SaveTrackTrk(string savename) { diff --git a/src/Game/TrackWriter.cs b/src/Game/TrackWriter.cs index 62278d33..92e98c7c 100644 --- a/src/Game/TrackWriter.cs +++ b/src/Game/TrackWriter.cs @@ -68,6 +68,21 @@ public EditorGrid Cells return _editorcells; } } + public List Triggers + { + get + { + if (_disposed) + throw new ObjectDisposedException("TrackWriter"); + return _track.Triggers; + } + set + { + if (_disposed) + throw new ObjectDisposedException("TrackWriter"); + _track.Triggers = value; + } + } protected TrackWriter(ResourceSync.ResourceLock sync, Track track) : base(sync, track) { @@ -174,7 +189,6 @@ public void ReplaceLine(GameLine oldline, GameLine newline) RegisterUndoAction(oldline, newline); var std = oldline as StandardLine; - bool triggerchange = false; if (std != null) { SaveCells(oldline.Position, oldline.Position2); @@ -190,21 +204,12 @@ public void ReplaceLine(GameLine oldline, GameLine newline) } if (_updateextensions) AddExtensions(newstd); - if (std.Trigger != null || newstd.Trigger != null) - { - if (std.Trigger == null) - triggerchange = true; - else if (!std.Trigger.CompareTo(newstd.Trigger)) - triggerchange = true; - } } _editorcells.RemoveLine(oldline); _editorcells.AddLine(newline); Track.LineLookup[newline.ID] = newline; - if (triggerchange) - _timeline.TriggerChanged(newline); _renderer.RedrawLine(newline); } diff --git a/src/Game/TriggerType.cs b/src/Game/TriggerType.cs new file mode 100644 index 00000000..a4b3da9b --- /dev/null +++ b/src/Game/TriggerType.cs @@ -0,0 +1,27 @@ +// Author: +// Noah Ablaseau +// +// Copyright (c) 2017 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; + +namespace linerider.Game +{ + public enum TriggerType + { + Zoom = 0, + } +} \ No newline at end of file diff --git a/src/GameCanvas.cs b/src/GameCanvas.cs index aa15e650..16bd728d 100644 --- a/src/GameCanvas.cs +++ b/src/GameCanvas.cs @@ -323,6 +323,10 @@ public void ShowTrackPropertiesDialog() { ShowDialog(new TrackInfoWindow(this, game.Track)); } + public void ShowTriggerWindow() + { + ShowDialog(new TriggerWindow(this, game.Track)); + } public void ShowExportVideoWindow() { if (File.Exists(linerider.IO.ffmpeg.FFMPEG.ffmpeg_path)) diff --git a/src/IO/TRKLoader.cs b/src/IO/TRKLoader.cs index bd442c68..34c8b4dc 100644 --- a/src/IO/TRKLoader.cs +++ b/src/IO/TRKLoader.cs @@ -27,6 +27,79 @@ public static class TRKLoader private const int SONGINFO_INDEX = 3; private const int IGNORABLE_TRIGGER_INDEX = 4; private const int ZEROSTART_INDEX = 5; + private static float ParseFloat(string f) + { + if (!float.TryParse( + f, + NumberStyles.Float, + Program.Culture, + out float ret)) + throw new TrackIO.TrackLoadException( + "Unable to parse string into float"); + return ret; + } + private static int ParseInt(string f) + { + if (!int.TryParse( + f, + NumberStyles.Float, + Program.Culture, + out int ret)) + throw new TrackIO.TrackLoadException( + "Unable to parse string into int"); + return ret; + } + private static void ParseMetadata(Track ret, BinaryReader br) + { + var count = br.ReadInt16(); + for (int i = 0; i < count; i++) + { + var metadata = ReadString(br).Split('='); + switch (metadata[0]) + { + case TrackMetadata.startzoom: + ret.StartZoom = ParseFloat(metadata[1]); + break; + case TrackMetadata.triggers: + string[] triggers = metadata[1].Split('&'); + foreach (var t in triggers) + { + string[] tdata = t.Split(':'); + TriggerType ttype; + try + { + ttype = (TriggerType)int.Parse(tdata[0]); + } + catch + { + throw new TrackIO.TrackLoadException( + "Unsupported trigger type"); + } + GameTrigger newtrigger; + switch (ttype) + { + case TriggerType.Zoom: + var target = ParseFloat(tdata[1]); + var start = ParseInt(tdata[2]); + var end = ParseInt(tdata[3]); + newtrigger = new GameTrigger() + { + Start = start, + End = end, + TriggerType = TriggerType.Zoom, + ZoomTarget = target, + }; + break; + default: + throw new TrackIO.TrackLoadException( + "Unsupported trigger type"); + } + ret.Triggers.Add(newtrigger); + } + break; + } + } + } public static Track LoadTrack(string trackfile, string trackname) { var ret = new Track(); @@ -125,6 +198,7 @@ public static Track LoadTrack(string trackfile, string trackname) } ret.StartOffset = new Vector2d(br.ReadDouble(), br.ReadDouble()); var lines = br.ReadInt32(); + List linetriggers = new List(); for (var i = 0; i < lines; i++) { GameLine l; @@ -137,7 +211,7 @@ public static Track LoadTrack(string trackfile, string trackname) var nxtID = -1; var multiplier = 1; var linewidth = 1f; - LineTrigger tr = ignorabletrigger ? new LineTrigger() : null; + LineTrigger tr = null; if (redmultipier) { if (lt == LineType.Red) @@ -149,6 +223,7 @@ public static Track LoadTrack(string trackfile, string trackname) { if (ignorabletrigger) { + tr = new LineTrigger(); bool zoomtrigger = br.ReadBoolean(); if (zoomtrigger) { @@ -182,6 +257,12 @@ public static Track LoadTrack(string trackfile, string trackname) var y1 = br.ReadDouble(); var x2 = br.ReadDouble(); var y2 = br.ReadDouble(); + + if (tr != null) + { + tr.LineID = ID; + linetriggers.Add(tr); + } switch (lt) { case LineType.Blue: @@ -189,7 +270,6 @@ public static Track LoadTrack(string trackfile, string trackname) bl.ID = ID; bl.Extension = (StandardLine.Ext)lim; l = bl; - bl.Trigger = tr; break; case LineType.Red: @@ -201,7 +281,6 @@ public static Track LoadTrack(string trackfile, string trackname) rl.Multiplier = multiplier; } l = rl; - rl.Trigger = tr; break; case LineType.Scenery: @@ -225,24 +304,13 @@ public static Track LoadTrack(string trackfile, string trackname) ret.AddLine(l); } } + ret.Triggers = TriggerConverter.ConvertTriggers(linetriggers, ret); if (br.BaseStream.Position != br.BaseStream.Length) { var meta = br.ReadInt32(); if (meta == ('M' | 'E' << 8 | 'T' << 16 | 'A' << 24)) { - var count = br.ReadInt16(); - for (int i = 0; i < count; i++) - { - var metadata = ReadString(br).Split('='); - switch (metadata[0]) - { - case TrackMetadata.startzoom: - if (!float.TryParse(metadata[1], NumberStyles.Float, Program.Culture, out float startzoom)) - throw new TrackIO.TrackLoadException("Startzoom property was invalid"); - ret.StartZoom = startzoom; - break; - } - } + ParseMetadata(ret, br); } else { diff --git a/src/IO/TRKWriter.cs b/src/IO/TRKWriter.cs index 7caf5936..28889402 100644 --- a/src/IO/TRKWriter.cs +++ b/src/IO/TRKWriter.cs @@ -121,8 +121,27 @@ public static string SaveTrack(Track trk, string savename) } bw.Write(new byte[] { (byte)'M', (byte)'E', (byte)'T', (byte)'A' }); List metadata = new List(); - metadata.Add("STARTZOOM=" + trk.StartZoom.ToString(Program.Culture)); + metadata.Add(TrackMetadata.startzoom + "=" + trk.StartZoom.ToString(Program.Culture)); + StringBuilder triggerstring = new StringBuilder(); + for (int i = 0; i < trk.Triggers.Count; i++) + { + GameTrigger t = trk.Triggers[i]; + if (i != 0) + triggerstring.Append("&"); + triggerstring.Append((int)TriggerType.Zoom); + triggerstring.Append(":"); + if (t.TriggerType == TriggerType.Zoom) + { + triggerstring.Append(t.ZoomTarget.ToString(Program.Culture)); + triggerstring.Append(":"); + } + triggerstring.Append(t.Start.ToString(Program.Culture)); + triggerstring.Append(":"); + triggerstring.Append(t.End.ToString(Program.Culture)); + } + metadata.Add(TrackMetadata.triggers + "=" + triggerstring.ToString()); + bw.Write((short)metadata.Count); bw.Write((short)metadata.Count); foreach (var str in metadata) { diff --git a/src/IO/TrackIO.cs b/src/IO/TrackIO.cs index abd2e2cc..75a47f1f 100644 --- a/src/IO/TrackIO.cs +++ b/src/IO/TrackIO.cs @@ -26,6 +26,8 @@ using linerider.Audio; using linerider.Utils; using linerider.Game; +using linerider.IO.json; +using Utf8Json; namespace linerider.IO { @@ -44,7 +46,7 @@ public static string[] EnumerateTrackFiles(string folder) return Directory.GetFiles(folder, "*.*") .Where(x => x.EndsWith(".trk", StringComparison.OrdinalIgnoreCase) || - x.EndsWith(".json",StringComparison.OrdinalIgnoreCase)). + x.EndsWith(".json", StringComparison.OrdinalIgnoreCase)). OrderByDescending(x => { var fn = Path.GetFileName(x); @@ -236,6 +238,44 @@ public static string SaveToSOL(Track track, string savename) track.Filename = filename; return filename; } + public static void ExportTrackData(Track track, string fn, int frames, ICamera camera) + { + var timeline = new linerider.Game.Timeline( + track); + timeline.Restart(track.GetStart(), 1); + timeline.GetFrame(frames); + camera.SetTimeline(timeline); + List data = new List(); + for (int idx = 0; idx < frames; idx++) + { + var frame = timeline.GetFrame(idx); + var framedata = new RiderData(); + framedata.Frame = idx; + framedata.Points = new List(); + for (int i = 0; i < frame.Body.Length; i++) + { + var point = frame.Body[i]; + framedata.Points.Add(new track_json.point_json() + { + x = point.Location.X, + y = point.Location.Y + }); + var camframe = camera.GetFrameCamera(idx); + framedata.CameraCenter = new track_json.point_json() + { + x = camframe.X, + y = camframe.Y + }; + } + data.Add(framedata); + } + + using (var file = File.Create(fn)) + { + var bytes = JsonSerializer.Serialize>(data); + file.Write(bytes, 0, bytes.Length); + } + } public static string SaveTrackToFile(Track track, string savename) { int saveindex = GetSaveIndex(track); diff --git a/src/IO/TrackMetadata.cs b/src/IO/TrackMetadata.cs index d4df6f4b..35b46f7c 100644 --- a/src/IO/TrackMetadata.cs +++ b/src/IO/TrackMetadata.cs @@ -7,5 +7,9 @@ public static class TrackMetadata /// string=float /// public const string startzoom = "STARTZOOM"; + /// + /// + /// + public const string triggers = "TRIGGERS"; } } \ No newline at end of file diff --git a/src/IO/TrackRecorder.cs b/src/IO/TrackRecorder.cs index 7b342e32..e7b4b1f0 100644 --- a/src/IO/TrackRecorder.cs +++ b/src/IO/TrackRecorder.cs @@ -82,11 +82,11 @@ public static void RecordTrack(MainWindow game, bool is1080P, bool smooth, bool using (var trk = game.Track.CreateTrackReader()) { game.Track.Reset(); + Recording = true; + Recording1080p = is1080P; var state = game.Track.GetStart(); var frame = flag.FrameID; - Recording = true; - Recording1080p = is1080P; game.Canvas.SetCanvasSize(game.RenderSize.Width, game.RenderSize.Height); game.Canvas.Layout(); diff --git a/src/IO/TriggerConverter.cs b/src/IO/TriggerConverter.cs new file mode 100644 index 00000000..43a14300 --- /dev/null +++ b/src/IO/TriggerConverter.cs @@ -0,0 +1,85 @@ +using OpenTK; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using linerider.Audio; +using linerider.Game; +using System.Diagnostics; + +namespace linerider.IO +{ + /// + /// Static class that converts line based triggers to a timeline--based + /// trigger system. + /// + public static class TriggerConverter + { + public static List ConvertTriggers(List triggers, Track track) + { + List gametriggers = new List(); + const int minute = 40 * 60; + int lasthit = 0; + var rider = track.GetStart(); + var hittest = new HitTestManager(); + int i = 1; + int hitframe = -1; + LineTrigger activetrigger = null; + float zoom = track.StartZoom; + GameTrigger newtrigger = null; + do + { + var collisions = new LinkedList(); + rider = rider.Simulate( + track.Grid, + track.Bones, + collisions); + hittest.AddFrame(collisions); + LineTrigger hittrigger = null; + foreach (var lineid in collisions) + { + foreach (var trigger in triggers) + { + if (trigger.LineID == lineid) + { + hittrigger = trigger; + } + } + } + if (hittrigger != null && + hittrigger != activetrigger) + { + if (activetrigger != null) + { + newtrigger.ZoomTarget = zoom; + newtrigger.End = i; + gametriggers.Add(newtrigger); + } + hitframe = i; + activetrigger = hittrigger; + newtrigger = new GameTrigger() { TriggerType = TriggerType.Zoom, Start = i }; + } + if (activetrigger != null) + { + var delta = i - hitframe; + if (!activetrigger.Activate(delta, ref zoom)) + { + newtrigger.ZoomTarget = zoom; + newtrigger.End = i; + gametriggers.Add(newtrigger); + activetrigger = null; + } + } + if (hittest.HasUniqueCollisions(i)) + { + lasthit = i; + } + i++; + } + while (i - lasthit < (minute * 2)); // be REALLY sure, 2 minutes. + return gametriggers; + } + } +} \ No newline at end of file diff --git a/src/IO/json/JSONLoader.cs b/src/IO/json/JSONLoader.cs index 37975470..5af7caa3 100644 --- a/src/IO/json/JSONLoader.cs +++ b/src/IO/json/JSONLoader.cs @@ -73,6 +73,7 @@ public static Track LoadTrack(string trackfile) } if (trackobj.triggers != null) { + List linetriggers = new List(); foreach (var trigger in trackobj.triggers) { if (ret.LineLookup.TryGetValue(trigger.ID, out GameLine line)) @@ -81,19 +82,48 @@ public static Track LoadTrack(string trackfile) { if (trigger.zoom) { - stl.Trigger = new LineTrigger() + linetriggers.Add(new LineTrigger() { ZoomTrigger = trigger.zoom, ZoomTarget = (float)MathHelper.Clamp( trigger.target, Utils.Constants.MinimumZoom, Utils.Constants.MaxZoom), - ZoomFrames = trigger.frames - }; + ZoomFrames = trigger.frames, + LineID = trigger.ID + }); } } } } + ret.Triggers = TriggerConverter.ConvertTriggers(linetriggers, ret); + + } + if (trackobj.gameTriggers != null) + { + foreach (var t in trackobj.gameTriggers) + { + if (t.start < 1 || t.end < 1 || t.end < t.start) + throw new TrackIO.TrackLoadException( + "Trigger timing was outside of range"); + TriggerType ttype; + try + { + ttype = (TriggerType)t.triggerType; + } + catch + { + throw new TrackIO.TrackLoadException( + "Unsupported trigger type " + t.triggerType); + } + ret.Triggers.Add(new GameTrigger() + { + Start = t.start, + End = t.end, + TriggerType = ttype, + ZoomTarget = t.zoomTarget, + }); + } } return ret; } @@ -159,7 +189,11 @@ private static void AddLine(Track track, line_json line) add.Extension |= StandardLine.Ext.Left; if (Convert.ToBoolean(line.rightExtended)) add.Extension |= StandardLine.Ext.Right; - track.AddLine(add); + if (line.multiplier > 1) + { + add.Multiplier = line.multiplier; + } + track.AddLine(add); break; } case 2: diff --git a/src/IO/json/JSONWriter.cs b/src/IO/json/JSONWriter.cs index 57022ed1..168aa7a9 100644 --- a/src/IO/json/JSONWriter.cs +++ b/src/IO/json/JSONWriter.cs @@ -36,7 +36,7 @@ public static string SaveTrack(Track trk, string savename) } var sort = trk.GetSortedLines(); trackobj.linesArray = new object[sort.Length][]; - trackobj.triggers = new List(); + trackobj.gameTriggers = new List(); int idx = 0; foreach (var line in sort) { @@ -70,19 +70,19 @@ public static string SaveTrack(Track trk, string savename) { jline.multiplier = red.Multiplier; } - if (stl.Trigger != null && stl.Trigger.ZoomTrigger) - { - trackobj.triggers.Add(new track_json.zoomtrigger_json() - { - zoom = true, - ID = jline.id, - target = stl.Trigger.ZoomTarget, - frames = stl.Trigger.ZoomFrames - }); - } } trackobj.linesArray[idx++] = line_to_linearrayline(jline); } + foreach (var trigger in trk.Triggers) + { + trackobj.gameTriggers.Add(new track_json.gametrigger_json() + { + triggerType = (int)trigger.TriggerType, + zoomTarget = trigger.ZoomTarget, + start = trigger.Start, + end = trigger.End + }); + } var dir = TrackIO.GetTrackDirectory(trk); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); diff --git a/src/IO/json/RiderData.cs b/src/IO/json/RiderData.cs new file mode 100644 index 00000000..1f1dde37 --- /dev/null +++ b/src/IO/json/RiderData.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +namespace linerider.IO.json +{ + public struct RiderData + { + public int Frame; + public List Points; + public track_json.point_json CameraCenter; + } +} \ No newline at end of file diff --git a/src/IO/json/track_json.cs b/src/IO/json/track_json.cs index 433fc50e..8df987aa 100644 --- a/src/IO/json/track_json.cs +++ b/src/IO/json/track_json.cs @@ -11,6 +11,13 @@ public class zoomtrigger_json public float target; public int frames; } + public class gametrigger_json + { + public int start; + public int end; + public int triggerType; + public float zoomTarget = 4; + } public class point_json { public double x; @@ -28,6 +35,8 @@ public class point_json public string linesArrayCompressed { get; set; } public object[][] linesArray { get; set; } public List triggers { get; set; } + public List gameTriggers { get; set; } + public bool ShouldSerializezeroStart() { diff --git a/src/MainWindow.cs b/src/MainWindow.cs index c70d7b5c..5f86568d 100644 --- a/src/MainWindow.cs +++ b/src/MainWindow.cs @@ -113,6 +113,7 @@ public MainWindow() WindowBorder = WindowBorder.Resizable; RenderFrame += (o, e) => { Render(); }; UpdateFrame += (o, e) => { GameUpdate(); }; + new Thread(AutosaveThreadRunner) { IsBackground = true, Name = "Autosave" }.Start(); GameService.Initialize(this); RegisterHotkeys(); } @@ -212,6 +213,24 @@ private void GameUpdateHandleInput() CurrentTools.SelectedTool.OnMouseMoved(new Vector2d(x, y)); } } + /// + /// Indefinitely run the autosave function + /// + private void AutosaveThreadRunner() + { + while (true) + { + Thread.Sleep(1000 * 60 * Settings.autosaveMinutes); // Settings.autosaveMinutes minutes + try + { + Track.BackupTrack(false); + } + catch + { + // do nothing + } + } + } public void GameUpdate() { //TODO: Put these not in the main loop and put them in reasonable places @@ -299,11 +318,6 @@ public void GameUpdate() Track.ZoomBy(-0.08f); } } - if (_autosavewatch.Elapsed.TotalMinutes >= Settings.autosaveMinutes) - { - _autosavewatch.Restart(); - new Thread(() => { Track.BackupTrack(false); }).Start(); - } if (Track.Playing) diff --git a/src/Program.cs b/src/Program.cs index 231bb310..dd5f5c68 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -67,6 +67,13 @@ public static string UserDirectory _userdir = documents; } } + // linux support: some users use XDG_DOCUMENTS_DIR to change + // their Documents directory location. + var xdg_dir = Environment.GetEnvironmentVariable("XDG_DOCUMENTS_DIR"); + if (!string.IsNullOrEmpty(xdg_dir) && Directory.Exists(xdg_dir)) + { + _userdir = xdg_dir; + } _userdir += Path.DirectorySeparatorChar + "LRA" + Path.DirectorySeparatorChar; } return _userdir; diff --git a/src/Tools/MoveTool.cs b/src/Tools/MoveTool.cs index d296dac6..ddba9710 100644 --- a/src/Tools/MoveTool.cs +++ b/src/Tools/MoveTool.cs @@ -280,7 +280,7 @@ public override void OnMouseRightDown(Vector2d pos) using (var trk = game.Track.CreateTrackWriter()) { var line = SelectLine(trk, gamepos, out bool knob); - if (line != null && line.Type != LineType.Scenery) + if (line != null && line.Type == LineType.Red) { game.Canvas.ShowLineWindow(line, (int)pos.X, (int)pos.Y); } diff --git a/src/Tools/SelectTool.cs b/src/Tools/SelectTool.cs index ca2cd051..fcb82851 100644 --- a/src/Tools/SelectTool.cs +++ b/src/Tools/SelectTool.cs @@ -9,6 +9,7 @@ using linerider.Rendering; using linerider.Drawing; using System.Drawing; +using linerider.UI; namespace linerider.Tools { @@ -124,8 +125,13 @@ public override void OnMouseDown(Vector2d pos) { return; } - - if (UI.InputUtils.CheckPressed(UI.Hotkey.ToolAddSelection)) + var axis = UI.InputUtils.CheckPressed(Hotkey.ToolAxisLock); + var perpendicularaxis = UI.InputUtils.CheckPressed(Hotkey.ToolPerpendicularAxisLock); + if ((axis || perpendicularaxis) && StartMoveSelection(gamepos)) + { + return; + } + else if (UI.InputUtils.CheckPressed(UI.Hotkey.ToolAddSelection)) { StartAddSelection(gamepos); return; @@ -135,7 +141,7 @@ public override void OnMouseDown(Vector2d pos) ToggleSelection(gamepos); return; } - else if (StartMoveSelection(gamepos)) + else if (!(axis || perpendicularaxis) && StartMoveSelection(gamepos)) { return; } @@ -723,6 +729,24 @@ private void RotateSelection(Vector2d pos) } game.Invalidate(); } + + private bool ApplyModifiers(Vector2d joint1, ref Vector2d joint2) + { + var axis = UI.InputUtils.CheckPressed(Hotkey.ToolAxisLock); + var perpendicularaxis = UI.InputUtils.CheckPressed(Hotkey.ToolPerpendicularAxisLock); + var modified = false; + if (axis || perpendicularaxis) + { + var angle = Angle.FromVector(_snapline.GetVector()); + if (perpendicularaxis) + { + angle.Degrees -= 90; + } + joint2 = Utility.AngleLock(joint1, joint2, angle); + modified = true; + } + return modified; + } private void MoveSelection(Vector2d pos) { if (_selection.Count > 0) @@ -731,7 +755,12 @@ private void MoveSelection(Vector2d pos) var movediff = (pos - _clickstart); using (var trk = game.Track.CreateTrackWriter()) { - if (_snapline != null && EnableSnap) + var pos2 = pos; + if (ApplyModifiers(_clickstart, ref pos2)) + { + movediff = (pos2 - _clickstart); + } + else if (_snapline != null && EnableSnap) { movediff += GetSnapOffset(movediff, trk); } diff --git a/src/UI/Dialogs/LineWindow.cs b/src/UI/Dialogs/LineWindow.cs index 7660197b..fe3f554f 100644 --- a/src/UI/Dialogs/LineWindow.cs +++ b/src/UI/Dialogs/LineWindow.cs @@ -92,7 +92,6 @@ private void WarnClose() private void Setup() { SetupRedOptions(_proptree); - SetupTriggers(_proptree); Panel bottom = new Panel(this) { Dock = Dock.Bottom, @@ -181,89 +180,6 @@ private void SetupRedOptions(PropertyTree tree) }; } } - private void SetupTriggers(PropertyTree tree) - { - if (_ownerline is StandardLine physline) - { - var table = tree.Add("Triggers", 120); - var currenttrigger = physline.Trigger; - var triggerenabled = GwenHelper.AddPropertyCheckbox( - table, - "Enabled", - currenttrigger != null); - - var zoom = new NumberProperty(table) - { - Min = Constants.MinimumZoom, - Max = Constants.MaxZoom, - NumberValue = 4 - }; - table.Add("Target Zoom", zoom); - var frames = new NumberProperty(table) - { - Min = 0, - Max = 40 * 60 * 2,//2 minutes is enough for a zoom trigger, you crazy nuts. - NumberValue = 40, - OnlyWholeNumbers = true, - }; - if (currenttrigger != null) - { - zoom.NumberValue = currenttrigger.ZoomTarget; - frames.NumberValue = currenttrigger.ZoomFrames; - } - table.Add("Frames", frames); - zoom.ValueChanged += (o, e) => - { - using (var trk = _editor.CreateTrackWriter()) - { - trk.DisableExtensionUpdating(); - if (triggerenabled.IsChecked) - { - var cpy = (StandardLine)_ownerline.Clone(); - cpy.Trigger.ZoomTarget = (float)zoom.NumberValue; - UpdateOwnerLine(trk, cpy); - } - } - }; - frames.ValueChanged += (o, e) => - { - using (var trk = _editor.CreateTrackWriter()) - { - trk.DisableExtensionUpdating(); - if (triggerenabled.IsChecked) - { - var cpy = (StandardLine)_ownerline.Clone(); - cpy.Trigger.ZoomFrames = (int)frames.NumberValue; - UpdateOwnerLine(trk, cpy); - } - } - }; - triggerenabled.ValueChanged += (o, e) => - { - using (var trk = _editor.CreateTrackWriter()) - { - trk.DisableExtensionUpdating(); - var cpy = (StandardLine)_ownerline.Clone(); - if (triggerenabled.IsChecked) - { - cpy.Trigger = new LineTrigger() - { - ZoomTrigger = true, - ZoomFrames = (int)frames.NumberValue, - ZoomTarget = (float)zoom.NumberValue - }; - - UpdateOwnerLine(trk, cpy); - } - else - { - cpy.Trigger = null; - UpdateOwnerLine(trk, cpy); - } - } - }; - } - } private void UpdateOwnerLine(TrackWriter trk, GameLine replacement) { diff --git a/src/UI/Dialogs/TriggerWindow.cs b/src/UI/Dialogs/TriggerWindow.cs new file mode 100644 index 00000000..b32e65c4 --- /dev/null +++ b/src/UI/Dialogs/TriggerWindow.cs @@ -0,0 +1,516 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using Gwen; +using Gwen.Controls; +using linerider.Tools; +using linerider.Utils; +using linerider.IO; +using linerider.Game; +using System.Diagnostics; + +namespace linerider.UI +{ + public class TriggerWindow : DialogBase + { + private const int FramePadding = 0; + private HorizontalSlider _slider; + private ListBox _lbtriggers; + private ControlBase _zoomoptions; + + private Spinner _spinnerStart; + private Spinner _spinnerDuration; + private ComboBox _triggertype; + private Spinner _zoomtarget; + private int SliderFrames + { + get + { + return TriggerDuration + (FramePadding * 2); + } + } + private int TriggerDuration + { + get + { + return (int)(_spinnerDuration.Value); + } + } + private int SelectedTrigger + { + get + { + return _lbtriggers.SelectedRowIndex; + } + } + private List _triggers_copy = new List(); + private GameTrigger _trigger_copy = null; + private bool _changemade = false; + private bool _closing = false; + public TriggerWindow(GameCanvas parent, Editor editor) + : base(parent, editor) + { + Title = "Timeline Triggers"; + Size = new Size(400, 340); + Padding = new Padding(0, 0, 0, 0); + MakeModal(true); + using (var trk = editor.CreateTrackWriter()) + { + foreach (var trigger in trk.Triggers) + { + _triggers_copy.Add(trigger.Clone()); + } + } + Setup(); + ToggleDisable(true); + MinimumSize = new Size(250, MinimumSize.Height); + DisableResizing(); + + } + protected override void CloseButtonPressed( + ControlBase control, + EventArgs args) + { + if (_closing || !_changemade) + { + _closing = true; + base.CloseButtonPressed(control, args); + } + else + { + WarnClose(); + } + } + public override bool Close() + { + if (_closing || !_changemade) + { + _closing = true; + return base.Close(); + } + else + { + WarnClose(); + return false; + } + } + private void WarnClose() + { + var mbox = MessageBox.Show( + _canvas, + "The line has been modified. Do you want to save your changes?", + "Save Changes?", + MessageBox.ButtonType.YesNoCancel); + mbox.RenameButtonsYN("Save", "Discard", "Cancel"); + mbox.MakeModal(false); + mbox.Dismissed += (o, e) => + { + switch (e) + { + case DialogResult.Yes: + FinishChange(); + _closing = true; + base.Close(); + break; + case DialogResult.No: + CancelChange(); + _closing = true; + base.Close(); + break; + } + }; + } + private GameTrigger BeginModifyTrigger(TrackWriter trk) + { + var selected = SelectedTrigger; + if (selected == -1) + return null; + var trigger = trk.Triggers[selected]; + _trigger_copy = trigger.Clone(); + return trigger; + } + private void EndModifyTrigger(GameTrigger trigger, TrackWriter trk) + { + var selected = SelectedTrigger; + if (selected == -1) + throw new Exception( + "SelectedTrigger was removed during ModifyTrigger"); + + _changemade = true; + trigger.Start = (int)(_spinnerStart.Value); + trigger.End = (int)(_spinnerStart.Value + _spinnerDuration.Value); + trk.Triggers[SelectedTrigger] = trigger; + _editor.Timeline.TriggerChanged(_trigger_copy, trigger); + UpdateFrame(); + } + private void SetupZoom() + { + _zoomoptions = new ControlBase(null) + { + Margin = new Margin(0, 0, 0, 0), + Dock = Dock.Fill + }; + _zoomtarget = new Spinner(null) + { + Min = Constants.MinimumZoom, + Max = Constants.MaxZoom, + Value = _editor.Zoom, + }; + _zoomtarget.ValueChanged += (o, e) => + { + if (_selecting_trigger) + return; + using (var trk = _editor.CreateTrackWriter()) + { + var trigger = BeginModifyTrigger(trk); + if (trigger != null) + { + trigger.ZoomTarget = (float)_zoomtarget.Value; + EndModifyTrigger(trigger, trk); + } + } + }; + GwenHelper.CreateLabeledControl( + _zoomoptions, "Zoom Target:", _zoomtarget).Dock = Dock.Bottom; + } + private void SetupRight() + { + SetupZoom(); + ControlBase rightcontainer = new ControlBase(this) + { + Margin = new Margin(0, 0, 0, 0), + Dock = Dock.Right, + AutoSizeToContents = true + }; + ControlBase buttoncontainer = new ControlBase(rightcontainer) + { + Margin = new Margin(0, 0, 0, 0), + Dock = Dock.Bottom, + AutoSizeToContents = true, + Children = + { + new Button(null) + { + Text = "Cancel", + Name = "btncancel", + Dock = Dock.Right, + Margin = new Margin(5,0,0,0), + AutoSizeToContents = false, + Width = 60, + }, + new Button(null) + { + Text = "Save", + Name = "btnsave", + Dock = Dock.Right, + AutoSizeToContents = false, + Width = 120, + } + } + }; + var save = buttoncontainer.FindChildByName("btnsave"); + var cancel = buttoncontainer.FindChildByName("btncancel"); + save.Clicked += (o, e) => + { + FinishChange(); + Close(); + }; + cancel.Clicked += (o, e) => + { + CancelChange(); + Close(); + }; + Panel triggeroptions = new Panel(rightcontainer) + { + Dock = Dock.Fill, + Padding = Padding.Four, + Margin = new Margin(0, 0, 0, 5) + }; + + _zoomoptions.Parent = triggeroptions; + _triggertype = GwenHelper.CreateLabeledCombobox( + rightcontainer, "Trigger Type:"); + _triggertype.Dock = Dock.Bottom; + + var zoom = _triggertype.AddItem("Zoom", "", TriggerType.Zoom); + zoom.Selected += (o, e) => + { + triggeroptions.Children.Clear(); + _zoomoptions.Parent = triggeroptions; + }; + _triggertype.SelectedItem = zoom; + } + private void SetupLeft() + { + ControlBase leftcontainer = new ControlBase(this) + { + Margin = new Margin(0, 0, 0, 0), + Dock = Dock.Left, + AutoSizeToContents = true, + }; + var panel = new ControlBase(leftcontainer); + panel.Width = 150; + panel.Height = 200; + ControlBase topcontainer = new ControlBase(panel) + { + Margin = new Margin(0, 3, 0, 3), + Padding = Padding.Five, + Dock = Dock.Top, + + Children = + { + new Button(null) + { + Text = "-", + Name = "btndelete", + Dock = Dock.Right, + Margin = new Margin(2,0,2,0), + Height = 16, + Width = 16, + AutoSizeToContents = false + }, + new Button(null) + { + Text = "+", + Name = "btnadd", + Dock = Dock.Right, + Margin = new Margin(2,0,2,0), + Width = 16, + Height = 16, + AutoSizeToContents = false + } + }, + AutoSizeToContents = true, + + }; + var add = topcontainer.FindChildByName("btnadd"); + var delete = topcontainer.FindChildByName("btndelete"); + add.Clicked += (o, e) => + { + var trigger = new GameTrigger() + { + TriggerType = TriggerType.Zoom, + Start = _editor.Offset, + End = _editor.Offset + 40, + ZoomTarget = 4, + }; + + _changemade = true; + using (var trk = _editor.CreateTrackWriter()) + { + trk.Triggers.Add(trigger); + _editor.Timeline.TriggerChanged(trigger, trigger); + UpdateFrame(); + } + ToggleDisable(false); + _lbtriggers.AddRow(GetTriggerLabel(trigger), "", trigger); + _lbtriggers.SelectByUserData(trigger); + }; + delete.Clicked += (o, e) => + { + var row = _lbtriggers.SelectedRow; + if (row != null) + { + var trigger = (GameTrigger)(row.UserData); + _changemade = true; + using (var trk = _editor.CreateTrackWriter()) + { + trk.Triggers.RemoveAt(SelectedTrigger); + _editor.Timeline.TriggerChanged(trigger, trigger); + UpdateFrame(); + } + _lbtriggers.Children.Remove(row); + ToggleDisable(true); + } + }; + _lbtriggers = new ListBox(panel) + { + Dock = Dock.Fill, + Margin = new Margin(0, 0, 0, 5) + }; + ControlBase spinnerContainer = new ControlBase(leftcontainer) + { + Margin = new Margin(0, 0, 0, 0), + Padding = new Padding(0, 0, 50, 0), + Dock = Dock.Bottom, + AutoSizeToContents = true + }; + _spinnerStart = new Spinner(null) + { + OnlyWholeNumbers = true, + Min = 1, + Max = Constants.MaxFrames, + Value = _editor.Offset + //TODO set values + }; + _spinnerDuration = new Spinner(null) + { + OnlyWholeNumbers = true, + Min = 0, + Max = 40 * 60 * 2,//2 minutes is enough for a zoom trigger, you crazy nuts. + Value = 0 + //TODO set values + }; + _spinnerStart.ValueChanged += OnTriggerTimeChanged; + _spinnerDuration.ValueChanged += OnTriggerTimeChanged; + GwenHelper.CreateLabeledControl( + spinnerContainer, + "Duration: ", + _spinnerDuration).Dock = Dock.Bottom; + GwenHelper.CreateLabeledControl( + spinnerContainer, + "Start:", + _spinnerStart).Dock = Dock.Bottom; + + + _lbtriggers.RowSelected += OnTriggerSelected; + } + private void OnTriggerTimeChanged(object o, EventArgs e) + { + if (_selecting_trigger) + return; + using (var trk = _editor.CreateTrackWriter()) + { + var trigger = BeginModifyTrigger(trk); + if (trigger != null) + { + EndModifyTrigger(trigger, trk); + var selected = _lbtriggers.SelectedRow; + selected.Text = GetTriggerLabel(trigger); + selected.UserData = trigger; + } + UpdateFrame(); + } + } + private void ToggleDisable(bool disabled) + { + _spinnerStart.IsDisabled = disabled; + _spinnerDuration.IsDisabled = disabled; + _slider.IsDisabled = disabled; + } + private bool _selecting_trigger = false; + private void OnTriggerSelected(object o, ItemSelectedEventArgs e) + { + if (e.SelectedItem == null) + { + ToggleDisable(true); + } + else + { + ToggleDisable(false); + } + var trigger = (GameTrigger)e.SelectedItem.UserData; + try + { + _selecting_trigger = true; + _spinnerStart.Value = trigger.Start; + _spinnerDuration.Value = trigger.End - trigger.Start; + _triggertype.SelectByUserData(trigger.TriggerType); + if (trigger.TriggerType == TriggerType.Zoom) + { + _zoomtarget.Value = trigger.ZoomTarget; + } + } + finally + { + _selecting_trigger = false; + } + UpdateFrame(); + } + private void OnSliderValueChanged(object o, EventArgs e) + { + UpdateFrame(); + } + private void Setup() + { + ControlBase bottomcontainer = new ControlBase(this) + { + Margin = new Margin(0, 0, 0, 0), + Dock = Dock.Bottom, + AutoSizeToContents = true + }; + _slider = new HorizontalSlider(bottomcontainer) + { + Dock = Dock.Bottom, + Max = 1, + Value = 0 + }; + _slider.ValueChanged += OnSliderValueChanged; + SetupLeft(); + SetupRight(); + Populate(); + } + private void Populate() + { + using (var trk = _editor.CreateTrackWriter()) + { + foreach (var trigger in trk.Triggers) + { + _lbtriggers.AddRow( + GetTriggerLabel(trigger), string.Empty, trigger); + } + } + } + private string GetTriggerLabel(GameTrigger trigger) + { + string typelabel = ""; + switch (trigger.TriggerType) + { + case TriggerType.Zoom: + typelabel = "[Zoom]"; + break; + } + return $"{typelabel} {trigger.Start} - {trigger.End}"; + } + + private void UpdateFrame() + { + var start = Math.Max(_spinnerStart.Value - FramePadding, 0); + var end = (_spinnerStart.Value + _spinnerDuration.Value + FramePadding); + var frame = (int)(start + (end - start) * _slider.Value); + _editor.UseUserZoom = false; + _editor.SetFrame(frame); + _editor.UpdateCamera(); + } + private void CancelChange() + { + if (_changemade) + { + using (var trk = _editor.CreateTrackWriter()) + { + var triggers = trk.Triggers; + + foreach (var oldtrigger in _triggers_copy) + { + bool match = false; + foreach (var newtrigger in triggers) + { + if (oldtrigger.CompareTo(newtrigger)) + { + match = true; + } + } + if (!match) + { + _editor.Timeline.TriggerChanged(oldtrigger, oldtrigger); + } + } + trk.Triggers = _triggers_copy; + } + _changemade = false; + } + } + private void FinishChange() + { + using (var trk = _editor.CreateTrackWriter()) + { + var triggers = trk.Triggers; + if (_changemade) + { + _changemade = false; + } + } + } + } +} \ No newline at end of file diff --git a/src/UI/GwenHelper.cs b/src/UI/GwenHelper.cs index 96b6a0f9..6474de86 100644 --- a/src/UI/GwenHelper.cs +++ b/src/UI/GwenHelper.cs @@ -47,7 +47,7 @@ public static Panel CreateHeaderPanel(ControlBase parent, string headertext) }; return panel; } - public static void CreateLabeledControl(ControlBase parent, string label, ControlBase control) + public static ControlBase CreateLabeledControl(ControlBase parent, string label, ControlBase control) { control.Dock = Dock.Right; ControlBase container = new ControlBase(parent) @@ -67,6 +67,7 @@ public static void CreateLabeledControl(ControlBase parent, string label, Contro Dock = Dock.Top, Margin = new Margin(0, 1, 0, 1) }; + return container; } public static ComboBox CreateLabeledCombobox(ControlBase parent, string label) { diff --git a/src/UI/Widgets/Playhead.cs b/src/UI/Widgets/Playhead.cs index 49b2639d..8228c387 100644 --- a/src/UI/Widgets/Playhead.cs +++ b/src/UI/Widgets/Playhead.cs @@ -31,7 +31,7 @@ public class Playhead : HorizontalSlider private PlayheadMarker _flagmarker; private PlayheadMarker _endslider; private int _maxviewed = MinimumFrames; - private int MaxViewed + public int MaxViewed { get { @@ -39,7 +39,10 @@ private int MaxViewed } set { - _maxviewed = value; + if (value != _maxviewed) + { + _maxviewed = value; + } } } private int FlagFrame diff --git a/src/UI/Widgets/TimelineWidget.cs b/src/UI/Widgets/TimelineWidget.cs index e56abbb7..bf2a908b 100644 --- a/src/UI/Widgets/TimelineWidget.cs +++ b/src/UI/Widgets/TimelineWidget.cs @@ -109,7 +109,7 @@ private void Setup() Dock = Dock.Left, TextRequest = (o, e) => { - string ret = GetTimeString((int)Playhead.Value); + string ret = GetTimeString((int)Math.Round(Playhead.Value)); return ret; }, Alignment = Pos.Center, @@ -119,7 +119,7 @@ private void Setup() Dock = Dock.Right, TextRequest = (o, e) => { - string ret = GetTimeString((int)Playhead.DisplayMax); + string ret = GetTimeString(Playhead.DisplayMax); return ret; }, Alignment = Pos.Center, diff --git a/src/UI/Widgets/Toolbar.cs b/src/UI/Widgets/Toolbar.cs index 9b3a05e6..b8da74d7 100644 --- a/src/UI/Widgets/Toolbar.cs +++ b/src/UI/Widgets/Toolbar.cs @@ -93,17 +93,22 @@ private void SetupEvents() if (UI.InputUtils.Check(Hotkey.PlayButtonIgnoreFlag)) { _editor.StartIgnoreFlag(); + _editor.Scheduler.DefaultSpeed(); } else { if (_editor.Paused) + { _editor.TogglePause(); + } else + { _editor.StartFromFlag(); + _editor.Scheduler.DefaultSpeed(); + } } - _editor.Scheduler.DefaultSpeed(); _pause.IsHidden = false; - _start.IsHidden = true; ; + _start.IsHidden = true; }; _stop.Clicked += (o, e) => { @@ -164,6 +169,7 @@ private void SetupEvents() }; menu.AddItem("Preferences").Clicked += (o2, e2) => _canvas.ShowPreferencesDialog(); menu.AddItem("Track Properties").Clicked += (o2, e2) => _canvas.ShowTrackPropertiesDialog(); + menu.AddItem("Triggers").Clicked += (o2, e2) => _canvas.ShowTriggerWindow(); menu.AddItem("Export Video").Clicked += (o2, e2) => _canvas.ShowExportVideoWindow(); var canvaspos = LocalPosToCanvas(new Point(_menu.X, _menu.Y)); menu.SetPosition(canvaspos.X, canvaspos.Y + 32); diff --git a/src/Utils/Constants.cs b/src/Utils/Constants.cs index cd1bfd03..074f5f7d 100644 --- a/src/Utils/Constants.cs +++ b/src/Utils/Constants.cs @@ -36,6 +36,7 @@ static class Constants public const double MinimumZoom = 0.1; public const float MaxZoom = 24; public const float MaxSuperZoom = 200; + public const int MaxFrames = 40 * 60 * 60 * 3;// 3 hours of frames public static Color DefaultKnobColor => Settings.NightMode ? Color.FromArgb(ColorNightMode.ToArgb()) : Color.FromArgb(ColorWhite.ToArgb()); diff --git a/src/linerider.csproj b/src/linerider.csproj index 88566363..5ab82928 100644 --- a/src/linerider.csproj +++ b/src/linerider.csproj @@ -48,6 +48,10 @@ + + + + @@ -179,6 +183,7 @@ +