1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-15 00:02:54 +08:00

Merge branch 'master' into fix-taiko-conversion

This commit is contained in:
Dean Herbert 2018-03-06 01:16:47 +09:00
commit 7b18e8a5ca
20 changed files with 154 additions and 100 deletions

@ -1 +1 @@
Subproject commit e8ae207769ec26fb7ddd67a2433514fcda354ecd
Subproject commit 71900dc350bcebbb60d912d4023a1d2a6bbbc3c1

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects
SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5));
// spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being.
SpinsRequired = (int)(SpinsRequired * 0.6);
SpinsRequired = (int)Math.Max(1, SpinsRequired * 0.6);
}
}
}

View File

@ -82,8 +82,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return;
int countHit = NestedHitObjects.Count(o => o.IsHit);
if (countHit > HitObject.RequiredGoodHits)
if (countHit >= HitObject.RequiredGoodHits)
{
AddJudgement(new TaikoJudgement { Result = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good });
if (HitObject.IsStrong)

View File

@ -20,7 +20,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeBeatmapGeneral()
{
var decoder = new LegacyBeatmapDecoder();
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
@ -110,7 +110,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeBeatmapEvents()
{
var decoder = new LegacyBeatmapDecoder();
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
@ -128,7 +128,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeBeatmapTimingPoints()
{
var decoder = new LegacyBeatmapDecoder();
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
@ -187,7 +187,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeBeatmapHitObjects()
{
var decoder = new LegacyBeatmapDecoder();
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{

View File

@ -159,7 +159,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var sr = new StreamReader(stream))
{
var legacyDecoded = new LegacyBeatmapDecoder().DecodeBeatmap(sr);
var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.DecodeBeatmap(sr);
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms))
using (var sr2 = new StreamReader(ms))

View File

@ -58,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
Assert.AreEqual("Deif", meta.AuthorString);
Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile);
Assert.AreEqual(164471, meta.PreviewTime);
Assert.AreEqual(164471 + LegacyBeatmapDecoder.UniversalOffset, meta.PreviewTime);
Assert.AreEqual(string.Empty, meta.Source);
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags);
Assert.AreEqual("Renatus", meta.Title);

View File

@ -8,6 +8,7 @@ using OpenTK.Graphics;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Beatmaps.ControlPoints;
using osu.Framework;
namespace osu.Game.Beatmaps.Formats
{
@ -21,6 +22,19 @@ namespace osu.Game.Beatmaps.Formats
private LegacySampleBank defaultSampleBank;
private int defaultSampleVolume = 100;
/// <summary>
/// lazer's audio timings in general doesn't match stable. this is the result of user testing, albeit limited.
/// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
/// </summary>
public static int UniversalOffset => RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? -22 : 0;
/// <summary>
/// Whether or not beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes.
/// </summary>
public bool ApplyOffsets = true;
private readonly int offset = UniversalOffset;
public LegacyBeatmapDecoder()
{
}
@ -28,6 +42,9 @@ namespace osu.Game.Beatmaps.Formats
public LegacyBeatmapDecoder(string header)
{
BeatmapVersion = int.Parse(header.Substring(17));
// BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off)
offset += BeatmapVersion < 5 ? 24 : 0;
}
protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap)
@ -102,7 +119,7 @@ namespace osu.Game.Beatmaps.Formats
beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value);
break;
case @"PreviewTime":
metadata.PreviewTime = int.Parse(pair.Value);
metadata.PreviewTime = getOffsetTime(int.Parse(pair.Value));
break;
case @"Countdown":
beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1;
@ -257,8 +274,8 @@ namespace osu.Game.Beatmaps.Formats
case EventType.Break:
var breakEvent = new BreakPeriod
{
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
StartTime = getOffsetTime(double.Parse(split[1], NumberFormatInfo.InvariantInfo)),
EndTime = getOffsetTime(double.Parse(split[2], NumberFormatInfo.InvariantInfo))
};
if (!breakEvent.HasEffect)
@ -273,7 +290,7 @@ namespace osu.Game.Beatmaps.Formats
{
string[] split = line.Split(',');
double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
double time = getOffsetTime(double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo));
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
@ -396,7 +413,14 @@ namespace osu.Game.Beatmaps.Formats
var obj = parser.Parse(line);
if (obj != null)
{
obj.StartTime = getOffsetTime(obj.StartTime);
beatmap.HitObjects.Add(obj);
}
}
private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0);
private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0);
}
}

View File

@ -66,7 +66,7 @@ namespace osu.Game.Graphics.Containers
{
Vector2 offset = (input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2) * ParallaxAmount;
content.Position = Interpolation.ValueAt(Clock.ElapsedFrameTime, content.Position, offset, 0, 1000, Easing.OutQuint);
content.Position = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000), content.Position, offset, 0, 1000, Easing.OutQuint);
content.Scale = new Vector2(1 + ParallaxAmount);
}

