mirror of
https://github.com/SK-la/Ez2Lazer.git
synced 2026-03-15 03:20:27 +00:00
[Mod]LP mod增加3模式通用版
This commit is contained in:
@@ -12,6 +12,7 @@ using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.LAsEzExtensions.Mods;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Difficulty;
|
||||
@@ -113,6 +114,13 @@ namespace osu.Game.Rulesets.Catch
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ModType.LA_Mod:
|
||||
return new Mod[]
|
||||
{
|
||||
new ModNiceBPM(),
|
||||
new UniversalLoopPlayClip(),
|
||||
};
|
||||
|
||||
case ModType.DifficultyReduction:
|
||||
return new Mod[]
|
||||
{
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.LAsEzExtensions.Configuration;
|
||||
using osu.Game.LAsEzExtensions.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.EzOsu.Mods
|
||||
{
|
||||
public class OsuModLoopPlayClip : ModLoopPlayClip,
|
||||
IApplicableAfterBeatmapConversion
|
||||
{
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
Seed.Value ??= RNG.Next();
|
||||
var rng = new Random((int)Seed.Value);
|
||||
|
||||
var osuBeatmap = (OsuBeatmap)beatmap;
|
||||
|
||||
osuBeatmap.Breaks.Clear();
|
||||
|
||||
var (cutTimeStart, cutTimeEnd, length) = ResolveSliceTimesForBeatmap(beatmap);
|
||||
|
||||
// compute break time (ms) from quarter-beat multiples at slice start
|
||||
double breakTime;
|
||||
|
||||
try
|
||||
{
|
||||
var timing = beatmap.ControlPointInfo.TimingPointAt(cutTimeStart);
|
||||
double quarterMs = timing.BeatLength / 4.0;
|
||||
breakTime = quarterMs * Math.Max(1, BreakQuarter.Value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
breakTime = 250 * Math.Max(1, BreakQuarter.Value);
|
||||
}
|
||||
|
||||
var selectedPart = osuBeatmap.HitObjects.Where(h => h.StartTime > cutTimeStart && h.GetEndTime() < cutTimeEnd).ToList();
|
||||
|
||||
var newPart = new List<OsuHitObject>();
|
||||
|
||||
for (int timeIndex = 0; timeIndex < LoopCount.Value; timeIndex++)
|
||||
{
|
||||
double offset = timeIndex * (breakTime + length);
|
||||
|
||||
foreach (var note in selectedPart)
|
||||
{
|
||||
double baseOffset = offset - cutTimeStart;
|
||||
|
||||
if (note is HitCircle circle)
|
||||
{
|
||||
var nc = new HitCircle
|
||||
{
|
||||
StartTime = circle.StartTime + baseOffset,
|
||||
Samples = circle.Samples.ToList(),
|
||||
Position = circle.Position,
|
||||
};
|
||||
|
||||
newPart.Add(nc);
|
||||
}
|
||||
else if (note is Slider slider)
|
||||
{
|
||||
var ns = new Slider
|
||||
{
|
||||
StartTime = slider.StartTime + baseOffset,
|
||||
Path = slider.Path,
|
||||
RepeatCount = slider.RepeatCount,
|
||||
Samples = slider.Samples.ToList(),
|
||||
Position = slider.Position,
|
||||
SliderVelocityMultiplier = slider.SliderVelocityMultiplier,
|
||||
};
|
||||
|
||||
if (slider.NodeSamples != null)
|
||||
ns.NodeSamples = slider.NodeSamples.Select(n => (IList<HitSampleInfo>)n.ToList()).ToList();
|
||||
|
||||
newPart.Add(ns);
|
||||
}
|
||||
else if (note is Spinner spinner)
|
||||
{
|
||||
var ns = new Spinner
|
||||
{
|
||||
StartTime = spinner.StartTime + baseOffset,
|
||||
Duration = spinner.Duration,
|
||||
};
|
||||
|
||||
newPart.Add(ns);
|
||||
}
|
||||
else
|
||||
{
|
||||
// fallback: attempt to create a shallow instance of the same type and copy StartTime/Samples
|
||||
var type = note.GetType();
|
||||
|
||||
try
|
||||
{
|
||||
var inst = (OsuHitObject?)Activator.CreateInstance(type);
|
||||
|
||||
if (inst != null)
|
||||
{
|
||||
inst.StartTime = note.StartTime + baseOffset;
|
||||
inst.Samples = note.Samples.ToList();
|
||||
newPart.Add(inst);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore and fallback to skipping
|
||||
}
|
||||
|
||||
// As a last resort, skip adding this unknown object to avoid corrupting timing data.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure derived timing (slider velocity/endtime, nested objects) are populated
|
||||
foreach (var h in newPart)
|
||||
{
|
||||
try
|
||||
{
|
||||
h.ApplyDefaults(osuBeatmap.ControlPointInfo, osuBeatmap.Difficulty);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore per-object apply failures to avoid breaking the whole mod application.
|
||||
}
|
||||
}
|
||||
|
||||
osuBeatmap.HitObjects = newPart;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Difficulty;
|
||||
using osu.Game.Rulesets.Osu.Edit;
|
||||
using osu.Game.Rulesets.Osu.Edit.Setup;
|
||||
using osu.Game.Rulesets.Osu.EzOsu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
@@ -167,7 +166,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
return new Mod[]
|
||||
{
|
||||
new ModNiceBPM(),
|
||||
new OsuModLoopPlayClip(),
|
||||
new UniversalLoopPlayClip(),
|
||||
};
|
||||
|
||||
case ModType.DifficultyReduction:
|
||||
|
||||
@@ -34,6 +34,7 @@ using osu.Game.Screens.Ranking.Statistics;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.LAsEzExtensions.Mods;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
using osu.Game.Rulesets.Taiko.Configuration;
|
||||
@@ -131,6 +132,13 @@ namespace osu.Game.Rulesets.Taiko
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ModType.LA_Mod:
|
||||
return new Mod[]
|
||||
{
|
||||
new ModNiceBPM(),
|
||||
new UniversalLoopPlayClip(),
|
||||
};
|
||||
|
||||
case ModType.DifficultyReduction:
|
||||
return new Mod[]
|
||||
{
|
||||
|
||||
@@ -57,8 +57,11 @@ namespace osu.Game.LAsEzExtensions.Analysis
|
||||
double interval = 240000.0 / bpm; // 4拍的时间间隔(毫秒)
|
||||
double songEndTime = hitObjects[^1].StartTime;
|
||||
|
||||
// 预分配List容量以避免频繁扩容
|
||||
// 预分配List容量以避免频繁扩容(防御性处理 NaN/Infinity/负数)
|
||||
int estimatedIntervals = (int)((songEndTime / interval) + 1);
|
||||
if (double.IsNaN(songEndTime) || double.IsInfinity(songEndTime) || double.IsNaN(interval) || double.IsInfinity(interval))
|
||||
estimatedIntervals = 0;
|
||||
estimatedIntervals = Math.Max(0, estimatedIntervals);
|
||||
var kpsList = new List<double>(estimatedIntervals);
|
||||
|
||||
// 缓存hitObjects数组以提高访问性能
|
||||
@@ -242,6 +245,9 @@ namespace osu.Game.LAsEzExtensions.Analysis
|
||||
double songEndTime = hitObjects[^1].StartTime;
|
||||
|
||||
int estimatedIntervals = (int)((songEndTime / interval) + 1);
|
||||
if (double.IsNaN(songEndTime) || double.IsInfinity(songEndTime) || double.IsNaN(interval) || double.IsInfinity(interval))
|
||||
estimatedIntervals = 0;
|
||||
estimatedIntervals = Math.Max(0, estimatedIntervals);
|
||||
var kpsList = new List<double>(estimatedIntervals);
|
||||
var columnCounts = new Dictionary<int, int>();
|
||||
var holdNoteCounts = new Dictionary<int, int>();
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace osu.Game.LAsEzExtensions.Mods
|
||||
// LAsMods - Mod Descriptions
|
||||
// ====================================================================================================
|
||||
|
||||
public static readonly LocalisableString LoopPlayClip_Description = new EzLocalisableString("将谱面切割成片段用于循环练习。(原版是YuLiangSSS的Duplicate Mod)",
|
||||
public static readonly LocalisableString LoopPlayClip_Description = new EzLocalisableString("将谱面切割成片段用于循环练习。",
|
||||
"Cut the beatmap into a clip for loop practice. (The original is YuLiangSSS's Duplicate Mod)");
|
||||
|
||||
// SpaceBody
|
||||
|
||||
231
osu.Game/LAsEzExtensions/Mods/UniversalLoopPlayClip.cs
Normal file
231
osu.Game/LAsEzExtensions/Mods/UniversalLoopPlayClip.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
// Universal loop play clip mod that applies to arbitrary rulesets using best-effort cloning.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.LAsEzExtensions.Mods
|
||||
{
|
||||
public class UniversalLoopPlayClip : ModLoopPlayClip,
|
||||
IApplicableAfterBeatmapConversion
|
||||
{
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
var (cutTimeStart, cutTimeEnd, _) = ResolveSliceTimesForBeatmap(beatmap);
|
||||
|
||||
ApplyLoopToBeatmapStatic(beatmap, LoopCount.Value, cutTimeStart, cutTimeEnd, BreakQuarter.Value, Seed.Value);
|
||||
}
|
||||
|
||||
public static void ApplyLoopToBeatmapStatic(IBeatmap beatmap, int loopCount, double cutTimeStart, double cutTimeEnd, int breakQuarter, int? seed = null)
|
||||
{
|
||||
if (beatmap == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
var breaksProp = beatmap.GetType().GetProperty("Breaks");
|
||||
if (breaksProp != null && breaksProp.CanWrite)
|
||||
{
|
||||
var breaks = breaksProp.GetValue(beatmap) as System.Collections.IList;
|
||||
breaks?.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
beatmap.Breaks.Clear();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
double breakTime;
|
||||
try
|
||||
{
|
||||
var timing = beatmap.ControlPointInfo.TimingPointAt(cutTimeStart);
|
||||
double quarterMs = timing.BeatLength / 4.0;
|
||||
breakTime = quarterMs * Math.Max(1, breakQuarter);
|
||||
}
|
||||
catch
|
||||
{
|
||||
breakTime = 250 * Math.Max(1, breakQuarter);
|
||||
}
|
||||
|
||||
var selectedPart = beatmap.HitObjects.Where(h => h.StartTime > cutTimeStart && h.GetEndTime() < cutTimeEnd).ToList();
|
||||
|
||||
var newPart = new List<HitObject>();
|
||||
|
||||
var rng = seed.HasValue ? new Random((int)seed.Value) : new Random();
|
||||
|
||||
double length = cutTimeEnd - cutTimeStart;
|
||||
|
||||
for (int timeIndex = 0; timeIndex < loopCount; timeIndex++)
|
||||
{
|
||||
double offset = timeIndex * (breakTime + length);
|
||||
|
||||
foreach (var note in selectedPart)
|
||||
{
|
||||
double baseOffset = offset - cutTimeStart;
|
||||
|
||||
var type = note.GetType();
|
||||
|
||||
try
|
||||
{
|
||||
var inst = (HitObject?)Activator.CreateInstance(type);
|
||||
|
||||
if (inst != null)
|
||||
{
|
||||
// StartTime
|
||||
var startProp = type.GetProperty("StartTime");
|
||||
if (startProp != null && startProp.CanWrite)
|
||||
startProp.SetValue(inst, note.StartTime + baseOffset);
|
||||
else
|
||||
inst.StartTime = note.StartTime + baseOffset;
|
||||
|
||||
// Samples
|
||||
var samplesProp = type.GetProperty("Samples");
|
||||
if (samplesProp != null && samplesProp.CanWrite)
|
||||
{
|
||||
try
|
||||
{
|
||||
var s = note.Samples?.ToList();
|
||||
samplesProp.SetValue(inst, s);
|
||||
}
|
||||
catch
|
||||
{
|
||||
inst.Samples = note.Samples?.ToList();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
inst.Samples = note.Samples?.ToList();
|
||||
}
|
||||
|
||||
newPart.Add(inst);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// skip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var h in newPart)
|
||||
{
|
||||
try
|
||||
{
|
||||
h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
var propHitObjects = beatmap.GetType().GetProperty("HitObjects");
|
||||
if (propHitObjects != null && propHitObjects.CanWrite)
|
||||
{
|
||||
// Ensure we assign a list whose element type matches the property's generic argument
|
||||
var propType = propHitObjects.PropertyType;
|
||||
if (propType.IsGenericType)
|
||||
{
|
||||
var elementType = propType.GetGenericArguments()[0];
|
||||
|
||||
if (elementType == typeof(HitObject))
|
||||
{
|
||||
propHitObjects.SetValue(beatmap, newPart);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var listType = typeof(List<>).MakeGenericType(elementType);
|
||||
var listInstance = (System.Collections.IList)Activator.CreateInstance(listType)!;
|
||||
|
||||
foreach (var h in newPart)
|
||||
{
|
||||
if (h == null) continue;
|
||||
|
||||
if (elementType.IsAssignableFrom(h.GetType()))
|
||||
{
|
||||
listInstance.Add(h);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Attempt best-effort conversion: create instance of elementType and copy common fields.
|
||||
try
|
||||
{
|
||||
var target = Activator.CreateInstance(elementType) as HitObject;
|
||||
if (target != null)
|
||||
{
|
||||
var startProp = elementType.GetProperty("StartTime");
|
||||
if (startProp != null && startProp.CanWrite)
|
||||
startProp.SetValue(target, h.StartTime);
|
||||
else
|
||||
target.StartTime = h.StartTime;
|
||||
|
||||
var samplesProp = elementType.GetProperty("Samples");
|
||||
if (samplesProp != null && samplesProp.CanWrite)
|
||||
{
|
||||
try { samplesProp.SetValue(target, h.Samples?.ToList()); }
|
||||
catch { target.Samples = h.Samples?.ToList(); }
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Samples = h.Samples?.ToList();
|
||||
}
|
||||
|
||||
listInstance.Add(target);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore conversion failure for this element
|
||||
}
|
||||
}
|
||||
|
||||
propHitObjects.SetValue(beatmap, listInstance);
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// fallthrough to generic assignment attempt below
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: try to set directly (may fail if types mismatch)
|
||||
try
|
||||
{
|
||||
propHitObjects.SetValue(beatmap, newPart);
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// continue to other fallbacks
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
dynamic dyn = beatmap;
|
||||
dyn.HitObjects = newPart;
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
var current = beatmap.HitObjects as IList<HitObject>;
|
||||
if (current != null)
|
||||
{
|
||||
current.Clear();
|
||||
foreach (var h in newPart) current.Add(h);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user