private void Start() { // Fill GridItems using the imported scriptable object data GridItems = new List <GridItem>(); foreach (ScriptableObject obj in GenerationManager.Instance.scriptableObjects) { GridItem gridItem = Instantiate(gridItemPrefab, gridItemRoot.transform).GetComponent <GridItem>(); gridItem.InitializeItem(obj as SliderDataCustomizer); GridItems.Add(gridItem); } // Retrieve all customizer sliders in the UI CustomizerSliders = new List <SliderObject>(); foreach (Transform child in customizerEntryRoot.transform) { SliderObject customizerSlider = child.GetComponent <SliderObject>(); if (customizerSlider != null) { CustomizerSliders.Add(customizerSlider); } } // Prepare the Perlin layer previewer perlinTexture = new Texture2D( (int)perlinPreviewImage.rectTransform.sizeDelta.x, (int)perlinPreviewImage.rectTransform.sizeDelta.y); perlinPreviewImage.texture = perlinTexture; // Select the first item for customization if (GridItems.Count > 0) { GridItems[0].SelectItem(); } }
protected override void OnDataInitialize(VisualObject vObject) { SliderObject sliderObject = vObject as SliderObject; if (sliderObject == null || (!((ResourceData)this.BackGroundData != (ResourceData)null) || sliderObject.BackGroundData.GetResourceData().Type == this.BackGroundData.Type)) { return; } sliderObject.BackGroundData = (ResourceFile)null; }
protected override void OnDataInitialize(VisualObject vObject) { SliderObject sliderObject = vObject as SliderObject; if (sliderObject != null) { if (this.BackGroundData != null && sliderObject.BackGroundData.GetResourceData().Type != this.BackGroundData.Type) { sliderObject.BackGroundData = null; } } }
public void TestSlider() { var db = new OsuDbAPI.OsuDbFile("/home/will/osu!/osu!.db", byId: true); Beatmap b = db.BeatmapsById[539697].Load("/home/will/A/osu!/Songs"); Replay r = new Replay("/home/will/osu!/Data/r/20b064985202e1a5219432c774476b8b-132562134499563000.osr"); MissAnalyzer analyzer = new MissAnalyzer(r, b); SliderObject s = (SliderObject)b.HitObjects[465]; Console.WriteLine(s); Console.WriteLine(string.Join(", ", s.Curves)); Console.WriteLine(string.Join(", ", s.Points)); Console.WriteLine(string.Join(", ", s.Curves.SelectMany(curve => curve.CurveSnapshots))); }
/// <summary> /// Saves the beatmap /// </summary> /// <param name="filename">The file to save the beatmap as</param> public void Save(string filename) { WriteBuffer.Clear(); SectionLength.Clear(); CultureInfo lastCulture = Thread.CurrentThread.CurrentCulture; Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US", false); Save("", "osu file format v13"); FieldInfo[] newFields = GetType().GetFields(); FieldInfo[] oldFields = Info.GetType().GetFields(); foreach (FieldInfo f1 in newFields) { foreach (FieldInfo f2 in oldFields.Where(f2 => f1.Name == f2.Name)) { switch (f1.Name) { case "EditorBookmarks": { List <int> temps = (List <int>)f1.GetValue(this); if (temps.Count != 0) { Save("General", "EditorBookmarks:" + string.Join(",", temps.Select(t => t.ToString(CultureInfo.InvariantCulture)).ToArray())); } } break; case "Bookmarks": { List <int> temps = (List <int>)f1.GetValue(this); if (temps.Count != 0) { Save("Editor", "Bookmarks:" + string.Join(",", temps.Select(t => t.ToString(CultureInfo.InvariantCulture)).ToArray())); } } break; case "Tags": { List <string> temps = (List <string>)f1.GetValue(this); if (temps.Count != 0) { Save("Metadata", "Tags:" + string.Join(" ", temps.ToArray())); } } break; case "Events": foreach (EventBase o in (IEnumerable <EventBase>)f1.GetValue(this)) { if (o.GetType() == typeof(ContentEvent)) { ContentEvent backgroundInfo = (ContentEvent)o; Save("Events", "0," + o.StartTime + ",\"" + backgroundInfo.Filename + "\""); } else if (o.GetType() == typeof(BreakEvent)) { BreakEvent breakInfo = (BreakEvent)o; Save("Events", "2," + o.StartTime + "," + breakInfo.EndTime); } else if (o.GetType() == typeof(BackgroundColourEvent)) { BackgroundColourEvent colourInfo = (BackgroundColourEvent)o; Save("Events", "3," + o.StartTime + "," + colourInfo.Colour.R + "," + colourInfo.Colour.G + "," + colourInfo.Colour.B); } } break; case "TimingPoints": { foreach (TimingPoint o in (IEnumerable <TimingPoint>)f1.GetValue(this)) { Save("TimingPoints", o.Time + "," + o.BpmDelay + "," + o.TimeSignature + "," + o.SampleSet + "," + o.CustomSampleSet + "," + o.VolumePercentage + "," + Convert.ToInt32(!o.InheritsBPM) + "," + (int)o.VisualOptions); } } break; case "ComboColours": { foreach (Combo o in (IEnumerable <Combo>)f1.GetValue(this)) { Save("Colours", "Combo" + o.ComboNumber + ':' + o.Colour.R + "," + o.Colour.G + "," + o.Colour.B); } } break; //case "SliderBorder": // { // if (f1.GetValue(this) == f2.GetValue(Info)) // continue; // Colour o = (Colour)f1.GetValue(this); // Save("Colours", "SliderBorder: " + o.R + "," + o.G + "," + o.B); // } // break; case "HitObjects": foreach (CircleObject obj in (IEnumerable <CircleObject>)f1.GetValue(this)) { if (obj.GetType() == typeof(CircleObject)) { Save("HitObjects", obj.Location.X + "," + obj.Location.Y + "," + obj.StartTime + "," + (int)obj.Type + "," + (int)obj.Effect); } else if (obj.GetType() == typeof(SliderObject)) { SliderObject sliderInfo = (SliderObject)obj; string pointString = sliderInfo.Points.Aggregate("", (current, p) => current + ("|" + p.X + ':' + p.Y)); Save("HitObjects", obj.Location.X + "," + obj.Location.Y + "," + obj.StartTime + "," + (int)obj.Type + "," + (int)obj.Effect + "," + sliderInfo.Type.ToString().Substring(0, 1) + pointString + "," + sliderInfo.RepeatCount + "," + sliderInfo.MaxPoints); } else if (obj.GetType() == typeof(SpinnerObject)) { SpinnerObject spinnerInfo = (SpinnerObject)obj; Save("HitObjects", obj.Location.X + "," + obj.Location.Y + "," + obj.StartTime + "," + (int)obj.Type + "," + (int)obj.Effect + "," + spinnerInfo.EndTime); } } break; default: if (f1.Name != "Format" && f1.Name != "Filename" && f1.Name != "BeatmapHash") { if (f1.GetValue(this) != null) { if (f2.GetValue(Info) != null) { if ((f1.GetValue(this).GetType() == typeof(GameMode)) || (f1.GetValue(this).GetType() == typeof(OverlayOptions))) { Save(GetSection(f1.Name), f1.Name + ':' + (int)f1.GetValue(this)); } else { Save(GetSection(f1.Name), f1.Name + ':' + f1.GetValue(this)); } } else { if ((f2.GetValue(Info).GetType() == typeof(GameMode)) || (f2.GetValue(Info).GetType() == typeof(OverlayOptions))) { Save(GetSection(f2.Name), f2.Name + ':' + (int)f2.GetValue(Info)); } else { Save(GetSection(f2.Name), f2.Name + ':' + f2.GetValue(Info)); } } } } break; } } } foreach (PropertyInfo f1 in GetType().GetProperties()) { foreach (PropertyInfo f2 in Info.GetType().GetProperties().Where(f2 => f1.Name == f2.Name)) { if (f1.GetValue(this, null) != null) { if (f2.GetValue(Info, null) != null) { if ((f1.GetValue(this, null).GetType() == typeof(GameMode)) || (f1.GetValue(this, null).GetType() == typeof(OverlayOptions))) { Save(GetSection(f1.Name), f1.Name + ':' + (int)f1.GetValue(this, null)); } else { Save(GetSection(f1.Name), f1.Name + ':' + f1.GetValue(this, null)); } } else { if ((f2.GetValue(Info, null).GetType() == typeof(GameMode)) || (f2.GetValue(Info, null).GetType() == typeof(OverlayOptions))) { Save(GetSection(f2.Name), f2.Name + ':' + (int)f2.GetValue(Info, null)); } else { Save(GetSection(f2.Name), f2.Name + ':' + f2.GetValue(Info, null)); } } } } } FinishSave(filename); Thread.CurrentThread.CurrentCulture = lastCulture; }
private void Parse(string bm) { Info.Filename = bm; Info.BeatmapHash = MD5FromFile(bm); using (StreamReader sR = new StreamReader(bm)) { string currentSection = ""; while (sR.Peek() != -1) { string line = sR.ReadLine(); //Check for section tag if (line.StartsWith("[")) { currentSection = line; continue; } //Check for commented-out line //or blank lines if (line.StartsWith("//") || line.Length == 0) { continue; } //Check for version string if (line.StartsWith("osu file format")) { Info.Format = Convert.ToInt32(line.Substring(17).Replace(Environment.NewLine, "").Replace(" ", "")); } //Do work for [General], [Metadata], [Difficulty] and [Editor] sections if ((currentSection == "[General]") || (currentSection == "[Metadata]") || (currentSection == "[Difficulty]") || (currentSection == "[Editor]")) { string[] reSplit = line.Split(':'); string cProperty = reSplit[0].TrimEnd(); bool isValidProperty = false; foreach (string k in BM_Sections.Keys) { if (k.Contains(cProperty)) { isValidProperty = true; } } if (!isValidProperty) { continue; } //Check for blank value string cValue = reSplit[1].Trim(); //Import properties into Info switch (cProperty) { case "EditorBookmarks": { string[] marks = cValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); foreach (string m in marks.Where(m => m != "")) { Info.EditorBookmarks.Add(Convert.ToInt32(m)); } } break; case "Bookmarks": { string[] marks = cValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); foreach (string m in marks.Where(m => m != "")) { Info.Bookmarks.Add(Convert.ToInt32(m)); } } break; case "Tags": string[] tags = cValue.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach (string t in tags) { Info.Tags.Add(t); } break; case "Mode": Info.Mode = (GameMode)Convert.ToInt32(cValue); break; case "OverlayPosition": Info.OverlayPosition = (OverlayOptions)Enum.Parse(typeof(OverlayOptions), cValue); break; case "AlwaysShowPlayfield": Info.AlwaysShowPlayfield = Convert.ToBoolean(Convert.ToInt32(cValue)); break; default: FieldInfo fi = Info.GetType().GetField(cProperty); PropertyInfo pi = Info.GetType().GetProperty(cProperty); if (fi != null) { if (fi.FieldType == typeof(float?)) { fi.SetValue(Info, (float?)Convert.ToDouble(cValue)); } if (fi.FieldType == typeof(float)) { fi.SetValue(Info, (float)Convert.ToDouble(cValue)); } else if ((fi.FieldType == typeof(int?)) || (fi.FieldType == typeof(int))) { fi.SetValue(Info, Convert.ToInt32(cValue)); } else if (fi.FieldType == typeof(string)) { fi.SetValue(Info, cValue); } break; } if (pi.PropertyType == typeof(float?)) { pi.SetValue(Info, (float?)Convert.ToDouble(cValue), null); } if (pi.PropertyType == typeof(float)) { pi.SetValue(Info, (float)Convert.ToDouble(cValue), null); } else if ((pi.PropertyType == typeof(int?)) || (pi.PropertyType == typeof(int))) { pi.SetValue(Info, Convert.ToInt32(cValue), null); } else if (pi.PropertyType == typeof(string)) { pi.SetValue(Info, cValue, null); } break; } continue; } //The following are version-dependent, the version is stored as a numeric value inside Info.Format //Do work for [Events] section if (currentSection == "[Events]") { string[] reSplit = line.Split(','); switch (reSplit[0].ToLower()) { case "0": case "1": case "video": Info.Events.Add(new ContentEvent { Type = reSplit[0].ToLower() == "1" || reSplit[0].ToLower() == "video" ? ContentType.Video : ContentType.Image, StartTime = Convert.ToInt32(reSplit[1]), Filename = reSplit[2].Replace("\"", "") }); break; case "2": Info.Events.Add(new BreakEvent { StartTime = Convert.ToInt32(reSplit[1]), EndTime = Convert.ToInt32(reSplit[2]) }); break; case "3": Info.Events.Add(new BackgroundColourEvent { StartTime = Convert.ToInt32(reSplit[1]), Colour = new Colour { R = Convert.ToInt32(reSplit[2]), G = Convert.ToInt32(reSplit[3]), B = Convert.ToInt32(reSplit[4]) }, }); break; } } //Do work for [TimingPoints] section if (currentSection == "[TimingPoints]") { TimingPoint tempTimingPoint = new TimingPoint(); float[] values = { 0, 0, 4, 0, 0, 100, 0, 0, 0 }; string[] reSplit = line.Split(','); for (int i = 0; i < reSplit.Length; i++) { values[i] = (float)Convert.ToDouble(reSplit[i]); } tempTimingPoint.Time = (float)Convert.ToDouble(values[0]); tempTimingPoint.BpmDelay = (float)Convert.ToDouble(values[1]); tempTimingPoint.TimeSignature = Convert.ToInt32(values[2]); tempTimingPoint.SampleSet = Convert.ToInt32(values[3]); tempTimingPoint.CustomSampleSet = Convert.ToInt32(values[4]); tempTimingPoint.VolumePercentage = Convert.ToInt32(values[5]); tempTimingPoint.InheritsBPM = !Convert.ToBoolean(Convert.ToInt32(values[6])); tempTimingPoint.VisualOptions = (TimingPointOptions)Convert.ToInt32(values[7]); Info.TimingPoints.Add(tempTimingPoint); } //Do work for [Colours] section if (currentSection == "[Colours]") { string property = line.Substring(0, line.IndexOf(':', 1)).Trim(); string value = line.Substring(line.IndexOf(':', 1) + 1).Trim(); string[] reSplit = value.Split(','); if (property.Length > 5 && property.Substring(0, 5) == "Combo") { Combo newCombo = new Combo { Colour = new Colour { R = Convert.ToInt32(reSplit[0]), G = Convert.ToInt32(reSplit[1]), B = Convert.ToInt32(reSplit[2]) } }; try { newCombo.ComboNumber = Convert.ToInt32(property.Substring(5, 1)); } catch { Debug.Assert(false, "Invalid combonumber at index 5. " + line); continue; } } else if (property.Length > 5 && property == "SliderBorder") { Info.SliderBorder = new Colour { R = Convert.ToInt32(reSplit[0]), G = Convert.ToInt32(reSplit[1]), B = Convert.ToInt32(reSplit[2]) }; } } //Do work for [HitObjects] section if (currentSection == "[HitObjects]") { string[] reSplit = line.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); CircleObject newObject = new CircleObject { Radius = 40 - 4 * (Info.CircleSize - 2), Location = new Point2(Convert.ToInt32(reSplit[0]), Convert.ToInt32(reSplit[1])), StartTime = (float)Convert.ToDouble(reSplit[2]), Type = (HitObjectType)Convert.ToInt32(reSplit[3]), Effect = (EffectType)Convert.ToInt32(reSplit[4]) }; if ((newObject.Type & HitObjectType.Slider) > 0) { newObject = new SliderObject(newObject); ((SliderObject)newObject).Velocity = Info.SliderMultiplier; switch (reSplit[5].Substring(0, 1)) { case "B": ((SliderObject)newObject).Type = SliderType.Bezier; break; case "C": ((SliderObject)newObject).Type = SliderType.CSpline; break; case "L": ((SliderObject)newObject).Type = SliderType.Linear; break; case "P": ((SliderObject)newObject).Type = SliderType.PSpline; break; } string[] pts = reSplit[5].Split(new[] { "|" }, StringSplitOptions.None); //Todo: Check this if (Format <= 4) { ((SliderObject)newObject).Points.Add(newObject.Location); } //Always exclude index 1, this will contain the type for (int i = 1; i <= pts.Length - 1; i++) { Point2 p = new Point2((float)Convert.ToDouble(pts[i].Substring(0, pts[i].IndexOf(":", StringComparison.InvariantCulture))), (float)Convert.ToDouble(pts[i].Substring(pts[i].IndexOf(":", StringComparison.InvariantCulture) + 1))); ((SliderObject)newObject).Points.Add(p); } ((SliderObject)newObject).RepeatCount = Convert.ToInt32(reSplit[6]); float tempMaxPoints; if (float.TryParse(reSplit[7], out tempMaxPoints)) { ((SliderObject)newObject).MaxPoints = tempMaxPoints; } } if ((newObject.Type & HitObjectType.Spinner) > 0) { newObject = new SpinnerObject(newObject); ((SpinnerObject)newObject).EndTime = (float)Convert.ToDouble(reSplit[5]); } Info.HitObjects.Add(newObject); } } } //Copy the fields/properties of Info locally foreach (FieldInfo fi in Info.GetType().GetFields()) { FieldInfo ff = GetType().GetField(fi.Name); ff.SetValue(this, fi.GetValue(Info)); } foreach (PropertyInfo pi in Info.GetType().GetProperties()) { PropertyInfo ff = GetType().GetProperty(pi.Name); ff.SetValue(this, pi.GetValue(Info, null), null); } }
/// <summary> /// Draws the miss. /// </summary> /// <returns>A Bitmap containing the drawing</returns> /// <param name="num">Index of the miss as it shows up in r.misses.</param> public Image DrawHitObject(int num, Rectangle area) { Image img = new Image <Rgba32>(area.Width, area.Height, ColorScheme.BackgroundColor); img.Mutate(g => { bool hr = Replay.Mods.HasFlag(Mods.HardRock); CircleObject hitObject; if (drawAllHitObjects) { hitObject = Beatmap.HitObjects[num]; } else { hitObject = ReplayAnalyzer.misses[num]; } bool isMiss = !drawAllHitObjects || ReplayAnalyzer.misses.Contains(hitObject); float radius = (float)hitObject.Radius; Func <Color, Pen> circlePen = color => new Pen(color, radius * 2 / scale) { EndCapStyle = EndCapStyle.Round, JointStyle = JointStyle.Round, }; Func <Color, Pen> linePen = color => new Pen(color, 1.5f); RectangleF bounds = new RectangleF(PointF.Subtract(hitObject.Location.ToPointF(), Scale(area.Size, scale / 2)), Scale(area.Size, scale)); int replayFramesStart, replayFramesEnd, hitObjectsStart, hitObjectsEnd; for (hitObjectsStart = Beatmap.HitObjects.Count(x => x.StartTime <= hitObject.StartTime) - 1; hitObjectsStart >= 0 && bounds.Contains(Beatmap.HitObjects[hitObjectsStart].Location.ToPointF()) && hitObject.StartTime - Beatmap.HitObjects[hitObjectsStart].StartTime < maxTime; hitObjectsStart--) { ; } for (hitObjectsEnd = Beatmap.HitObjects.Count(x => x.StartTime <= hitObject.StartTime) - 1; hitObjectsEnd < Beatmap.HitObjects.Count && bounds.Contains(Beatmap.HitObjects[hitObjectsEnd].Location.ToPointF()) && Beatmap.HitObjects[hitObjectsEnd].StartTime - hitObject.StartTime < maxTime; hitObjectsEnd++) { ; } for (replayFramesStart = Replay.ReplayFrames.Count(x => x.Time <= Beatmap.HitObjects[hitObjectsStart + 1].StartTime); replayFramesStart > 1 && replayFramesStart < Replay.ReplayFrames.Count && bounds.Contains(Replay.ReplayFrames[replayFramesStart].GetPointF()) && hitObject.StartTime - Replay.ReplayFrames[replayFramesStart].Time < maxTime; replayFramesStart--) { ; } for (replayFramesEnd = Replay.ReplayFrames.Count(x => x.Time <= Beatmap.HitObjects[hitObjectsEnd - 1].StartTime); replayFramesEnd < Replay.ReplayFrames.Count - 1 && (replayFramesEnd < 2 || bounds.Contains(Replay.ReplayFrames[replayFramesEnd - 2].GetPointF())) && Replay.ReplayFrames[replayFramesEnd].Time - hitObject.StartTime < maxTime; replayFramesEnd++) { ; } g.Draw(linePen(ColorScheme.PlayfieldColor), Rectangle.Round(ScaleToRect(new RectangleF(pSub(new PointF(0, hr? 384 : 0), bounds, hr), new SizeF(512, 384)), bounds, area))); for (int q = hitObjectsEnd - 1; q > hitObjectsStart; q--) { if (Beatmap.HitObjects[q].Type.HasFlag(HitObjectType.Slider)) { SliderObject slider = (SliderObject)Beatmap.HitObjects[q]; PointF[] pt = slider.Curves.SelectMany(curve => curve.CurveSnapshots) .Select(c => c.point + slider.StackOffset.ToVector2()) .Select(s => ScaleToRect(pSub(s.ToPointF(), bounds, hr), bounds, area)).ToArray(); if (pt.Length > 1) { g.DrawLines(circlePen(ColorScheme.SliderColor.WithAlpha(80 / 255f)), pt); } } var color = ColorScheme.GetCircleColor(Math.Abs(Beatmap.HitObjects[q].StartTime - hitObject.StartTime) / maxTime); var circleRect = ScaleToRect(new RectangleF(PointF.Subtract( pSub(Beatmap.HitObjects[q].Location.ToPointF(), bounds, hr), (Size) new SizeF(radius, radius)), new SizeF(radius * 2, radius * 2)), bounds, area); var circle = new EllipsePolygon(RectangleF.Center(circleRect), circleRect.Size); if (HitCircleOutlines) { g.Draw(linePen(color), circle); } else { g.Fill(color, circle); } } float distance = 10.0001f; float?closestHit = null; float closestDistance = 0; string verdict = null; for (int k = replayFramesStart; k < replayFramesEnd - 2; k++) { PointF p1 = pSub(Replay.ReplayFrames[k].GetPointF(), bounds, hr); PointF p2 = pSub(Replay.ReplayFrames[k + 1].GetPointF(), bounds, hr); float hitAcc = Replay.ReplayFrames[k].Time - hitObject.StartTime; var pen = linePen(GetHitColor(Beatmap.OverallDifficulty, hitAcc) ?? ColorScheme.LineColor); g.DrawLines(pen, ScaleToRect(p1, bounds, area), ScaleToRect(p2, bounds, area)); if (distance > 10 && Math.Abs(hitObject.StartTime - Replay.ReplayFrames[k + 1].Time) > 50) { Point2 v1 = new Point2(p1.X - p2.X, p1.Y - p2.Y); if (v1.Length > 0) { v1.Normalize(); v1 *= (float)(Math.Sqrt(2) * arrowLength / 2); PointF p3 = PointF.Add(p2, new SizeF(v1.X + v1.Y, v1.Y - v1.X)); PointF p4 = PointF.Add(p2, new SizeF(v1.X - v1.Y, v1.X + v1.Y)); p2 = ScaleToRect(p2, bounds, area); p3 = ScaleToRect(p3, bounds, area); p4 = ScaleToRect(p4, bounds, area); g.DrawLines(pen, p2, p3); g.DrawLines(pen, p2, p4); } distance = 0; } else { distance += new Point2(p1.X - p2.X, p1.Y - p2.Y).Length; } if (Replay.ReplayFrames[k].Time <= hitObject.StartTime && Replay.ReplayFrames[k + 1].Time > hitObject.StartTime) { var lerp = (hitObject.StartTime - Replay.ReplayFrames[k].Time) / (Replay.ReplayFrames[k + 1].Time - Replay.ReplayFrames[k].Time); var p3 = new PointF(p1.X + (p2.X - p1.X) * lerp, p1.Y + (p2.Y - p1.Y) * lerp); EllipsePolygon circle = new EllipsePolygon(ScaleToRect(p3, bounds, area), ScaleToRect(new SizeF(2, 2), bounds, area)); g.Draw(linePen(ColorScheme.MidpointColor), circle); if (hitObject.ContainsPoint(Replay.ReplayFrames[k].GetPoint2())) { verdict = "Misclick"; } else { verdict = "Misaim"; } } if (ReplayAnalyzer.getKey(k == 0 ? Keys.None : Replay.ReplayFrames[k - 1].Keys, Replay.ReplayFrames[k].Keys) > 0) { EllipsePolygon circle = new EllipsePolygon(ScaleToRect(p1, bounds, area), ScaleToRect(new SizeF(6, 6), bounds, area)); g.Draw(pen, circle); if (Math.Abs(hitAcc) < GetHitWindow(Beatmap.OverallDifficulty, 50) && (!closestHit.HasValue || Math.Abs(closestHit.Value) > Math.Abs(hitAcc))) { closestHit = hitAcc; closestDistance = hitObject.DistanceToPoint(Replay.ReplayFrames[k].GetPoint2()); } } } if (closestDistance <= 0) { verdict = "Notelock"; closestDistance = 0; } int textSize = 16; int textPadding = 3; Font f = new Font(SystemFonts.Get("Segoe UI"), textSize); var opts = new TextOptions(f) { WrappingLength = area.Width - 2 * textPadding, Origin = new System.Numerics.Vector2(textPadding, textPadding), }; var topText = Beatmap.ToString(); if (drawAllHitObjects) { topText += $"\nObject {num + 1} of {Beatmap.HitObjects.Count}"; } else { topText += $"\nMiss {num + 1} of {MissCount}"; } g.DrawText(opts, topText, ColorScheme.TextColor); float time = hitObject.StartTime; if (Replay.Mods.HasFlag(Mods.DoubleTime)) { time /= 1.5f; } else if (Replay.Mods.HasFlag(Mods.HalfTime)) { time /= 0.75f; } TimeSpan ts = TimeSpan.FromMilliseconds(time); opts = new TextOptions(f) { VerticalAlignment = VerticalAlignment.Bottom, Origin = new System.Numerics.Vector2(textPadding, area.Height - textPadding), }; g.DrawText(opts, $"Time: {ts:mm\\:ss\\.fff}", ColorScheme.TextColor); if (closestHit.HasValue && isMiss) { opts = new TextOptions(f) { HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Bottom, TextAlignment = TextAlignment.End, Origin = new System.Numerics.Vector2(area.Width - textPadding, area.Height - textPadding), }; string clickText = $"Closest click: {Math.Abs(closestHit.Value)}ms {(closestHit.Value < 0? "early":"late")}"; if (closestDistance > 0) { clickText += $", {closestDistance:N} units off"; } g.DrawText(opts, $"Verdict: {verdict}\n{clickText}", ColorScheme.TextColor); } }); return(img); }
/// <summary> /// Draws the miss. /// </summary> /// <returns>A Bitmap containing the drawing</returns> /// <param name="num">Index of the miss as it shows up in r.misses.</param> private Bitmap drawMiss(int num) { bool hr = r.Mods.HasFlag(Mods.HardRock); CircleObject miss; if (all) { miss = b.HitObjects[num]; } else { miss = re.misses[num]; } float radius = (float)miss.Radius; Pen circle = new Pen(Color.Gray, radius * 2 / scale); circle.StartCap = System.Drawing.Drawing2D.LineCap.Round; circle.EndCap = System.Drawing.Drawing2D.LineCap.Round; circle.LineJoin = System.Drawing.Drawing2D.LineJoin.Round; Pen p = new Pen(Color.White); g.FillRectangle(p.Brush, 0, 0, size, size); RectangleF bounds = new RectangleF((miss.Location - new Point2(size * scale / 2, size * scale / 2)).ToPointF(), new SizeF(size * scale, size * scale)); int i, j, y, z; for (y = b.HitObjects.Count(x => x.StartTime <= miss.StartTime) - 1; y >= 0 && bounds.Contains(b.HitObjects[y].Location.ToPointF()) && miss.StartTime - b.HitObjects[y].StartTime < maxTime; y--) { } for (z = b.HitObjects.Count(x => x.StartTime <= miss.StartTime) - 1; z < b.HitObjects.Count && bounds.Contains(b.HitObjects[z].Location.ToPointF()) && b.HitObjects[z].StartTime - miss.StartTime < maxTime; z++) { } for (i = r.ReplayFrames.Count(x => x.Time <= b.HitObjects[y + 1].StartTime); i > 0 && bounds.Contains(r.ReplayFrames[i].PointF) && miss.StartTime - r.ReplayFrames[i].Time < maxTime; i--) { } for (j = r.ReplayFrames.Count(x => x.Time <= b.HitObjects[z - 1].StartTime); j < r.ReplayFrames.Count - 1 && bounds.Contains(r.ReplayFrames[j].PointF) && r.ReplayFrames[j].Time - miss.StartTime < maxTime; j++) { } p.Color = Color.Gray; for (int q = z - 1; q > y; q--) { int c = Math.Min(255, 100 + (int)(Math.Abs(b.HitObjects[q].StartTime - miss.StartTime) * 100 / maxTime)); if (b.HitObjects[q].Type == HitObjectType.Slider) { SliderObject slider = (SliderObject)b.HitObjects[q]; PointF[] pt = new PointF[sliderGranularity]; for (int x = 0; x < sliderGranularity; x++) { pt[x] = ScaleToRect( pSub(slider.PositionAtDistance(x * 1f * slider.PixelLength / sliderGranularity).toPoint(), bounds, hr), bounds); } circle.Color = Color.LemonChiffon; g.DrawLines(circle, pt); } p.Color = Color.FromArgb(c == 100 ? c + 50 : c, c, c); if (ring) { g.DrawEllipse(p, ScaleToRect(new RectangleF(PointF.Subtract( pSub(b.HitObjects[q].Location.ToPointF(), bounds, hr), new SizeF(radius, radius).ToSize()), new SizeF(radius * 2, radius * 2)), bounds)); } else { g.FillEllipse(p.Brush, ScaleToRect(new RectangleF(PointF.Subtract( pSub(b.HitObjects[q].Location.ToPointF(), bounds, hr), new SizeF(radius, radius).ToSize()), new SizeF(radius * 2, radius * 2)), bounds)); } } float distance = 10.0001f; for (int k = i; k < j; k++) { PointF p1 = pSub(r.ReplayFrames[k].PointF, bounds, hr); PointF p2 = pSub(r.ReplayFrames[k + 1].PointF, bounds, hr); p.Color = getHitColor(b.OverallDifficulty, (int)(miss.StartTime - r.ReplayFrames[k].Time)); g.DrawLine(p, ScaleToRect(p1, bounds), ScaleToRect(p2, bounds)); if (distance > 10 && Math.Abs(miss.StartTime - r.ReplayFrames[k + 1].Time) > 50) { Point2 v1 = new Point2(p1.X - p2.X, p1.Y - p2.Y); if (v1.Length > 0) { v1.Normalize(); v1 *= (float)(Math.Sqrt(2) * arrowLength / 2); PointF p3 = PointF.Add(p2, new SizeF(v1.X + v1.Y, v1.Y - v1.X)); PointF p4 = PointF.Add(p2, new SizeF(v1.X - v1.Y, v1.X + v1.Y)); p2 = ScaleToRect(p2, bounds); p3 = ScaleToRect(p3, bounds); p4 = ScaleToRect(p4, bounds); g.DrawLine(p, p2, p3); g.DrawLine(p, p2, p4); } distance = 0; } else { distance += new Point2(p1.X - p2.X, p1.Y - p2.Y).Length; } if (re.getKey(k == 0 ? ReplayAPI.Keys.None : r.ReplayFrames[k - 1].Keys, r.ReplayFrames[k].Keys) > 0) { g.DrawEllipse(p, ScaleToRect(new RectangleF(PointF.Subtract(p1, new Size(3, 3)), new Size(6, 6)), bounds)); } } p.Color = Color.Black; Font f = new Font(FontFamily.GenericSansSerif, 12); if (all) { g.DrawString("Object " + (num + 1) + " of " + b.HitObjects.Count, f, p.Brush, 0, 0); } else { g.DrawString("Miss " + (num + 1) + " of " + re.misses.Count, f, p.Brush, 0, 0); } TimeSpan ts = TimeSpan.FromMilliseconds(miss.StartTime); g.DrawString("Time: " + ts.ToString(@"mm\:ss\.fff"), f, p.Brush, 0, size - f.Height); return(img); }
private void Parse(string bm) { Info.Filename = bm; Info.BeatmapHash = MD5FromFile(bm); using (StreamReader sR = new StreamReader(bm)) { string currentSection = ""; while (sR.Peek() != -1) { string line = sR.ReadLine(); //Check for section tag if (line.StartsWith("[")) { currentSection = line; continue; } //Check for commented-out line //or blank lines if (line.StartsWith("//") || line.Length == 0) continue; //Check for version string if (line.StartsWith("osu file format")) Info.Format = Convert.ToInt32(line.Substring(17).Replace(Environment.NewLine, "").Replace(" ", "")); //Do work for [General], [Metadata], [Difficulty] and [Editor] sections if ((currentSection == "[General]") || (currentSection == "[Metadata]") || (currentSection == "[Difficulty]") || (currentSection == "[Editor]")) { string[] reSplit = line.Split(':'); string cProperty = reSplit[0].TrimEnd(); bool isValidProperty = false; foreach (string k in BM_Sections.Keys) { if (k.Contains(cProperty)) isValidProperty = true; } if (!isValidProperty) continue; //Check for blank value string cValue = reSplit[1].Trim(); //Import properties into Info switch (cProperty) { case "EditorBookmarks": { string[] marks = cValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); foreach (string m in marks.Where(m => m != "")) Info.EditorBookmarks.Add(Convert.ToInt32(m)); } break; case "Bookmarks": { string[] marks = cValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); foreach (string m in marks.Where(m => m != "")) Info.Bookmarks.Add(Convert.ToInt32(m)); } break; case "Tags": string[] tags = cValue.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach (string t in tags) Info.Tags.Add(t); break; case "Mode": Info.Mode = (GameMode)Convert.ToInt32(cValue); break; case "OverlayPosition": Info.OverlayPosition = (OverlayOptions)Enum.Parse(typeof(OverlayOptions), cValue); break; case "AlwaysShowPlayfield": Info.AlwaysShowPlayfield = Convert.ToBoolean(Convert.ToInt32(cValue)); break; default: FieldInfo fi = Info.GetType().GetField(cProperty); PropertyInfo pi = Info.GetType().GetProperty(cProperty); if (fi != null) { if (fi.FieldType == typeof(float?)) fi.SetValue(Info, (float?)Convert.ToDouble(cValue)); if (fi.FieldType == typeof(float)) fi.SetValue(Info, (float)Convert.ToDouble(cValue)); else if ((fi.FieldType == typeof(int?)) || (fi.FieldType == typeof(int))) fi.SetValue(Info, Convert.ToInt32(cValue)); else if (fi.FieldType == typeof(string)) fi.SetValue(Info, cValue); break; } if (pi.PropertyType == typeof(float?)) pi.SetValue(Info, (float?)Convert.ToDouble(cValue), null); if (pi.PropertyType == typeof(float)) pi.SetValue(Info, (float)Convert.ToDouble(cValue), null); else if ((pi.PropertyType == typeof(int?)) || (pi.PropertyType == typeof(int))) pi.SetValue(Info, Convert.ToInt32(cValue), null); else if (pi.PropertyType == typeof(string)) pi.SetValue(Info, cValue, null); break; } continue; } //The following are version-dependent, the version is stored as a numeric value inside Info.Format //Do work for [Events] section if (currentSection == "[Events]") { string[] reSplit = line.Split(','); switch (reSplit[0].ToLower()) { case "0": case "1": case "video": Info.Events.Add(new ContentEvent { Type = reSplit[0].ToLower() == "1" || reSplit[0].ToLower() == "video" ? ContentType.Video : ContentType.Image, StartTime = Convert.ToInt32(reSplit[1]), Filename = reSplit[2].Replace("\"", "") }); break; case "2": Info.Events.Add(new BreakEvent { StartTime = Convert.ToInt32(reSplit[1]), EndTime = Convert.ToInt32(reSplit[2]) }); break; case "3": Info.Events.Add(new BackgroundColourEvent { StartTime = Convert.ToInt32(reSplit[1]), Colour = new Colour { R = Convert.ToInt32(reSplit[2]), G = Convert.ToInt32(reSplit[3]), B = Convert.ToInt32(reSplit[4]) }, }); break; } } //Do work for [TimingPoints] section if (currentSection == "[TimingPoints]") { TimingPoint tempTimingPoint = new TimingPoint(); float[] values = { 0, 0, 4, 0, 0, 100, 0, 0, 0 }; string[] reSplit = line.Split(','); for (int i = 0; i < reSplit.Length; i++) values[i] = (float)Convert.ToDouble(reSplit[i]); tempTimingPoint.Time = (float)Convert.ToDouble(values[0]); tempTimingPoint.BpmDelay = (float)Convert.ToDouble(values[1]); tempTimingPoint.TimeSignature = Convert.ToInt32(values[2]); tempTimingPoint.SampleSet = Convert.ToInt32(values[3]); tempTimingPoint.CustomSampleSet = Convert.ToInt32(values[4]); tempTimingPoint.VolumePercentage = Convert.ToInt32(values[5]); tempTimingPoint.InheritsBPM = !Convert.ToBoolean(Convert.ToInt32(values[6])); tempTimingPoint.VisualOptions = (TimingPointOptions)Convert.ToInt32(values[7]); Info.TimingPoints.Add(tempTimingPoint); } //Do work for [Colours] section if (currentSection == "[Colours]") { string property = line.Substring(0, line.IndexOf(':', 1)).Trim(); string value = line.Substring(line.IndexOf(':', 1) + 1).Trim(); string[] reSplit = value.Split(','); if (property.Length > 5 && property.Substring(0, 5) == "Combo") { Combo newCombo = new Combo { Colour = new Colour { R = Convert.ToInt32(reSplit[0]), G = Convert.ToInt32(reSplit[1]), B = Convert.ToInt32(reSplit[2]) } }; try { newCombo.ComboNumber = Convert.ToInt32(property.Substring(5, 1)); } catch { Debug.Assert(false, "Invalid combonumber at index 5. " + line); continue; } } else if (property.Length > 5 && property == "SliderBorder") { Info.SliderBorder = new Colour { R = Convert.ToInt32(reSplit[0]), G = Convert.ToInt32(reSplit[1]), B = Convert.ToInt32(reSplit[2]) }; } } //Do work for [HitObjects] section if (currentSection == "[HitObjects]") { string[] reSplit = line.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); CircleObject newObject = new CircleObject { Radius = 40 - 4 * (Info.CircleSize - 2), Location = new Point2(Convert.ToInt32(reSplit[0]), Convert.ToInt32(reSplit[1])), StartTime = (float)Convert.ToDouble(reSplit[2]), Type = (HitObjectType)Convert.ToInt32(reSplit[3]), Effect = (EffectType)Convert.ToInt32(reSplit[4]) }; if ((newObject.Type & HitObjectType.Slider) > 0) { newObject = new SliderObject(newObject); ((SliderObject)newObject).Velocity = Info.SliderMultiplier; switch (reSplit[5].Substring(0, 1)) { case "B": ((SliderObject)newObject).Type = SliderType.Bezier; break; case "C": ((SliderObject)newObject).Type = SliderType.CSpline; break; case "L": ((SliderObject)newObject).Type = SliderType.Linear; break; case "P": ((SliderObject)newObject).Type = SliderType.PSpline; break; } string[] pts = reSplit[5].Split(new[] { "|" }, StringSplitOptions.None); //Todo: Check this if (Format <= 4) ((SliderObject)newObject).Points.Add(newObject.Location); //Always exclude index 1, this will contain the type for (int i = 1; i <= pts.Length - 1; i++) { Point2 p = new Point2((float)Convert.ToDouble(pts[i].Substring(0, pts[i].IndexOf(":", StringComparison.InvariantCulture))), (float)Convert.ToDouble(pts[i].Substring(pts[i].IndexOf(":", StringComparison.InvariantCulture) + 1))); ((SliderObject)newObject).Points.Add(p); } ((SliderObject)newObject).RepeatCount = Convert.ToInt32(reSplit[6]); float tempMaxPoints; if (float.TryParse(reSplit[7], out tempMaxPoints)) ((SliderObject)newObject).MaxPoints = tempMaxPoints; } if ((newObject.Type & HitObjectType.Spinner) > 0) { newObject = new SpinnerObject(newObject); ((SpinnerObject)newObject).EndTime = (float)Convert.ToDouble(reSplit[5]); } Info.HitObjects.Add(newObject); } } } //Copy the fields/properties of Info locally foreach (FieldInfo fi in Info.GetType().GetFields()) { FieldInfo ff = GetType().GetField(fi.Name); ff.SetValue(this, fi.GetValue(Info)); } foreach (PropertyInfo pi in Info.GetType().GetProperties()) { PropertyInfo ff = GetType().GetProperty(pi.Name); ff.SetValue(this, pi.GetValue(Info, null), null); } }
/// <summary> /// Draws the miss. /// </summary> /// <returns>A Bitmap containing the drawing</returns> /// <param name="num">Index of the miss as it shows up in r.misses.</param> public Bitmap DrawHitObject(int num, Rectangle area) { Bitmap img = new Bitmap(area.Width, area.Height); Graphics g = Graphics.FromImage(img); bool hr = Replay.Mods.HasFlag(Mods.HardRock); CircleObject hitObject; if (drawAllHitObjects) { hitObject = Beatmap.HitObjects[num]; } else { hitObject = ReplayAnalyzer.misses[num]; } float radius = (float)hitObject.Radius; Pen circle = new Pen(Color.Gray, radius * 2 / scale) { StartCap = System.Drawing.Drawing2D.LineCap.Round, EndCap = System.Drawing.Drawing2D.LineCap.Round, LineJoin = System.Drawing.Drawing2D.LineJoin.Round }; Pen p = new Pen(Color.White); g.FillRectangle(p.Brush, area); RectangleF bounds = new RectangleF(PointF.Subtract(hitObject.Location.ToPointF(), MathUtils.Scale(area.Size, scale / 2)), MathUtils.Scale(area.Size, scale)); int replayFramesStart, replayFramesEnd, hitObjectsStart, hitObjectsEnd; for (hitObjectsStart = Beatmap.HitObjects.Count(x => x.StartTime <= hitObject.StartTime) - 1; hitObjectsStart >= 0 && bounds.Contains(Beatmap.HitObjects[hitObjectsStart].Location.ToPointF()) && hitObject.StartTime - Beatmap.HitObjects[hitObjectsStart].StartTime < maxTime; hitObjectsStart--) { } for (hitObjectsEnd = Beatmap.HitObjects.Count(x => x.StartTime <= hitObject.StartTime) - 1; hitObjectsEnd < Beatmap.HitObjects.Count && bounds.Contains(Beatmap.HitObjects[hitObjectsEnd].Location.ToPointF()) && Beatmap.HitObjects[hitObjectsEnd].StartTime - hitObject.StartTime < maxTime; hitObjectsEnd++) { } for (replayFramesStart = Replay.ReplayFrames.Count(x => x.Time <= Beatmap.HitObjects[hitObjectsStart + 1].StartTime); replayFramesStart > 1 && replayFramesStart < Replay.ReplayFrames.Count && bounds.Contains(Replay.ReplayFrames[replayFramesStart].GetPointF()) && hitObject.StartTime - Replay.ReplayFrames[replayFramesStart].Time < maxTime; replayFramesStart--) { } for (replayFramesEnd = Replay.ReplayFrames.Count(x => x.Time <= Beatmap.HitObjects[hitObjectsEnd - 1].StartTime); replayFramesEnd < Replay.ReplayFrames.Count - 1 && bounds.Contains(Replay.ReplayFrames[replayFramesEnd].GetPointF()) && Replay.ReplayFrames[replayFramesEnd].Time - hitObject.StartTime < maxTime; replayFramesEnd++) { } p.Color = Color.DarkGray; g.DrawRectangle(p, Rectangle.Round(ScaleToRect(new RectangleF(pSub(new PointF(0, hr? 384 : 0), bounds, hr), new SizeF(512, 384)), bounds, area))); p.Color = Color.Gray; for (int q = hitObjectsEnd - 1; q > hitObjectsStart; q--) { int c = Math.Min(255, 100 + (int)(Math.Abs(Beatmap.HitObjects[q].StartTime - hitObject.StartTime) * 100 / maxTime)); if (Beatmap.HitObjects[q].Type.HasFlag(HitObjectType.Slider)) { SliderObject slider = (SliderObject)Beatmap.HitObjects[q]; PointF[] pt = slider.Curves.SelectMany(curve => curve.CurveSnapshots).Select(s => ScaleToRect( pSub(s.point.ToPointF(), bounds, hr), bounds, area)).ToArray(); circle.Color = Color.FromArgb(80, Color.DarkGoldenrod); g.DrawLines(circle, pt); } p.Color = Color.FromArgb(c == 100 ? c + 50 : c, c, c); if (HitCircleOutlines) { g.DrawEllipse(p, ScaleToRect(new RectangleF(PointF.Subtract( pSub(Beatmap.HitObjects[q].Location.ToPointF(), bounds, hr), new SizeF(radius, radius).ToSize()), new SizeF(radius * 2, radius * 2)), bounds, area)); } else { g.FillEllipse(p.Brush, ScaleToRect(new RectangleF(PointF.Subtract( pSub(Beatmap.HitObjects[q].Location.ToPointF(), bounds, hr), new SizeF(radius, radius).ToSize()), new SizeF(radius * 2, radius * 2)), bounds, area)); } } float distance = 10.0001f; for (int k = replayFramesStart; k < replayFramesEnd - 2; k++) { PointF p1 = pSub(Replay.ReplayFrames[k].GetPointF(), bounds, hr); PointF p2 = pSub(Replay.ReplayFrames[k + 1].GetPointF(), bounds, hr); p.Color = GetHitColor(Beatmap.OverallDifficulty, (int)(hitObject.StartTime - Replay.ReplayFrames[k].Time)); g.DrawLine(p, ScaleToRect(p1, bounds, area), ScaleToRect(p2, bounds, area)); if (distance > 10 && Math.Abs(hitObject.StartTime - Replay.ReplayFrames[k + 1].Time) > 50) { Point2 v1 = new Point2(p1.X - p2.X, p1.Y - p2.Y); if (v1.Length > 0) { v1.Normalize(); v1 *= (float)(Math.Sqrt(2) * arrowLength / 2); PointF p3 = PointF.Add(p2, new SizeF(v1.X + v1.Y, v1.Y - v1.X)); PointF p4 = PointF.Add(p2, new SizeF(v1.X - v1.Y, v1.X + v1.Y)); p2 = ScaleToRect(p2, bounds, area); p3 = ScaleToRect(p3, bounds, area); p4 = ScaleToRect(p4, bounds, area); g.DrawLine(p, p2, p3); g.DrawLine(p, p2, p4); } distance = 0; } else { distance += new Point2(p1.X - p2.X, p1.Y - p2.Y).Length; } if (ReplayAnalyzer.getKey(k == 0 ? ReplayAPI.Keys.None : Replay.ReplayFrames[k - 1].Keys, Replay.ReplayFrames[k].Keys) > 0) { g.DrawEllipse(p, ScaleToRect(new RectangleF(PointF.Subtract(p1, new Size(3, 3)), new Size(6, 6)), bounds, area)); } } p.Color = Color.Black; Font f = new Font(FontFamily.GenericSansSerif, 12); g.DrawString(Beatmap.ToString(), f, p.Brush, 0, 0); if (drawAllHitObjects) { g.DrawString($"Object {num + 1} of {Beatmap.HitObjects.Count}", f, p.Brush, 0, f.Height); } else { g.DrawString($"Miss {num + 1} of {MissCount}", f, p.Brush, 0, f.Height); } float time = hitObject.StartTime; if (Replay.Mods.HasFlag(Mods.DoubleTime)) { time /= 1.5f; } else if (Replay.Mods.HasFlag(Mods.HalfTime)) { time /= 0.75f; } TimeSpan ts = TimeSpan.FromMilliseconds(time); g.DrawString($"Time: {ts:mm\\:ss\\.fff}", f, p.Brush, 0, area.Height - f.Height); return(img); }