View File

@ -35,8 +35,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
protected virtual IEnumerable<SampleInfo> GetSamples() => HitObject.Samples;
private List<DrawableHitObject> nestedHitObjects;
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects;
private readonly Lazy<List<DrawableHitObject>> nestedHitObjects = new Lazy<List<DrawableHitObject>>();
public bool HasNestedHitObjects => nestedHitObjects.IsValueCreated;
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects.Value;
public event Action<DrawableHitObject, Judgement> OnJudgement;
public event Action<DrawableHitObject, Judgement> OnJudgementRemoved;
@ -52,12 +53,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// <summary>
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been hit.
/// </summary>
public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && (NestedHitObjects?.All(n => n.IsHit) ?? true);
public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && (!HasNestedHitObjects || NestedHitObjects.All(n => n.IsHit));
/// <summary>
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been judged.
/// </summary>
public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (NestedHitObjects?.All(h => h.AllJudged) ?? true);
public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (!HasNestedHitObjects || NestedHitObjects.All(h => h.AllJudged));
/// <summary>
/// Whether this <see cref="DrawableHitObject"/> can be judged.
@ -160,14 +161,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
protected virtual void AddNested(DrawableHitObject h)
{
if (nestedHitObjects == null)
nestedHitObjects = new List<DrawableHitObject>();
h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j);
h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j);
h.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j);
nestedHitObjects.Add(h);
nestedHitObjects.Value.Add(h);
}
/// <summary>
@ -211,7 +209,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (AllJudged)
return false;
if (NestedHitObjects != null)
if (HasNestedHitObjects)
foreach (var d in NestedHitObjects)
judgementOccurred |= d.UpdateJudgement(userTriggered);

View File

@ -91,8 +91,6 @@ namespace osu.Game.Rulesets.UI
#region Clock control
protected override bool ShouldProcessClock => false; // We handle processing the clock ourselves
private ManualClock clock;
private IFrameBasedClock parentClock;
@ -103,6 +101,7 @@ namespace osu.Game.Rulesets.UI
//our clock will now be our parent's clock, but we want to replace this to allow manual control.
parentClock = Clock;
ProcessCustomClock = false;
Clock = new FramedClock(clock = new ManualClock
{
CurrentTime = parentClock.CurrentTime,

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
var controlPoint = controlPointAt(obj.HitObject.StartTime);
obj.LifetimeStart = obj.HitObject.StartTime - timeRange / controlPoint.Multiplier;
if (obj.NestedHitObjects != null)
if (obj.HasNestedHitObjects)
{
ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);

View File

@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
}
}
if (obj.NestedHitObjects != null)
if (obj.HasNestedHitObjects)
{
ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);

View File

@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play
private static bool hasShownNotificationOnce;
public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, DecoupleableInterpolatingFramedClock decoupledClock, WorkingBeatmap working, IAdjustableClock adjustableSourceClock)
public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IClock offsetClock, IAdjustableClock adjustableClock)
{
RelativeSizeAxes = Axes.Both;
@ -66,13 +66,13 @@ namespace osu.Game.Screens.Play
BindRulesetContainer(rulesetContainer);
Progress.Objects = rulesetContainer.Objects;
Progress.AudioClock = decoupledClock;
Progress.AudioClock = offsetClock;
Progress.AllowSeeking = rulesetContainer.HasReplayLoaded;
Progress.OnSeek = pos => decoupledClock.Seek(pos);
Progress.OnSeek = pos => adjustableClock.Seek(pos);
ModDisplay.Current.BindTo(working.Mods);
PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableSourceClock;
PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableClock;
}
[BackgroundDependencyLoader(true)]

View File

