1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-22 04:32:55 +08:00

Merge branch 'master' into taiko-explosion-rework

This commit is contained in:
Bartłomiej Dach 2020-09-28 16:32:15 +02:00
commit 40dea0e2db
22 changed files with 244 additions and 67 deletions

View File

@ -31,6 +31,9 @@ namespace osu.Game.Rulesets.Catch.Replays
public override Replay Generate() public override Replay Generate()
{ {
if (Beatmap.HitObjects.Count == 0)
return Replay;
// todo: add support for HT DT // todo: add support for HT DT
const double dash_speed = Catcher.BASE_SPEED; const double dash_speed = Catcher.BASE_SPEED;
const double movement_speed = dash_speed / 2; const double movement_speed = dash_speed / 2;

View File

@ -46,6 +46,9 @@ namespace osu.Game.Rulesets.Mania.Replays
public override Replay Generate() public override Replay Generate()
{ {
if (Beatmap.HitObjects.Count == 0)
return Replay;
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
var actions = new List<ManiaAction>(); var actions = new List<ManiaAction>();

View File

@ -15,6 +15,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{ {
private readonly ManualSliderBody body; private readonly ManualSliderBody body;
/// <summary>
/// Offset in absolute (local) coordinates from the start of the curve.
/// </summary>
public Vector2 PathStartLocation => body.PathOffset;
public SliderBodyPiece() public SliderBodyPiece()
{ {
InternalChild = body = new ManualSliderBody InternalChild = body = new ManualSliderBody

View File

@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
}; };
public override Vector2 ScreenSpaceSelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre; public override Vector2 ScreenSpaceSelectionPoint => BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation);
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);

View File

@ -8,6 +8,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osuTK; using osuTK;
@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay. /// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points. /// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
/// </summary> /// </summary>
private const double editor_hit_object_fade_out_extension = 500; private const double editor_hit_object_fade_out_extension = 700;
public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods) public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods)
: base(ruleset, beatmap, mods) : base(ruleset, beatmap, mods)
@ -32,11 +33,30 @@ namespace osu.Game.Rulesets.Osu.Edit
private void updateState(DrawableHitObject hitObject, ArmedState state) private void updateState(DrawableHitObject hitObject, ArmedState state)
{ {
switch (state) if (state == ArmedState.Idle)
return;
// adjust the visuals of certain object types to make them stay on screen for longer than usual.
switch (hitObject)
{ {
case ArmedState.Miss: default:
// there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.)
return;
case DrawableSlider _:
// no specifics to sliders but let them fade slower below.
break;
case DrawableHitCircle circle: // also handles slider heads
circle.ApproachCircle
.FadeOutFromOne(editor_hit_object_fade_out_extension)
.Expire();
break;
}
// Get the existing fade out transform // Get the existing fade out transform
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
if (existing == null) if (existing == null)
return; return;
@ -44,8 +64,6 @@ namespace osu.Game.Rulesets.Osu.Edit
using (hitObject.BeginAbsoluteSequence(existing.StartTime)) using (hitObject.BeginAbsoluteSequence(existing.StartTime))
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
break;
}
} }
protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor(); protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor();

View File

@ -3,7 +3,6 @@
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -93,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
base.LoadComplete(); base.LoadComplete();
drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200)); drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200));
drawableSpinner.State.BindValueChanged(updateStateTransforms, true); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
} }
protected override void Update() protected override void Update()
@ -123,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation; mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation;
} }
private void updateStateTransforms(ValueChangedEvent<ArmedState> state) private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{ {
centre.ScaleTo(0); centre.ScaleTo(0);
mainContainer.ScaleTo(0); mainContainer.ScaleTo(0);
@ -144,11 +143,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
} }
// transforms we have from completing the spinner will be rolled back, so reapply immediately. // transforms we have from completing the spinner will be rolled back, so reapply immediately.
updateComplete(state.NewValue == ArmedState.Hit, 0); updateComplete(state == ArmedState.Hit, 0);
using (BeginDelayedSequence(spinner.Duration, true)) using (BeginDelayedSequence(spinner.Duration, true))
{ {
switch (state.NewValue) switch (state)
{ {
case ArmedState.Hit: case ArmedState.Hit:
this.ScaleTo(Scale * 1.2f, 320, Easing.Out); this.ScaleTo(Scale * 1.2f, 320, Easing.Out);
@ -185,5 +184,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return true; return true;
} }
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (drawableSpinner != null)
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
}
} }
} }

