diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e60e0a39ae..a8167ec4db 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -121,12 +121,24 @@ jobs:
build-only-ios:
name: Build only (iOS)
- runs-on: macos-latest
+ # `macos-13` is required, because Xcode 14.3 is required (see below).
+ # TODO: can be changed to `macos-latest` once `macos-13` becomes latest (currently in beta)
+ runs-on: macos-13
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v3
+ # newest Microsoft.iOS.Sdk versions require Xcode 14.3.
+ # 14.3 is currently not the default Xcode version (https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md#xcode),
+ # so set it manually.
+ # TODO: remove when 14.3 becomes the default Xcode version.
+ - name: Set Xcode version
+ shell: bash
+ run: |
+ sudo xcode-select -s "/Applications/Xcode_14.3.app"
+ echo "MD_APPLE_SDK_ROOT=/Applications/Xcode_14.3.app" >> $GITHUB_ENV
+
- name: Install .NET 6.0.x
uses: actions/setup-dotnet@v3
with:
diff --git a/.github/workflows/update-web-mod-definitions.yml b/.github/workflows/update-web-mod-definitions.yml
new file mode 100644
index 0000000000..32d3d37ffe
--- /dev/null
+++ b/.github/workflows/update-web-mod-definitions.yml
@@ -0,0 +1,53 @@
+name: Update osu-web mod definitions
+on:
+ push:
+ tags:
+ - '*'
+
+permissions:
+ contents: read # to fetch code (actions/checkout)
+
+jobs:
+ update-mod-definitions:
+ name: Update osu-web mod definitions
+ runs-on: ubuntu-latest
+ steps:
+ - name: Install .NET 6.0.x
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: "6.0.x"
+
+ - name: Checkout ppy/osu
+ uses: actions/checkout@v3
+ with:
+ path: osu
+
+ - name: Checkout ppy/osu-tools
+ uses: actions/checkout@v3
+ with:
+ repository: ppy/osu-tools
+ path: osu-tools
+
+ - name: Checkout ppy/osu-web
+ uses: actions/checkout@v3
+ with:
+ repository: ppy/osu-web
+ path: osu-web
+
+ - name: Setup local game checkout for tools
+ run: ./UseLocalOsu.sh
+ working-directory: ./osu-tools
+
+ - name: Regenerate mod definitions
+ run: dotnet run --project PerformanceCalculator -- mods > ../osu-web/database/mods.json
+ working-directory: ./osu-tools
+
+ - name: Create pull request with changes
+ uses: peter-evans/create-pull-request@v5
+ with:
+ title: Update mod definitions
+ body: "This PR has been auto-generated to update the mod definitions to match ppy/osu@${{ github.ref_name }}."
+ branch: update-mod-definitions
+ commit-message: Update mod definitions
+ path: osu-web
+ token: ${{ secrets.OSU_WEB_PULL_REQUEST_PAT }}
diff --git a/osu.Android.props b/osu.Android.props
index e9ecbaa10b..ff76e17184 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -11,7 +11,7 @@
manifestmerger.jar
-
+
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs
index 7e4ffc7408..72031b4958 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs
@@ -98,6 +98,7 @@ namespace osu.Game.Rulesets.Osu.Mods
ComboOffset = original.ComboOffset;
LegacyLastTickOffset = original.LegacyLastTickOffset;
TickDistanceMultiplier = original.TickDistanceMultiplier;
+ SliderVelocity = original.SliderVelocity;
}
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 5e98025c9a..49594ca969 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -384,11 +384,11 @@ namespace osu.Game.Beatmaps.Formats
break;
case @"SliderMultiplier":
- difficulty.SliderMultiplier = Parsing.ParseDouble(pair.Value);
+ difficulty.SliderMultiplier = Math.Clamp(Parsing.ParseDouble(pair.Value), 0.4, 3.6);
break;
case @"SliderTickRate":
- difficulty.SliderTickRate = Parsing.ParseDouble(pair.Value);
+ difficulty.SliderTickRate = Math.Clamp(Parsing.ParseDouble(pair.Value), 0.5, 8);
break;
}
}
diff --git a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs
index dad9bbbd0b..78234a9dd9 100644
--- a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs
+++ b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs
@@ -34,7 +34,8 @@ namespace osu.Game.Beatmaps
float ApproachRate { get; }
///
- /// The slider multiplier of the associated beatmap.
+ /// The base slider velocity of the associated beatmap.
+ /// This was known as "SliderMultiplier" in the .osu format and stable editor.
///
double SliderMultiplier { get; }
diff --git a/osu.Game/Localisation/EditorSetupStrings.cs b/osu.Game/Localisation/EditorSetupStrings.cs
index 4ddacf2c5b..401411365b 100644
--- a/osu.Game/Localisation/EditorSetupStrings.cs
+++ b/osu.Game/Localisation/EditorSetupStrings.cs
@@ -42,8 +42,7 @@ namespace osu.Game.Localisation
///
/// "If enabled, an "Are you ready? 3, 2, 1, GO!" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so."
///
- public static LocalisableString CountdownDescription => new TranslatableString(getKey(@"countdown_description"),
- @"If enabled, an ""Are you ready? 3, 2, 1, GO!"" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so.");
+ public static LocalisableString CountdownDescription => new TranslatableString(getKey(@"countdown_description"), @"If enabled, an ""Are you ready? 3, 2, 1, GO!"" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so.");
///
/// "Countdown speed"
@@ -53,8 +52,7 @@ namespace osu.Game.Localisation
///
/// "If the countdown sounds off-time, use this to make it appear one or more beats early."
///
- public static LocalisableString CountdownOffsetDescription =>
- new TranslatableString(getKey(@"countdown_offset_description"), @"If the countdown sounds off-time, use this to make it appear one or more beats early.");
+ public static LocalisableString CountdownOffsetDescription => new TranslatableString(getKey(@"countdown_offset_description"), @"If the countdown sounds off-time, use this to make it appear one or more beats early.");
///
/// "Countdown offset"
@@ -69,8 +67,7 @@ namespace osu.Game.Localisation
///
/// "Allows storyboards to use the full screen space, rather than be confined to a 4:3 area."
///
- public static LocalisableString WidescreenSupportDescription =>
- new TranslatableString(getKey(@"widescreen_support_description"), @"Allows storyboards to use the full screen space, rather than be confined to a 4:3 area.");
+ public static LocalisableString WidescreenSupportDescription => new TranslatableString(getKey(@"widescreen_support_description"), @"Allows storyboards to use the full screen space, rather than be confined to a 4:3 area.");
///
/// "Epilepsy warning"
@@ -80,8 +77,7 @@ namespace osu.Game.Localisation
///
/// "Recommended if the storyboard or video contain scenes with rapidly flashing colours."
///
- public static LocalisableString EpilepsyWarningDescription =>
- new TranslatableString(getKey(@"epilepsy_warning_description"), @"Recommended if the storyboard or video contain scenes with rapidly flashing colours.");
+ public static LocalisableString EpilepsyWarningDescription => new TranslatableString(getKey(@"epilepsy_warning_description"), @"Recommended if the storyboard or video contain scenes with rapidly flashing colours.");
///
/// "Letterbox during breaks"
@@ -91,8 +87,7 @@ namespace osu.Game.Localisation
///
/// "Adds horizontal letterboxing to give a cinematic look during breaks."
///
- public static LocalisableString LetterboxDuringBreaksDescription =>
- new TranslatableString(getKey(@"letterbox_during_breaks_description"), @"Adds horizontal letterboxing to give a cinematic look during breaks.");
+ public static LocalisableString LetterboxDuringBreaksDescription => new TranslatableString(getKey(@"letterbox_during_breaks_description"), @"Adds horizontal letterboxing to give a cinematic look during breaks.");
///
/// "Samples match playback rate"
@@ -102,8 +97,7 @@ namespace osu.Game.Localisation
///
/// "When enabled, all samples will speed up or slow down when rate-changing mods are enabled."
///
- public static LocalisableString SamplesMatchPlaybackRateDescription => new TranslatableString(getKey(@"samples_match_playback_rate_description"),
- @"When enabled, all samples will speed up or slow down when rate-changing mods are enabled.");
+ public static LocalisableString SamplesMatchPlaybackRateDescription => new TranslatableString(getKey(@"samples_match_playback_rate_description"), @"When enabled, all samples will speed up or slow down when rate-changing mods are enabled.");
///
/// "The size of all hit objects"
@@ -123,8 +117,27 @@ namespace osu.Game.Localisation
///
/// "The harshness of hit windows and difficulty of special objects (ie. spinners)"
///
- public static LocalisableString OverallDifficultyDescription =>
- new TranslatableString(getKey(@"overall_difficulty_description"), @"The harshness of hit windows and difficulty of special objects (ie. spinners)");
+ public static LocalisableString OverallDifficultyDescription => new TranslatableString(getKey(@"overall_difficulty_description"), @"The harshness of hit windows and difficulty of special objects (ie. spinners)");
+
+ ///
+ /// "Tick Rate"
+ ///
+ public static LocalisableString TickRate => new TranslatableString(getKey(@"tick_rate"), @"Tick Rate");
+
+ ///
+ /// "Determines how many "ticks" are generated within long hit objects. A tick rate of 1 will generate ticks on each beat, 2 would be twice per beat, etc."
+ ///
+ public static LocalisableString TickRateDescription => new TranslatableString(getKey(@"tick_rate_description"), @"Determines how many ""ticks"" are generated within long hit objects. A tick rate of 1 will generate ticks on each beat, 2 would be twice per beat, etc.");
+
+ ///
+ /// "Base Velocity"
+ ///
+ public static LocalisableString BaseVelocity => new TranslatableString(getKey(@"base_velocity"), @"Base Velocity");
+
+ ///
+ /// "The base velocity of the beatmap, affecting things like slider velocity and scroll speed in some rulesets."
+ ///
+ public static LocalisableString BaseVelocityDescription => new TranslatableString(getKey(@"base_velocity_description"), @"The base velocity of the beatmap, affecting things like slider velocity and scroll speed in some rulesets.");
///
/// "Metadata"
diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs
index c5274d6223..f4a79d65e6 100644
--- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Settings.Sections
new SettingsButton
{
Text = GeneralSettingsStrings.RunSetupWizard,
- Keywords = new[] { @"first run", @"initial", @"getting started" },
+ Keywords = new[] { @"first run", @"initial", @"getting started", @"import", @"tutorial", @"recommended beatmaps" },
TooltipText = FirstRunSetupOverlayStrings.FirstRunSetupDescription,
Action = () => firstRunSetupOverlay?.Show(),
},
diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs
index 597925e3e2..7beaf7d086 100644
--- a/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs
@@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (selected is IHasSliderVelocity sliderVelocity)
{
AddHeader("Slider Velocity");
- AddValue($"{sliderVelocity.SliderVelocity:#,0.00}x");
+ AddValue($"{sliderVelocity.SliderVelocity:#,0.00}x ({sliderVelocity.SliderVelocity * EditorBeatmap.Difficulty.SliderMultiplier:#,0.00}x)");
}
if (selected is IHasRepeats repeats)
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs
index 13a1c30cfe..173a665d5c 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs
@@ -96,7 +96,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
RelativeSizeAxes = Axes.X,
Text = "Hold shift while dragging the end of an object to adjust velocity while snapping."
},
- new SliderVelocityInspector(),
+ new SliderVelocityInspector(sliderVelocitySlider.Current),
}
}
};
@@ -145,34 +145,48 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
internal partial class SliderVelocityInspector : EditorInspector
{
+ private readonly Bindable current;
+
+ public SliderVelocityInspector(Bindable current)
+ {
+ this.current = current;
+ }
+
[BackgroundDependencyLoader]
private void load()
{
EditorBeatmap.TransactionBegan += updateInspectorText;
EditorBeatmap.TransactionEnded += updateInspectorText;
+ EditorBeatmap.BeatmapReprocessed += updateInspectorText;
+ current.ValueChanged += _ => updateInspectorText();
+
updateInspectorText();
}
private void updateInspectorText()
{
+ double beatmapVelocity = EditorBeatmap.Difficulty.SliderMultiplier;
+
InspectorText.Clear();
double[] sliderVelocities = EditorBeatmap.HitObjects.OfType().Select(sv => sv.SliderVelocity).OrderBy(v => v).ToArray();
- if (sliderVelocities.Length < 2)
- return;
+ AddHeader("Base velocity (from beatmap setup)");
+ AddValue($"{beatmapVelocity:#,0.00}x");
- double? modeSliderVelocity = sliderVelocities.GroupBy(v => v).MaxBy(v => v.Count())?.Key;
- double? medianSliderVelocity = sliderVelocities[sliderVelocities.Length / 2];
+ AddHeader("Final velocity");
+ AddValue($"{beatmapVelocity * current.Value:#,0.00}x");
- AddHeader("Average velocity");
- AddValue($"{medianSliderVelocity:#,0.00}x");
+ if (sliderVelocities.First() != sliderVelocities.Last())
+ {
+ AddHeader("Beatmap velocity range");
- AddHeader("Most used velocity");
- AddValue($"{modeSliderVelocity:#,0.00}x");
+ string range = $"{sliderVelocities.First():#,0.00}x - {sliderVelocities.Last():#,0.00}x";
+ if (beatmapVelocity != 1)
+ range += $" ({beatmapVelocity * sliderVelocities.First():#,0.00}x - {beatmapVelocity * sliderVelocities.Last():#,0.00}x)";
- AddHeader("Velocity range");
- AddValue($"{sliderVelocities.First():#,0.00}x - {sliderVelocities.Last():#,0.00}x");
+ AddValue(range);
+ }
}
protected override void Dispose(bool isDisposing)
@@ -181,6 +195,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
EditorBeatmap.TransactionBegan -= updateInspectorText;
EditorBeatmap.TransactionEnded -= updateInspectorText;
+ EditorBeatmap.BeatmapReprocessed -= updateInspectorText;
}
}
}
diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs
index 7026bde681..4c062b0cb7 100644
--- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs
+++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs
@@ -19,6 +19,8 @@ namespace osu.Game.Screens.Edit.Setup
private LabelledSliderBar healthDrainSlider = null!;
private LabelledSliderBar approachRateSlider = null!;
private LabelledSliderBar overallDifficultySlider = null!;
+ private LabelledSliderBar baseVelocitySlider = null!;
+ private LabelledSliderBar tickRateSlider = null!;
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
@@ -79,13 +81,42 @@ namespace osu.Game.Screens.Edit.Setup
Precision = 0.1f,
}
},
+ baseVelocitySlider = new LabelledSliderBar
+ {
+ Label = EditorSetupStrings.BaseVelocity,
+ FixedLabelWidth = LABEL_WIDTH,
+ Description = EditorSetupStrings.BaseVelocityDescription,
+ Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
+ {
+ Default = 1,
+ MinValue = 0.4,
+ MaxValue = 3.6,
+ Precision = 0.01f,
+ }
+ },
+ tickRateSlider = new LabelledSliderBar
+ {
+ Label = EditorSetupStrings.TickRate,
+ FixedLabelWidth = LABEL_WIDTH,
+ Description = EditorSetupStrings.TickRateDescription,
+ Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
+ {
+ Default = 1,
+ MinValue = 1,
+ MaxValue = 4,
+ Precision = 1,
+ }
+ },
};
foreach (var item in Children.OfType>())
- item.Current.ValueChanged += onValueChanged;
+ item.Current.ValueChanged += _ => updateValues();
+
+ foreach (var item in Children.OfType>())
+ item.Current.ValueChanged += _ => updateValues();
}
- private void onValueChanged(ValueChangedEvent args)
+ private void updateValues()
{
// for now, update these on commit rather than making BeatmapMetadata bindables.
// after switching database engines we can reconsider if switching to bindables is a good direction.
@@ -93,6 +124,8 @@ namespace osu.Game.Screens.Edit.Setup
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
Beatmap.Difficulty.ApproachRate = approachRateSlider.Current.Value;
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
+ Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
+ Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value;
Beatmap.UpdateAllHitObjects();
Beatmap.SaveState();
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 7ab9810ab5..4315f44e07 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -36,8 +36,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index bfa0dc63bb..c5477f765e 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -16,6 +16,6 @@
iossimulator-x64
-
+