@ -44,14 +44,22 @@ namespace osu.Game.Screens.Play
public Action OnResume;
public Action OnPause;
public IAdjustableClock AudioClock;
public FramedClock FramedClock;
private readonly IAdjustableClock adjustableClock;
private readonly FramedClock framedClock;
public PauseContainer()
public PauseContainer(FramedClock framedClock, IAdjustableClock adjustableClock)
{
this.framedClock = framedClock;
this.adjustableClock = adjustableClock;
RelativeSizeAxes = Axes.Both;
AddInternal(content = new Container { RelativeSizeAxes = Axes.Both });
AddInternal(content = new Container
{
Clock = this.framedClock,
ProcessCustomClock = false,
RelativeSizeAxes = Axes.Both
});
AddInternal(pauseOverlay = new PauseOverlay
{
@ -65,47 +73,37 @@ namespace osu.Game.Screens.Play
});
}
public void Pause(bool force = false)
public void Pause(bool force = false) => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called.
{
if (!CanPause && !force) return;
if (IsPaused) return;
// stop the decoupled clock (stops the audio eventually)
AudioClock.Stop();
// stop processing updatess on the offset clock (instantly freezes time for all our components)
FramedClock.ProcessSourceClockFrames = false;
// stop the seekable clock (stops the audio eventually)
adjustableClock.Stop();
IsPaused = true;
// we need to do a final check after all of our children have processed up to the paused clock time.
// this is to cover cases where, for instance, the player fails in the current processing frame.
Schedule(() =>
{
if (!CanPause) return;
OnPause?.Invoke();
pauseOverlay.Show();
lastPauseActionTime = Time.Current;
OnPause?.Invoke();
pauseOverlay.Show();
});
}
lastPauseActionTime = Time.Current;
});
public void Resume()
{
if (!IsPaused) return;
IsPaused = false;
FramedClock.ProcessSourceClockFrames = true;
IsResuming = false;
lastPauseActionTime = Time.Current;
OnResume?.Invoke();
// seek back to the time of the framed clock.
// this accounts for the audio clock potentially taking time to enter a completely stopped state.
adjustableClock.Seek(framedClock.CurrentTime);
adjustableClock.Start();
OnResume?.Invoke();
pauseOverlay.Hide();
AudioClock.Start();
IsResuming = false;
}
private OsuGameBase game;
@ -122,6 +120,9 @@ namespace osu.Game.Screens.Play
if (!game.IsActive && CanPause)
Pause();
if (!IsPaused)
framedClock.ProcessFrame();
base.Update();
}

View File

@ -56,9 +56,12 @@ namespace osu.Game.Screens.Play
public CursorContainer Cursor => RulesetContainer.Cursor;
public bool ProvidingUserCursor => RulesetContainer?.Cursor != null && !RulesetContainer.HasReplayLoaded.Value;
private IAdjustableClock adjustableSourceClock;
private FramedOffsetClock offsetClock;
private DecoupleableInterpolatingFramedClock decoupledClock;
private IAdjustableClock sourceClock;
/// <summary>
/// The decoupled clock used for gameplay. Should be used for seeks and clock control.
/// </summary>
private DecoupleableInterpolatingFramedClock adjustableClock;
private PauseContainer pauseContainer;
@ -140,17 +143,18 @@ namespace osu.Game.Screens.Play
return;
}
adjustableSourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock();
decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock();
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
var firstObjectTime = RulesetContainer.Objects.First().StartTime;
decoupledClock.Seek(AllowLeadIn
adjustableClock.Seek(AllowLeadIn
? Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn))
: firstObjectTime);
decoupledClock.ProcessFrame();
adjustableClock.ProcessFrame();
offsetClock = new FramedOffsetClock(decoupledClock);
// the final usable gameplay clock with user-set offsets applied.
var offsetClock = new FramedOffsetClock(adjustableClock);
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
userAudioOffset.ValueChanged += v => offsetClock.Offset = v;
@ -160,16 +164,8 @@ namespace osu.Game.Screens.Play
Children = new Drawable[]
{
storyboardContainer = new Container
pauseContainer = new PauseContainer(offsetClock, adjustableClock)
{
RelativeSizeAxes = Axes.Both,
Clock = offsetClock,
Alpha = 0,
},
pauseContainer = new PauseContainer
{
AudioClock = decoupledClock,
FramedClock = offsetClock,
OnRetry = Restart,
OnQuit = Exit,
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded,
@ -181,15 +177,23 @@ namespace osu.Game.Screens.Play
OnResume = () => hudOverlay.KeyCounter.IsCounting = true,
Children = new Drawable[]
{
new Container
storyboardContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Clock = offsetClock,
Child = RulesetContainer,
Alpha = 0,
},
new SkipButton(firstObjectTime) { AudioClock = decoupledClock },
hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, decoupledClock, working, adjustableSourceClock)
RulesetContainer,
new SkipButton(firstObjectTime)
{
Clock = Clock, // skip button doesn't want to use the audio clock directly
ProcessCustomClock = false,
AdjustableClock = adjustableClock,
FramedClock = offsetClock,
},
hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock)
{
Clock = Clock, // hud overlay doesn't want to use the audio clock directly
ProcessCustomClock = false,
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
@ -197,7 +201,7 @@ namespace osu.Game.Screens.Play
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Clock = decoupledClock,
ProcessCustomClock = false,
Breaks = beatmap.Breaks
}
}
@ -234,11 +238,11 @@ namespace osu.Game.Screens.Play
private void applyRateFromMods()
{
if (adjustableSourceClock == null) return;
if (sourceClock == null) return;
adjustableSourceClock.Rate = 1;
sourceClock.Rate = 1;
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToClock>())
mod.ApplyToClock(adjustableSourceClock);
mod.ApplyToClock(sourceClock);
}
private void initializeStoryboard(bool asyncLoad)
@ -297,7 +301,7 @@ namespace osu.Game.Screens.Play
if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
return false;
decoupledClock.Stop();
adjustableClock.Stop();
HasFailed = true;
failOverlay.Retries = RestartCount;
@ -326,17 +330,19 @@ namespace osu.Game.Screens.Play
Task.Run(() =>
{
adjustableSourceClock.Reset();
sourceClock.Reset();
Schedule(() =>
{
decoupledClock.ChangeSource(adjustableSourceClock);
adjustableClock.ChangeSource(sourceClock);
applyRateFromMods();
this.Delay(750).Schedule(() =>
{
if (!pauseContainer.IsPaused)
decoupledClock.Start();
{
adjustableClock.Start();
}
});
});
});
@ -363,9 +369,7 @@ namespace osu.Game.Screens.Play
}
if (loadedSuccessfully)
{
pauseContainer?.Pause();
}
return true;
}