View File

@ -72,6 +72,9 @@ namespace osu.Game.Rulesets.Osu.Replays
public override Replay Generate() public override Replay Generate()
{ {
if (Beatmap.HitObjects.Count == 0)
return Replay;
buttonIndex = 0; buttonIndex = 0;
AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500))); AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500)));

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -72,10 +71,10 @@ namespace osu.Game.Rulesets.Osu.Skinning
base.LoadComplete(); base.LoadComplete();
this.FadeOut(); this.FadeOut();
drawableSpinner.State.BindValueChanged(updateStateTransforms, true); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
} }
private void updateStateTransforms(ValueChangedEvent<ArmedState> state) private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{ {
var spinner = (Spinner)drawableSpinner.HitObject; var spinner = (Spinner)drawableSpinner.HitObject;
@ -95,5 +94,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
Scale = new Vector2(final_scale * (0.8f + (float)Interpolation.ApplyEasing(Easing.Out, drawableSpinner.Progress) * 0.2f)); Scale = new Vector2(final_scale * (0.8f + (float)Interpolation.ApplyEasing(Easing.Out, drawableSpinner.Progress) * 0.2f));
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (drawableSpinner != null)
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
}
} }
} }

View File

@ -3,7 +3,6 @@
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -86,10 +85,10 @@ namespace osu.Game.Rulesets.Osu.Skinning
base.LoadComplete(); base.LoadComplete();
this.FadeOut(); this.FadeOut();
drawableSpinner.State.BindValueChanged(updateStateTransforms, true); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
} }
private void updateStateTransforms(ValueChangedEvent<ArmedState> state) private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{ {
var spinner = drawableSpinner.HitObject; var spinner = drawableSpinner.HitObject;
@ -127,5 +126,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
return (float)barCount / total_bars * final_metre_height; return (float)barCount / total_bars * final_metre_height;
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (drawableSpinner != null)
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
}
} }
} }

View File

@ -57,7 +57,13 @@ namespace osu.Game.Rulesets.Taiko.Edit
ChangeHandler.BeginChange(); ChangeHandler.BeginChange();
foreach (var h in hits) foreach (var h in hits)
{
if (h.IsStrong != state)
{
h.IsStrong = state; h.IsStrong = state;
EditorBeatmap.UpdateHitObject(h);
}
}
ChangeHandler.EndChange(); ChangeHandler.EndChange();
} }

View File

@ -30,6 +30,9 @@ namespace osu.Game.Rulesets.Taiko.Replays
public override Replay Generate() public override Replay Generate()
{ {
if (Beatmap.HitObjects.Count == 0)
return Replay;
bool hitButton = true; bool hitButton = true;
Frames.Add(new TaikoReplayFrame(-100000)); Frames.Add(new TaikoReplayFrame(-100000));

View File

@ -2,7 +2,9 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
namespace osu.Game.Tests.Editing namespace osu.Game.Tests.Editing
@ -13,11 +15,12 @@ namespace osu.Game.Tests.Editing
[Test] [Test]
public void TestSaveRestoreState() public void TestSaveRestoreState()
{ {
var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); var (handler, beatmap) = createChangeHandler();
Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.False);
addArbitraryChange(beatmap);
handler.SaveState(); handler.SaveState();
Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanUndo.Value, Is.True);
@ -29,15 +32,48 @@ namespace osu.Game.Tests.Editing
Assert.That(handler.CanRedo.Value, Is.True); Assert.That(handler.CanRedo.Value, Is.True);
} }
[Test]
public void TestSaveSameStateDoesNotSave()
{
var (handler, beatmap) = createChangeHandler();
Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.False);
addArbitraryChange(beatmap);
handler.SaveState();
Assert.That(handler.CanUndo.Value, Is.True);
Assert.That(handler.CanRedo.Value, Is.False);
string hash = handler.CurrentStateHash;
// save a save without making any changes
handler.SaveState();
Assert.That(hash, Is.EqualTo(handler.CurrentStateHash));
handler.RestoreState(-1);
Assert.That(hash, Is.Not.EqualTo(handler.CurrentStateHash));
// we should only be able to restore once even though we saved twice.
Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.True);
}
[Test] [Test]
public void TestMaxStatesSaved() public void TestMaxStatesSaved()
{ {
var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); var (handler, beatmap) = createChangeHandler();
Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanUndo.Value, Is.False);
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++)
{
addArbitraryChange(beatmap);
handler.SaveState(); handler.SaveState();
}
Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanUndo.Value, Is.True);
@ -53,12 +89,15 @@ namespace osu.Game.Tests.Editing
[Test] [Test]
public void TestMaxStatesExceeded() public void TestMaxStatesExceeded()
{ {
var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); var (handler, beatmap) = createChangeHandler();
Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanUndo.Value, Is.False);
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES * 2; i++) for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES * 2; i++)
{
addArbitraryChange(beatmap);
handler.SaveState(); handler.SaveState();
}
Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanUndo.Value, Is.True);
@ -70,5 +109,17 @@ namespace osu.Game.Tests.Editing
Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanUndo.Value, Is.False);
} }
private (EditorChangeHandler, EditorBeatmap) createChangeHandler()
{
var beatmap = new EditorBeatmap(new Beatmap());
return (new EditorChangeHandler(beatmap), beatmap);
}
private void addArbitraryChange(EditorBeatmap beatmap)
{
beatmap.Add(new HitCircle { StartTime = RNG.Next(0, 100000) });
}
} }
} }

