mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 15:44:04 +09:00
Merge branch 'master' into present-recommended
This commit is contained in:
@ -42,6 +42,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
/// </summary>
|
||||
public HitObject HitObject { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parenting <see cref="DrawableHitObject"/>, if any.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
protected internal DrawableHitObject ParentHitObject { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The colour used for various elements of this DrawableHitObject.
|
||||
/// </summary>
|
||||
@ -227,12 +233,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
foreach (var h in HitObject.NestedHitObjects)
|
||||
{
|
||||
var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h);
|
||||
var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h, this);
|
||||
var drawableNested = pooledDrawableNested
|
||||
?? CreateNestedHitObject(h)
|
||||
?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
|
||||
|
||||
// Invoke the event only if this nested object is just created by `CreateNestedHitObject`.
|
||||
// Only invoke the event for non-pooled DHOs, otherwise the event will be fired by the playfield.
|
||||
if (pooledDrawableNested == null)
|
||||
OnNestedDrawableCreated?.Invoke(drawableNested);
|
||||
|
||||
@ -240,10 +246,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
drawableNested.OnRevertResult += onRevertResult;
|
||||
drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState;
|
||||
|
||||
// This is only necessary for non-pooled DHOs. For pooled DHOs, this is handled inside GetPooledDrawableRepresentation().
|
||||
// Must be done before the nested DHO is added to occur before the nested Apply()!
|
||||
drawableNested.ParentHitObject = this;
|
||||
|
||||
nestedHitObjects.Value.Add(drawableNested);
|
||||
AddNestedHitObject(drawableNested);
|
||||
|
||||
drawableNested.OnParentReceived(this);
|
||||
}
|
||||
|
||||
StartTimeBindable.BindTo(HitObject.StartTimeBindable);
|
||||
@ -295,7 +303,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
samplesBindable.CollectionChanged -= onSamplesChanged;
|
||||
|
||||
// Release the samples for other hitobjects to use.
|
||||
Samples.Samples = null;
|
||||
if (Samples != null)
|
||||
Samples.Samples = null;
|
||||
|
||||
if (nestedHitObjects.IsValueCreated)
|
||||
{
|
||||
@ -315,6 +324,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
OnFree();
|
||||
|
||||
HitObject = null;
|
||||
ParentHitObject = null;
|
||||
Result = null;
|
||||
lifetimeEntry = null;
|
||||
|
||||
@ -348,14 +358,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when this <see cref="DrawableHitObject"/> receives a new parenting <see cref="DrawableHitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parenting <see cref="DrawableHitObject"/>.</param>
|
||||
protected virtual void OnParentReceived(DrawableHitObject parent)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked by the base <see cref="DrawableHitObject"/> to populate samples, once on initial load and potentially again on any change to the samples collection.
|
||||
/// </summary>
|
||||
@ -450,7 +452,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
state.Value = newState;
|
||||
|
||||
if (LifetimeEnd == double.MaxValue && (state.Value != ArmedState.Idle || HitObject.HitWindows == null))
|
||||
Expire();
|
||||
LifetimeEnd = Math.Max(LatestTransformEndTime, HitStateUpdateTime + (Samples?.Length ?? 0));
|
||||
|
||||
// apply any custom state overrides
|
||||
ApplyCustomUpdateState?.Invoke(this, newState);
|
||||
|
@ -68,7 +68,12 @@ namespace osu.Game.Rulesets.Scoring
|
||||
private readonly double comboPortion;
|
||||
|
||||
private int maxAchievableCombo;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum achievable base score.
|
||||
/// </summary>
|
||||
private double maxBaseScore;
|
||||
|
||||
private double rollingMaxBaseScore;
|
||||
private double baseScore;
|
||||
|
||||
@ -196,8 +201,8 @@ namespace osu.Game.Rulesets.Scoring
|
||||
private double getScore(ScoringMode mode)
|
||||
{
|
||||
return GetScore(mode, maxAchievableCombo,
|
||||
maxBaseScore > 0 ? baseScore / maxBaseScore : 0,
|
||||
maxAchievableCombo > 0 ? (double)HighestCombo.Value / maxAchievableCombo : 1,
|
||||
calculateAccuracyRatio(baseScore),
|
||||
calculateComboRatio(HighestCombo.Value),
|
||||
scoreResultCounts);
|
||||
}
|
||||
|
||||
@ -227,6 +232,37 @@ namespace osu.Game.Rulesets.Scoring
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a minimal set of inputs, return the computed score and accuracy for the tracked beatmap / mods combination.
|
||||
/// </summary>
|
||||
/// <param name="mode">The <see cref="ScoringMode"/> to compute the total score in.</param>
|
||||
/// <param name="maxCombo">The maximum combo achievable in the beatmap.</param>
|
||||
/// <param name="statistics">Statistics to be used for calculating accuracy, bonus score, etc.</param>
|
||||
/// <returns>The computed score and accuracy for provided inputs.</returns>
|
||||
public (double score, double accuracy) GetScoreAndAccuracy(ScoringMode mode, int maxCombo, Dictionary<HitResult, int> statistics)
|
||||
{
|
||||
// calculate base score from statistics pairs
|
||||
int computedBaseScore = 0;
|
||||
|
||||
foreach (var pair in statistics)
|
||||
{
|
||||
if (!pair.Key.AffectsAccuracy())
|
||||
continue;
|
||||
|
||||
computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value;
|
||||
}
|
||||
|
||||
double accuracy = calculateAccuracyRatio(computedBaseScore);
|
||||
double comboRatio = calculateComboRatio(maxCombo);
|
||||
|
||||
double score = GetScore(mode, maxAchievableCombo, accuracy, comboRatio, scoreResultCounts);
|
||||
|
||||
return (score, accuracy);
|
||||
}
|
||||
|
||||
private double calculateAccuracyRatio(double baseScore) => maxBaseScore > 0 ? baseScore / maxBaseScore : 0;
|
||||
private double calculateComboRatio(int maxCombo) => maxAchievableCombo > 0 ? (double)maxCombo / maxAchievableCombo : 1;
|
||||
|
||||
private double getBonusScore(Dictionary<HitResult, int> statistics)
|
||||
=> statistics.GetOrDefault(HitResult.SmallBonus) * Judgement.SMALL_BONUS_SCORE
|
||||
+ statistics.GetOrDefault(HitResult.LargeBonus) * Judgement.LARGE_BONUS_SCORE;
|
||||
|
@ -268,12 +268,12 @@ namespace osu.Game.Rulesets.UI
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void SetRecordTarget(Replay recordingReplay)
|
||||
public sealed override void SetRecordTarget(Score score)
|
||||
{
|
||||
if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputManager))
|
||||
throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports recording is not available");
|
||||
|
||||
var recorder = CreateReplayRecorder(recordingReplay);
|
||||
var recorder = CreateReplayRecorder(score);
|
||||
|
||||
if (recorder == null)
|
||||
return;
|
||||
@ -327,7 +327,7 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null;
|
||||
|
||||
protected virtual ReplayRecorder CreateReplayRecorder(Replay replay) => null;
|
||||
protected virtual ReplayRecorder CreateReplayRecorder(Score score) => null;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Playfield.
|
||||
@ -516,8 +516,8 @@ namespace osu.Game.Rulesets.UI
|
||||
/// <summary>
|
||||
/// Sets a replay to be used to record gameplay.
|
||||
/// </summary>
|
||||
/// <param name="recordingReplay">The target to be recorded to.</param>
|
||||
public abstract void SetRecordTarget(Replay recordingReplay);
|
||||
/// <param name="score">The target to be recorded to.</param>
|
||||
public abstract void SetRecordTarget(Score score);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the interactive user requests resuming from a paused state.
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
if (resources != null)
|
||||
{
|
||||
TextureStore = new TextureStore(new TextureLoaderStore(new NamespacedResourceStore<byte[]>(resources, @"Textures")));
|
||||
TextureStore = new TextureStore(parent.Get<GameHost>().CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(resources, @"Textures")));
|
||||
CacheAs(TextureStore = new FallbackTextureStore(TextureStore, parent.Get<TextureStore>()));
|
||||
|
||||
SampleStore = parent.Get<AudioManager>().GetSampleStore(new NamespacedResourceStore<byte[]>(resources, @"Samples"));
|
||||
|
@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
Debug.Assert(!drawableMap.ContainsKey(entry));
|
||||
|
||||
var drawable = pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject);
|
||||
var drawable = pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null);
|
||||
if (drawable == null)
|
||||
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
||||
|
||||
|
@ -13,8 +13,9 @@ namespace osu.Game.Rulesets.UI
|
||||
/// Attempts to retrieve the poolable <see cref="DrawableHitObject"/> representation of a <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> to retrieve the <see cref="DrawableHitObject"/> representation of.</param>
|
||||
/// <param name="parent">The parenting <see cref="DrawableHitObject"/>, if any.</param>
|
||||
/// <returns>The <see cref="DrawableHitObject"/> representing <see cref="HitObject"/>, or <c>null</c> if no poolable representation exists.</returns>
|
||||
[CanBeNull]
|
||||
DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject);
|
||||
DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject, [CanBeNull] DrawableHitObject parent);
|
||||
}
|
||||
}
|
||||
|
@ -330,7 +330,7 @@ namespace osu.Game.Rulesets.UI
|
||||
AddInternal(pool);
|
||||
}
|
||||
|
||||
DrawableHitObject IPooledHitObjectProvider.GetPooledDrawableRepresentation(HitObject hitObject)
|
||||
DrawableHitObject IPooledHitObjectProvider.GetPooledDrawableRepresentation(HitObject hitObject, DrawableHitObject parent)
|
||||
{
|
||||
var lookupType = hitObject.GetType();
|
||||
|
||||
@ -366,6 +366,7 @@ namespace osu.Game.Rulesets.UI
|
||||
if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry))
|
||||
lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject);
|
||||
|
||||
dho.ParentHitObject = parent;
|
||||
dho.Apply(hitObject, entry);
|
||||
});
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK;
|
||||
|
||||
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.UI
|
||||
public abstract class ReplayRecorder<T> : ReplayRecorder, IKeyBindingHandler<T>
|
||||
where T : struct
|
||||
{
|
||||
private readonly Replay target;
|
||||
private readonly Score target;
|
||||
|
||||
private readonly List<T> pressedActions = new List<T>();
|
||||
|
||||
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.UI
|
||||
[Resolved]
|
||||
private GameplayBeatmap gameplayBeatmap { get; set; }
|
||||
|
||||
protected ReplayRecorder(Replay target)
|
||||
protected ReplayRecorder(Score target)
|
||||
{
|
||||
this.target = target;
|
||||
|
||||
@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
inputManager = GetContainingInputManager();
|
||||
|
||||
spectatorStreaming?.BeginPlaying(gameplayBeatmap);
|
||||
spectatorStreaming?.BeginPlaying(gameplayBeatmap, target);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
private void recordFrame(bool important)
|
||||
{
|
||||
var last = target.Frames.LastOrDefault();
|
||||
var last = target.Replay.Frames.LastOrDefault();
|
||||
|
||||
if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate))
|
||||
return;
|
||||
@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
if (frame != null)
|
||||
{
|
||||
target.Frames.Add(frame);
|
||||
target.Replay.Frames.Add(frame);
|
||||
|
||||
spectatorStreaming?.HandleFrame(frame);
|
||||
}
|
||||
|
@ -225,10 +225,19 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject);
|
||||
|
||||
toComputeLifetime.Clear();
|
||||
}
|
||||
|
||||
// only AliveObjects need to be considered for layout (reduces overhead in the case of scroll speed changes).
|
||||
protected override void UpdateAfterChildrenLife()
|
||||
{
|
||||
base.UpdateAfterChildrenLife();
|
||||
|
||||
// We need to calculate hit object positions (including nested hit objects) as soon as possible after lifetimes
|
||||
// to prevent hit objects displayed in a wrong position for one frame.
|
||||
// Only AliveObjects need to be considered for layout (reduces overhead in the case of scroll speed changes).
|
||||
foreach (var obj in AliveObjects)
|
||||
{
|
||||
updatePosition(obj, Time.Current);
|
||||
|
||||
if (layoutComputed.Contains(obj))
|
||||
continue;
|
||||
|
||||
@ -293,15 +302,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildrenLife()
|
||||
{
|
||||
base.UpdateAfterChildrenLife();
|
||||
|
||||
// We need to calculate hitobject positions as soon as possible after lifetimes so that hitobjects get the final say in their positions
|
||||
foreach (var obj in AliveObjects)
|
||||
updatePosition(obj, Time.Current);
|
||||
}
|
||||
|
||||
private void updatePosition(DrawableHitObject hitObject, double currentTime)
|
||||
{
|
||||
switch (direction.Value)
|
||||
|
Reference in New Issue
Block a user