mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 07:33:55 +09:00
Merge branch 'master' into difficulty-adjustment-extension
This commit is contained in:
@ -332,7 +332,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
EditorBeatmap.Add(hitObject);
|
||||
|
||||
if (EditorClock.CurrentTime < hitObject.StartTime)
|
||||
EditorClock.SeekTo(hitObject.StartTime);
|
||||
EditorClock.SeekSmoothlyTo(hitObject.StartTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,8 @@ namespace osu.Game.Rulesets
|
||||
{
|
||||
public interface ILegacyRuleset
|
||||
{
|
||||
const int MAX_LEGACY_RULESET_ID = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the server-side ID of a legacy ruleset.
|
||||
/// </summary>
|
||||
|
@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Judgements
|
||||
{
|
||||
JudgementText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = Result.GetDescription().ToUpperInvariant(),
|
||||
Colour = colours.ForHitResult(Result),
|
||||
Font = OsuFont.Numeric.With(size: 20),
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
@ -84,12 +83,10 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
foreach ((SettingSourceAttribute attr, PropertyInfo property) in this.GetOrderedSettingsSourceProperties())
|
||||
{
|
||||
object bindableObj = property.GetValue(this);
|
||||
var bindable = (IBindable)property.GetValue(this);
|
||||
|
||||
if ((bindableObj as IHasDefaultValue)?.IsDefault == true)
|
||||
continue;
|
||||
|
||||
tooltipTexts.Add($"{attr.Label} {bindableObj}");
|
||||
if (!bindable.IsDefault)
|
||||
tooltipTexts.Add($"{attr.Label} {bindable}");
|
||||
}
|
||||
|
||||
return string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s)));
|
||||
@ -131,24 +128,55 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// </summary>
|
||||
public virtual Mod CreateCopy()
|
||||
{
|
||||
var copy = (Mod)Activator.CreateInstance(GetType());
|
||||
var result = (Mod)Activator.CreateInstance(GetType());
|
||||
result.CopyFrom(this);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies mod setting values from <paramref name="source"/> into this instance, overwriting all existing settings.
|
||||
/// </summary>
|
||||
/// <param name="source">The mod to copy properties from.</param>
|
||||
public void CopyFrom(Mod source)
|
||||
{
|
||||
if (source.GetType() != GetType())
|
||||
throw new ArgumentException($"Expected mod of type {GetType()}, got {source.GetType()}.", nameof(source));
|
||||
|
||||
// Copy bindable values across
|
||||
foreach (var (_, prop) in this.GetSettingsSourceProperties())
|
||||
{
|
||||
var origBindable = prop.GetValue(this);
|
||||
var copyBindable = prop.GetValue(copy);
|
||||
var targetBindable = (IBindable)prop.GetValue(this);
|
||||
var sourceBindable = (IBindable)prop.GetValue(source);
|
||||
|
||||
// The bindables themselves are readonly, so the value must be transferred through the Bindable<T>.Value property.
|
||||
var valueProperty = origBindable.GetType().GetProperty(nameof(Bindable<object>.Value), BindingFlags.Public | BindingFlags.Instance);
|
||||
Debug.Assert(valueProperty != null);
|
||||
|
||||
valueProperty.SetValue(copyBindable, valueProperty.GetValue(origBindable));
|
||||
CopyAdjustedSetting(targetBindable, sourceBindable);
|
||||
}
|
||||
}
|
||||
|
||||
return copy;
|
||||
/// <summary>
|
||||
/// When creating copies or clones of a Mod, this method will be called
|
||||
/// to copy explicitly adjusted user settings from <paramref name="target"/>.
|
||||
/// The base implementation will transfer the value via <see cref="Bindable{T}.Parse"/>
|
||||
/// or by binding and unbinding (if <paramref name="source"/> is an <see cref="IBindable"/>)
|
||||
/// and should be called unless replaced with custom logic.
|
||||
/// </summary>
|
||||
/// <param name="target">The target bindable to apply the adjustment to.</param>
|
||||
/// <param name="source">The adjustment to apply.</param>
|
||||
internal virtual void CopyAdjustedSetting(IBindable target, object source)
|
||||
{
|
||||
if (source is IBindable sourceBindable)
|
||||
{
|
||||
// copy including transfer of default values.
|
||||
target.BindTo(sourceBindable);
|
||||
target.UnbindFrom(sourceBindable);
|
||||
}
|
||||
else
|
||||
target.Parse(source);
|
||||
}
|
||||
|
||||
public bool Equals(IMod other) => GetType() == other?.GetType();
|
||||
|
||||
/// <summary>
|
||||
/// Reset all custom settings for this mod back to their defaults.
|
||||
/// </summary>
|
||||
public virtual void ResetSettingsToDefaults() => CopyFrom((Mod)Activator.CreateInstance(GetType()));
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public void ApplyToPlayer(Player player)
|
||||
{
|
||||
player.Background.EnableUserDim.Value = false;
|
||||
player.ApplyToBackground(b => b.EnableUserDim.Value = false);
|
||||
|
||||
player.DimmableStoryboard.IgnoreUserSettings.Value = true;
|
||||
|
||||
|
@ -132,14 +132,43 @@ namespace osu.Game.Rulesets.Mods
|
||||
bindable.ValueChanged += _ => userChangedSettings[bindable] = !bindable.IsDefault;
|
||||
}
|
||||
|
||||
internal override void CopyAdjustedSetting(IBindable target, object source)
|
||||
{
|
||||
// if the value is non-bindable, it's presumably coming from an external source (like the API) - therefore presume it is not default.
|
||||
// if the value is bindable, defer to the source's IsDefault to be able to tell.
|
||||
userChangedSettings[target] = !(source is IBindable bindableSource) || !bindableSource.IsDefault;
|
||||
base.CopyAdjustedSetting(target, source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a setting from a configuration bindable using <paramref name="applyFunc"/>, if it has been changed by the user.
|
||||
/// </summary>
|
||||
protected void ApplySetting<T>(BindableNumber<T> setting, Action<T> applyFunc)
|
||||
where T : struct, IComparable<T>, IConvertible, IEquatable<T>
|
||||
{
|
||||
if (userChangedSettings.TryGetValue(setting, out bool userChangedSetting) && userChangedSetting)
|
||||
applyFunc.Invoke(setting.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply all custom settings to the provided beatmap.
|
||||
/// </summary>
|
||||
/// <param name="difficulty">The beatmap to have settings applied.</param>
|
||||
protected virtual void ApplySettings(BeatmapDifficulty difficulty)
|
||||
{
|
||||
difficulty.DrainRate = DrainRate.Value;
|
||||
difficulty.OverallDifficulty = OverallDifficulty.Value;
|
||||
ApplySetting(DrainRate, dr => difficulty.DrainRate = dr);
|
||||
ApplySetting(OverallDifficulty, od => difficulty.OverallDifficulty = od);
|
||||
}
|
||||
|
||||
public override void ResetSettingsToDefaults()
|
||||
{
|
||||
base.ResetSettingsToDefaults();
|
||||
|
||||
if (difficulty != null)
|
||||
{
|
||||
// base implementation potentially overwrite modified defaults that came from a beatmap selection.
|
||||
TransferSettings(difficulty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
}
|
||||
|
||||
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
||||
public virtual void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
||||
{
|
||||
const float ratio = 1.4f;
|
||||
difficulty.CircleSize = Math.Min(difficulty.CircleSize * 1.3f, 10.0f); // CS uses a custom 1.3 ratio.
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// <summary>
|
||||
/// The point in the beatmap at which the final ramping rate should be reached.
|
||||
/// </summary>
|
||||
private const double final_rate_progress = 0.75f;
|
||||
public const double FINAL_RATE_PROGRESS = 0.75f;
|
||||
|
||||
[SettingSource("Initial rate", "The starting speed of the track")]
|
||||
public abstract BindableNumber<double> InitialRate { get; }
|
||||
@ -66,17 +66,18 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public virtual void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
HitObject lastObject = beatmap.HitObjects.LastOrDefault();
|
||||
|
||||
SpeedChange.SetDefault();
|
||||
|
||||
beginRampTime = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0;
|
||||
finalRateTime = final_rate_progress * (lastObject?.GetEndTime() ?? 0);
|
||||
double firstObjectStart = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0;
|
||||
double lastObjectEnd = beatmap.HitObjects.LastOrDefault()?.GetEndTime() ?? 0;
|
||||
|
||||
beginRampTime = firstObjectStart;
|
||||
finalRateTime = firstObjectStart + FINAL_RATE_PROGRESS * (lastObjectEnd - firstObjectStart);
|
||||
}
|
||||
|
||||
public virtual void Update(Playfield playfield)
|
||||
{
|
||||
applyRateAdjustment((track.CurrentTime - beginRampTime) / finalRateTime);
|
||||
applyRateAdjustment((track.CurrentTime - beginRampTime) / Math.Max(1, finalRateTime - beginRampTime));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -303,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)
|
||||
{
|
||||
@ -749,7 +750,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
if (Result.Type != originalType)
|
||||
{
|
||||
Logger.Log($"{GetType().ReadableName()} applied an invalid hit result ({originalType}) when {nameof(HitResult.IgnoreMiss)} or {nameof(HitResult.IgnoreHit)} is expected.\n"
|
||||
+ $"This has been automatically adjusted to {Result.Type}, and support will be removed from 2020-03-28 onwards.", level: LogLevel.Important);
|
||||
+ $"This has been automatically adjusted to {Result.Type}, and support will be removed from 2021-03-28 onwards.", level: LogLevel.Important);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,10 +10,7 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset,
|
||||
#pragma warning disable 618
|
||||
IHasCurve
|
||||
#pragma warning restore 618
|
||||
internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset
|
||||
{
|
||||
/// <summary>
|
||||
/// Scoring distance with a speed-adjusted beat length of 1 second.
|
||||
|
@ -1,55 +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 osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Types
|
||||
{
|
||||
[Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126
|
||||
public interface IHasCurve : IHasDistance, IHasRepeats
|
||||
{
|
||||
/// <summary>
|
||||
/// The curve.
|
||||
/// </summary>
|
||||
SliderPath Path { get; }
|
||||
}
|
||||
|
||||
#pragma warning disable 618
|
||||
[Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126
|
||||
public static class HasCurveExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes the position on the curve relative to how much of the <see cref="HitObject"/> has been completed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The curve.</param>
|
||||
/// <param name="progress">[0, 1] where 0 is the start time of the <see cref="HitObject"/> and 1 is the end time of the <see cref="HitObject"/>.</param>
|
||||
/// <returns>The position on the curve.</returns>
|
||||
public static Vector2 CurvePositionAt(this IHasCurve obj, double progress)
|
||||
=> obj.Path.PositionAt(obj.ProgressAt(progress));
|
||||
|
||||
/// <summary>
|
||||
/// Computes the progress along the curve relative to how much of the <see cref="HitObject"/> has been completed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The curve.</param>
|
||||
/// <param name="progress">[0, 1] where 0 is the start time of the <see cref="HitObject"/> and 1 is the end time of the <see cref="HitObject"/>.</param>
|
||||
/// <returns>[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</returns>
|
||||
public static double ProgressAt(this IHasCurve obj, double progress)
|
||||
{
|
||||
double p = progress * obj.SpanCount() % 1;
|
||||
if (obj.SpanAt(progress) % 2 == 1)
|
||||
p = 1 - p;
|
||||
return p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines which span of the curve the progress point is on.
|
||||
/// </summary>
|
||||
/// <param name="obj">The curve.</param>
|
||||
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
|
||||
/// <returns>[0, SpanCount) where 0 is the first run.</returns>
|
||||
public static int SpanAt(this IHasCurve obj, double progress)
|
||||
=> (int)(progress * obj.SpanCount());
|
||||
}
|
||||
#pragma warning restore 618
|
||||
}
|
@ -6,26 +6,16 @@ namespace osu.Game.Rulesets.Objects.Types
|
||||
/// <summary>
|
||||
/// A HitObject that ends at a different time than its start time.
|
||||
/// </summary>
|
||||
#pragma warning disable 618
|
||||
public interface IHasDuration : IHasEndTime
|
||||
#pragma warning restore 618
|
||||
public interface IHasDuration
|
||||
{
|
||||
double IHasEndTime.EndTime
|
||||
{
|
||||
get => EndTime;
|
||||
set => Duration = (Duration - EndTime) + value;
|
||||
}
|
||||
|
||||
double IHasEndTime.Duration => Duration;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the HitObject ends.
|
||||
/// </summary>
|
||||
new double EndTime { get; }
|
||||
double EndTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the HitObject.
|
||||
/// </summary>
|
||||
new double Duration { get; set; }
|
||||
double Duration { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +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 Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// A HitObject that ends at a different time than its start time.
|
||||
/// </summary>
|
||||
[Obsolete("Use IHasDuration instead.")] // can be removed 20201126
|
||||
public interface IHasEndTime
|
||||
{
|
||||
/// <summary>
|
||||
/// The time at which the HitObject ends.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
double EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the HitObject.
|
||||
/// </summary>
|
||||
double Duration { get; }
|
||||
}
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
// 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 MessagePack;
|
||||
|
||||
namespace osu.Game.Rulesets.Replays
|
||||
{
|
||||
[MessagePackObject]
|
||||
public class ReplayFrame
|
||||
{
|
||||
[Key(0)]
|
||||
public double Time;
|
||||
|
||||
public ReplayFrame()
|
||||
|
@ -24,9 +24,9 @@ using osu.Game.Skinning;
|
||||
using osu.Game.Users;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets
|
||||
{
|
||||
@ -272,7 +272,7 @@ namespace osu.Game.Rulesets
|
||||
var validResults = GetValidHitResults();
|
||||
|
||||
// enumerate over ordered list to guarantee return order is stable.
|
||||
foreach (var result in OrderAttributeUtils.GetValuesInOrder<HitResult>())
|
||||
foreach (var result in EnumExtensions.GetValuesInOrder<HitResult>())
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
@ -298,7 +298,7 @@ namespace osu.Game.Rulesets
|
||||
/// <remarks>
|
||||
/// <see cref="HitResult.Miss"/> is implicitly included. Special types like <see cref="HitResult.IgnoreHit"/> are ignored even when specified.
|
||||
/// </remarks>
|
||||
protected virtual IEnumerable<HitResult> GetValidHitResults() => OrderAttributeUtils.GetValuesInOrder<HitResult>();
|
||||
protected virtual IEnumerable<HitResult> GetValidHitResults() => EnumExtensions.GetValuesInOrder<HitResult>();
|
||||
|
||||
/// <summary>
|
||||
/// Get a display friendly name for the specified result type.
|
||||
|
@ -100,9 +100,7 @@ namespace osu.Game.Rulesets
|
||||
|
||||
foreach (var r in instances.Where(r => !(r is ILegacyRuleset)))
|
||||
{
|
||||
// todo: StartsWith can be changed to Equals on 2020-11-08
|
||||
// This is to give users enough time to have their database use new abbreviated info).
|
||||
if (existingRulesets.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null)
|
||||
if (existingRulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null)
|
||||
context.RulesetInfo.Add(r.RulesetInfo);
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using osu.Game.Utils;
|
||||
using osu.Framework.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
@ -188,7 +193,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
private void updateScore()
|
||||
{
|
||||
if (rollingMaxBaseScore != 0)
|
||||
Accuracy.Value = baseScore / rollingMaxBaseScore;
|
||||
Accuracy.Value = calculateAccuracyRatio(baseScore, true);
|
||||
|
||||
TotalScore.Value = getScore(Mode.Value);
|
||||
}
|
||||
@ -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,45 @@ namespace osu.Game.Rulesets.Scoring
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a minimal set of inputs, return the computed score for the tracked beatmap / mods combination, at the current point in time.
|
||||
/// </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 for provided inputs.</returns>
|
||||
public double GetImmediateScore(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;
|
||||
}
|
||||
|
||||
return GetScore(mode, maxAchievableCombo, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), scoreResultCounts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the accuracy fraction for the provided base score.
|
||||
/// </summary>
|
||||
/// <param name="baseScore">The score to be used for accuracy calculation.</param>
|
||||
/// <param name="preferRolling">Whether the rolling base score should be used (ie. for the current point in time based on Apply/Reverted results).</param>
|
||||
/// <returns>The computed accuracy.</returns>
|
||||
private double calculateAccuracyRatio(double baseScore, bool preferRolling = false)
|
||||
{
|
||||
if (preferRolling && rollingMaxBaseScore != 0)
|
||||
return baseScore / rollingMaxBaseScore;
|
||||
|
||||
return 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.
|
||||
|
@ -8,11 +8,11 @@ using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Audio.Track;
|
||||
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 +46,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"));
|
||||
|
@ -17,19 +17,10 @@ using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
public class HitObjectContainer : LifetimeManagementContainer
|
||||
public class HitObjectContainer : LifetimeManagementContainer, IHitObjectContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// All currently in-use <see cref="DrawableHitObject"/>s.
|
||||
/// </summary>
|
||||
public IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
||||
|
||||
/// <summary>
|
||||
/// All currently in-use <see cref="DrawableHitObject"/>s that are alive.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this <see cref="HitObjectContainer"/> uses pooled objects, this is equivalent to <see cref="Objects"/>.
|
||||
/// </remarks>
|
||||
public IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
||||
|
||||
/// <summary>
|
||||
@ -124,9 +115,11 @@ namespace osu.Game.Rulesets.UI
|
||||
Debug.Assert(drawableMap.ContainsKey(entry));
|
||||
|
||||
var drawable = drawableMap[entry];
|
||||
|
||||
// OnKilled can potentially change the hitobject's result, so it needs to run first before unbinding.
|
||||
drawable.OnKilled();
|
||||
drawable.OnNewResult -= onNewResult;
|
||||
drawable.OnRevertResult -= onRevertResult;
|
||||
drawable.OnKilled();
|
||||
|
||||
drawableMap.Remove(entry);
|
||||
|
||||
|
24
osu.Game/Rulesets/UI/IHitObjectContainer.cs
Normal file
24
osu.Game/Rulesets/UI/IHitObjectContainer.cs
Normal file
@ -0,0 +1,24 @@
|
||||
// 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.Collections.Generic;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
public interface IHitObjectContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// All currently in-use <see cref="DrawableHitObject"/>s.
|
||||
/// </summary>
|
||||
IEnumerable<DrawableHitObject> Objects { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All currently in-use <see cref="DrawableHitObject"/>s that are alive.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this <see cref="IHitObjectContainer"/> uses pooled objects, this is equivalent to <see cref="Objects"/>.
|
||||
/// </remarks>
|
||||
IEnumerable<DrawableHitObject> AliveObjects { get; }
|
||||
}
|
||||
}
|
@ -16,6 +16,9 @@ using osu.Framework.Bindables;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Display the specified mod at a fixed size.
|
||||
/// </summary>
|
||||
public class ModIcon : Container, IHasTooltip
|
||||
{
|
||||
public readonly BindableBool Selected = new BindableBool();
|
||||
@ -26,11 +29,10 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
private const float size = 80;
|
||||
|
||||
private readonly ModType type;
|
||||
|
||||
public virtual string TooltipText => mod.IconTooltip;
|
||||
public virtual string TooltipText => showTooltip ? mod.IconTooltip : null;
|
||||
|
||||
private Mod mod;
|
||||
private readonly bool showTooltip;
|
||||
|
||||
public Mod Mod
|
||||
{
|
||||
@ -38,15 +40,27 @@ namespace osu.Game.Rulesets.UI
|
||||
set
|
||||
{
|
||||
mod = value;
|
||||
updateMod(value);
|
||||
|
||||
if (IsLoaded)
|
||||
updateMod(value);
|
||||
}
|
||||
}
|
||||
|
||||
public ModIcon(Mod mod)
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
private Color4 backgroundColour;
|
||||
private Color4 highlightedColour;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance.
|
||||
/// </summary>
|
||||
/// <param name="mod">The mod to be displayed</param>
|
||||
/// <param name="showTooltip">Whether a tooltip describing the mod should display on hover.</param>
|
||||
public ModIcon(Mod mod, bool showTooltip = true)
|
||||
{
|
||||
this.mod = mod ?? throw new ArgumentNullException(nameof(mod));
|
||||
|
||||
type = mod.Type;
|
||||
this.showTooltip = showTooltip;
|
||||
|
||||
Size = new Vector2(size);
|
||||
|
||||
@ -79,6 +93,13 @@ namespace osu.Game.Rulesets.UI
|
||||
Icon = FontAwesome.Solid.Question
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Selected.BindValueChanged(_ => updateColour());
|
||||
|
||||
updateMod(mod);
|
||||
}
|
||||
@ -92,20 +113,14 @@ namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
modIcon.FadeOut();
|
||||
modAcronym.FadeIn();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
modIcon.FadeIn();
|
||||
modAcronym.FadeOut();
|
||||
}
|
||||
|
||||
modIcon.FadeIn();
|
||||
modAcronym.FadeOut();
|
||||
}
|
||||
|
||||
private Color4 backgroundColour;
|
||||
private Color4 highlightedColour;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
switch (type)
|
||||
switch (value.Type)
|
||||
{
|
||||
default:
|
||||
case ModType.DifficultyIncrease:
|
||||
@ -139,12 +154,13 @@ namespace osu.Game.Rulesets.UI
|
||||
modIcon.Colour = colours.Yellow;
|
||||
break;
|
||||
}
|
||||
|
||||
updateColour();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
private void updateColour()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Selected.BindValueChanged(selected => background.Colour = selected.NewValue ? highlightedColour : backgroundColour, true);
|
||||
background.Colour = Selected.Value ? highlightedColour : backgroundColour;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ using JetBrains.Annotations;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -20,6 +19,7 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Audio.Sample;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -91,7 +91,11 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
scrollingInfo = new LocalScrollingInfo();
|
||||
scrollingInfo.Direction.BindTo(Direction);
|
||||
scrollingInfo.TimeRange.BindTo(TimeRange);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
switch (VisualisationMethod)
|
||||
{
|
||||
case ScrollVisualisationMethod.Sequential:
|
||||
@ -106,11 +110,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
scrollingInfo.Algorithm = new ConstantScrollAlgorithm();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
double lastObjectTime = Objects.LastOrDefault()?.GetEndTime() ?? double.MaxValue;
|
||||
double baseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH;
|
||||
|
||||
|
Reference in New Issue
Block a user