View File

@ -24,7 +24,9 @@ namespace osu.Game.Screens.Play
public class SkipButton : OverlayContainer, IKeyBindingHandler<GlobalAction>
{
private readonly double startTime;
public IAdjustableClock AudioClock;
public IAdjustableClock AdjustableClock;
public IFrameBasedClock FramedClock;
private Button button;
private Box remainingTimeBox;
@ -60,8 +62,11 @@ namespace osu.Game.Screens.Play
{
var baseClock = Clock;
if (AudioClock != null)
Clock = new FramedClock(AudioClock) { ProcessSourceClockFrames = false };
if (FramedClock != null)
{
Clock = FramedClock;
ProcessCustomClock = false;
}
Children = new Drawable[]
{
@ -109,7 +114,7 @@ namespace osu.Game.Screens.Play
using (BeginAbsoluteSequence(beginFadeTime))
this.FadeOut(fade_time);
button.Action = () => AudioClock?.Seek(startTime - skip_required_cutoff - fade_time);
button.Action = () => AdjustableClock?.Seek(startTime - skip_required_cutoff - fade_time);
displayTime = Time.Current;

View File

@ -22,16 +22,14 @@ namespace osu.Game.Skinning
public LegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, AudioManager audioManager)
: base(skin)
{
storage = new LegacySkinResourceStore(skin, storage);
samples = audioManager.GetSampleManager(storage);
textures = new TextureStore(new RawTextureLoaderStore(storage));
}
private string getPathForFile(string filename) =>
SkinInfo.Files.FirstOrDefault(f => string.Equals(Path.GetFileNameWithoutExtension(f.Filename), filename, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath;
public override Drawable GetDrawableComponent(string componentName)
{
var texture = textures.Get(getPathForFile(componentName.Split('/').Last()));
var texture = textures.Get(componentName);
if (texture == null) return null;
return new Sprite
@ -42,6 +40,25 @@ namespace osu.Game.Skinning
};
}
public override SampleChannel GetSample(string sampleName) => samples.Get(getPathForFile(sampleName.Split('/').Last()));
public override SampleChannel GetSample(string sampleName) => samples.Get(sampleName);
private class LegacySkinResourceStore : IResourceStore<byte[]>
{
private readonly SkinInfo skin;
private readonly IResourceStore<byte[]> underlyingStore;
private string getPathForFile(string filename) =>
skin.Files.FirstOrDefault(f => string.Equals(Path.GetFileNameWithoutExtension(f.Filename), filename.Split('/').Last(), StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath;
public LegacySkinResourceStore(SkinInfo skin, IResourceStore<byte[]> underlyingStore)
{
this.skin = skin;
this.underlyingStore = underlyingStore;
}
public Stream GetStream(string name) => underlyingStore.GetStream(getPathForFile(name));
byte[] IResourceStore<byte[]>.Get(string name) => underlyingStore.Get(getPathForFile(name));
}
}
}

View File

@ -33,6 +33,8 @@ namespace osu.Game.Storyboards.Drawables
}
}
public override bool RemoveCompletedTransforms => false;
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) =>
dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));

View File

@ -17,6 +17,8 @@ namespace osu.Game.Storyboards.Drawables
public bool FlipH { get; set; }
public bool FlipV { get; set; }
public override bool RemoveWhenNotAlive => false;
protected override Vector2 DrawScale
=> new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y);

View File

@ -17,6 +17,8 @@ namespace osu.Game.Storyboards.Drawables
public bool FlipH { get; set; }
public bool FlipV { get; set; }
public override bool RemoveWhenNotAlive => false;
protected override Vector2 DrawScale
=> new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y);