View File

@ -12,6 +12,14 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer; protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer;
public override void SetUpSteps()
{
base.SetUpSteps();
AddUntilStep("gameplay has started",
() => Player.GameplayClockContainer.GameplayClock.CurrentTime > Player.DrawableRuleset.GameplayStartTime);
}
[Test] [Test]
public void TestGameplayOverlayActivation() public void TestGameplayOverlayActivation()
{ {
@ -21,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestGameplayOverlayActivationPaused() public void TestGameplayOverlayActivationPaused()
{ {
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("pause gameplay", () => Player.Pause()); AddStep("pause gameplay", () => Player.Pause());
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
} }

View File

@ -112,6 +112,9 @@ namespace osu.Game.Graphics.Containers
CornerRadius = 5; CornerRadius = 5;
// needs to be set initially for the ResizeTo to respect minimum size
Size = new Vector2(SCROLL_BAR_HEIGHT);
const float margin = 3; const float margin = 3;
Margin = new MarginPadding Margin = new MarginPadding

View File

@ -45,15 +45,21 @@ namespace osu.Game.Rulesets.Edit
base.LoadComplete(); base.LoadComplete();
beatmap.HitObjectAdded += addHitObject; beatmap.HitObjectAdded += addHitObject;
beatmap.HitObjectUpdated += updateReplay;
beatmap.HitObjectRemoved += removeHitObject; beatmap.HitObjectRemoved += removeHitObject;
} }
private void updateReplay(HitObject obj = null) =>
drawableRuleset.RegenerateAutoplay();
private void addHitObject(HitObject hitObject) private void addHitObject(HitObject hitObject)
{ {
var drawableObject = drawableRuleset.CreateDrawableRepresentation((TObject)hitObject); var drawableObject = drawableRuleset.CreateDrawableRepresentation((TObject)hitObject);
drawableRuleset.Playfield.Add(drawableObject); drawableRuleset.Playfield.Add(drawableObject);
drawableRuleset.Playfield.PostProcess(); drawableRuleset.Playfield.PostProcess();
updateReplay();
} }
private void removeHitObject(HitObject hitObject) private void removeHitObject(HitObject hitObject)
@ -62,6 +68,8 @@ namespace osu.Game.Rulesets.Edit
drawableRuleset.Playfield.Remove(drawableObject); drawableRuleset.Playfield.Remove(drawableObject);
drawableRuleset.Playfield.PostProcess(); drawableRuleset.Playfield.PostProcess();
drawableRuleset.RegenerateAutoplay();
} }
public override bool PropagatePositionalInputSubTree => false; public override bool PropagatePositionalInputSubTree => false;

View File

@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Edit
try try
{ {
drawableRulesetWrapper = new DrawableEditRulesetWrapper<TObject>(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap)) drawableRulesetWrapper = new DrawableEditRulesetWrapper<TObject>(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap, new[] { Ruleset.GetAutoplayMod() }))
{ {
Clock = EditorClock, Clock = EditorClock,
ProcessCustomClock = false ProcessCustomClock = false

View File

@ -151,8 +151,11 @@ namespace osu.Game.Rulesets.UI
public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer();
[Resolved]
private OsuConfigManager config { get; set; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config, CancellationToken? cancellationToken) private void load(CancellationToken? cancellationToken)
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
@ -178,11 +181,18 @@ namespace osu.Game.Rulesets.UI
.WithChild(ResumeOverlay))); .WithChild(ResumeOverlay)));
} }
applyRulesetMods(Mods, config); RegenerateAutoplay();
loadObjects(cancellationToken); loadObjects(cancellationToken);
} }
public void RegenerateAutoplay()
{
// for now this is applying mods which aren't just autoplay.
// we'll need to reconsider this flow in the future.
applyRulesetMods(Mods, config);
}
/// <summary> /// <summary>
/// Creates and adds drawable representations of hit objects to the play field. /// Creates and adds drawable representations of hit objects to the play field.
/// </summary> /// </summary>

View File

@ -123,9 +123,8 @@ namespace osu.Game.Rulesets.UI
try try
{ {
if (!FrameStablePlayback) if (FrameStablePlayback)
return; {
if (firstConsumption) if (firstConsumption)
{ {
// On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour.
@ -145,18 +144,43 @@ namespace osu.Game.Rulesets.UI
? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time)
: Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time);
} }
}
if (isAttached) if (isAttached)
{ {
double? newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime); double? newTime;
if (FrameStablePlayback)
{
// when stability is turned on, we shouldn't execute for time values the replay is unable to satisfy.
if ((newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime)) == null)
{
// setting invalid state here ensures that gameplay will not continue (ie. our child
// hierarchy won't be updated).
validState = false;
// potentially loop to catch-up playback.
requireMoreUpdateLoops = true;
return;
}
}
else
{
// when stability is disabled, we don't really care about accuracy.
// looping over the replay will allow it to catch up and feed out the required values
// for the current time.
while ((newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime)) != newProposedTime)
{
if (newTime == null) if (newTime == null)
{ {
// we shouldn't execute for this time value. probably waiting on more replay data. // special case for when the replay actually can't arrive at the required time.
// protects from potential endless loop.
validState = false; validState = false;
requireMoreUpdateLoops = true;
return; return;
} }
}
}
newProposedTime = newTime.Value; newProposedTime = newTime.Value;
} }

View File

@ -201,6 +201,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (isDraggingBlueprint) if (isDraggingBlueprint)
{ {
// handle positional change etc.
foreach (var obj in selectedHitObjects)
Beatmap.UpdateHitObject(obj);
changeHandler?.EndChange(); changeHandler?.EndChange();
isDraggingBlueprint = false; isDraggingBlueprint = false;
} }

View File

@ -212,6 +212,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (blueprint != null) if (blueprint != null)
{ {
// doing this post-creations as adding the default hit sample should be the case regardless of the ruleset.
blueprint.HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_NORMAL });
placementBlueprintContainer.Child = currentPlacement = blueprint; placementBlueprintContainer.Child = currentPlacement = blueprint;
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame

View File

@ -288,8 +288,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
var comboInfo = h as IHasComboInformation; var comboInfo = h as IHasComboInformation;
if (comboInfo == null) if (comboInfo == null || comboInfo.NewCombo == state) continue;
continue;
comboInfo.NewCombo = state; comboInfo.NewCombo = state;
EditorBeatmap?.UpdateHitObject(h); EditorBeatmap?.UpdateHitObject(h);

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions; using osu.Framework.Extensions;
@ -89,24 +90,28 @@ namespace osu.Game.Screens.Edit
if (isRestoring) if (isRestoring)
return; return;
using (var stream = new MemoryStream())
{
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
new LegacyBeatmapEncoder(editorBeatmap, editorBeatmap.BeatmapSkin).Encode(sw);
var newState = stream.ToArray();
// if the previous state is binary equal we don't need to push a new one, unless this is the initial state.
if (savedStates.Count > 0 && newState.SequenceEqual(savedStates.Last())) return;
if (currentState < savedStates.Count - 1) if (currentState < savedStates.Count - 1)
savedStates.RemoveRange(currentState + 1, savedStates.Count - currentState - 1); savedStates.RemoveRange(currentState + 1, savedStates.Count - currentState - 1);
if (savedStates.Count > MAX_SAVED_STATES) if (savedStates.Count > MAX_SAVED_STATES)
savedStates.RemoveAt(0); savedStates.RemoveAt(0);
using (var stream = new MemoryStream()) savedStates.Add(newState);
{
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
new LegacyBeatmapEncoder(editorBeatmap, editorBeatmap.BeatmapSkin).Encode(sw);
savedStates.Add(stream.ToArray());
}
currentState = savedStates.Count - 1; currentState = savedStates.Count - 1;
updateBindables(); updateBindables();
} }
}
/// <summary> /// <summary>
/// Restores an older or newer state. /// Restores an older or newer state.