mirror of
https://github.com/osukey/osukey.git
synced 2025-08-03 22:56:36 +09:00
Merge branch 'master' into new-chat-channel-control
This commit is contained in:
72
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
Normal file
72
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
name: Bug report
|
||||
description: Report a very clearly broken issue.
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# osu! bug report
|
||||
|
||||
Important to note that your issue may have already been reported before. Please check:
|
||||
- Pinned issues, at the top of https://github.com/ppy/osu/issues.
|
||||
- Current open `priority:0` issues, filterable [here](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Apriority%3A0).
|
||||
- And most importantly, search for your issue. If you find that it already exists, respond with a reaction or add any further information that may be helpful.
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Type
|
||||
options:
|
||||
- Crash to desktop
|
||||
- Game behaviour
|
||||
- Performance
|
||||
- Cosmetic
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Bug description
|
||||
description: How did you find the bug? Any additional details that might help?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Screenshots or videos
|
||||
description: Add screenshots or videos that show the bug here.
|
||||
placeholder: Drag and drop the screenshots/videos into this box.
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: The version you encountered this bug on. This is shown at the bottom of the main menu and also at the end of the settings screen.
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Logs
|
||||
|
||||
Attaching log files is required for every reported bug. See instructions below on how to find them.
|
||||
|
||||
If the game has not yet been closed since you found the bug:
|
||||
1. Head on to game settings and click on "Open osu! folder"
|
||||
2. Then open the `logs` folder located there
|
||||
|
||||
**Logs are reset when you reopen the game.** If the game crashed or has been closed since you found the bug, retrieve the logs using the file explorer instead.
|
||||
|
||||
The default places to find the logs are as follows:
|
||||
- `%AppData%/osu/logs` *on Windows*
|
||||
- `~/.local/share/osu/logs` *on Linux & macOS*
|
||||
- `Android/data/sh.ppy.osulazer/files/logs` *on Android*
|
||||
- *On iOS*, they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer)
|
||||
|
||||
If you have selected a custom location for the game files, you can find the `logs` folder there.
|
||||
|
||||
After locating the `logs` folder, select all log files inside and drag them into the "Logs" box below.
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Logs
|
||||
placeholder: Drag and drop the log files into this box.
|
||||
validations:
|
||||
required: true
|
@ -31,7 +31,7 @@ If you are looking to install or test osu! without setting up a development envi
|
||||
|
||||
**Latest build:**
|
||||
|
||||
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.15+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
||||
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
|
||||
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
|
||||
|
@ -1,11 +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 osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Pippidon.Scoring
|
||||
{
|
||||
public class PippidonScoreProcessor : ScoreProcessor
|
||||
{
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
scoreProcessor = new ScoreProcessor();
|
||||
scoreProcessor = new ScoreProcessor(new CatchRuleset());
|
||||
|
||||
SetContents(_ => new CatchComboDisplay
|
||||
{
|
||||
|
@ -19,7 +19,6 @@ using osu.Game.Rulesets.Catch.Difficulty;
|
||||
using osu.Game.Rulesets.Catch.Scoring;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using System;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Game.Rulesets.Catch.Edit;
|
||||
@ -182,7 +181,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
|
||||
public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin);
|
||||
|
||||
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score);
|
||||
public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator();
|
||||
|
||||
public int LegacyID => 2;
|
||||
|
||||
|
@ -13,33 +13,29 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
public class CatchPerformanceCalculator : PerformanceCalculator
|
||||
{
|
||||
protected new CatchDifficultyAttributes Attributes => (CatchDifficultyAttributes)base.Attributes;
|
||||
|
||||
private Mod[] mods;
|
||||
|
||||
private int fruitsHit;
|
||||
private int ticksHit;
|
||||
private int tinyTicksHit;
|
||||
private int tinyTicksMissed;
|
||||
private int misses;
|
||||
|
||||
public CatchPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
|
||||
: base(ruleset, attributes, score)
|
||||
public CatchPerformanceCalculator()
|
||||
: base(new CatchRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
public override PerformanceAttributes Calculate()
|
||||
protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes)
|
||||
{
|
||||
mods = Score.Mods;
|
||||
var catchAttributes = (CatchDifficultyAttributes)attributes;
|
||||
|
||||
fruitsHit = Score.Statistics.GetValueOrDefault(HitResult.Great);
|
||||
ticksHit = Score.Statistics.GetValueOrDefault(HitResult.LargeTickHit);
|
||||
tinyTicksHit = Score.Statistics.GetValueOrDefault(HitResult.SmallTickHit);
|
||||
tinyTicksMissed = Score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss);
|
||||
misses = Score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||
fruitsHit = score.Statistics.GetValueOrDefault(HitResult.Great);
|
||||
ticksHit = score.Statistics.GetValueOrDefault(HitResult.LargeTickHit);
|
||||
tinyTicksHit = score.Statistics.GetValueOrDefault(HitResult.SmallTickHit);
|
||||
tinyTicksMissed = score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss);
|
||||
misses = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||
|
||||
// We are heavily relying on aim in catch the beat
|
||||
double value = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0;
|
||||
double value = Math.Pow(5.0 * Math.Max(1.0, catchAttributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0;
|
||||
|
||||
// Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo
|
||||
int numTotalHits = totalComboHits();
|
||||
@ -52,10 +48,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
value *= Math.Pow(0.97, misses);
|
||||
|
||||
// Combo scaling
|
||||
if (Attributes.MaxCombo > 0)
|
||||
value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
if (catchAttributes.MaxCombo > 0)
|
||||
value *= Math.Min(Math.Pow(score.MaxCombo, 0.8) / Math.Pow(catchAttributes.MaxCombo, 0.8), 1.0);
|
||||
|
||||
double approachRate = Attributes.ApproachRate;
|
||||
double approachRate = catchAttributes.ApproachRate;
|
||||
double approachRateFactor = 1.0;
|
||||
if (approachRate > 9.0)
|
||||
approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9
|
||||
@ -66,7 +62,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
value *= approachRateFactor;
|
||||
|
||||
if (mods.Any(m => m is ModHidden))
|
||||
if (score.Mods.Any(m => m is ModHidden))
|
||||
{
|
||||
// Hiddens gives almost nothing on max approach rate, and more the lower it is
|
||||
if (approachRate <= 10.0)
|
||||
@ -75,12 +71,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
value *= 1.01 + 0.04 * (11.0 - Math.Min(11.0, approachRate)); // 5% at AR 10, 1% at AR 11
|
||||
}
|
||||
|
||||
if (mods.Any(m => m is ModFlashlight))
|
||||
if (score.Mods.Any(m => m is ModFlashlight))
|
||||
value *= 1.35 * lengthBonus;
|
||||
|
||||
value *= Math.Pow(accuracy(), 5.5);
|
||||
|
||||
if (mods.Any(m => m is ModNoFail))
|
||||
if (score.Mods.Any(m => m is ModNoFail))
|
||||
value *= 0.90;
|
||||
|
||||
return new CatchPerformanceAttributes
|
||||
|
@ -7,6 +7,11 @@ namespace osu.Game.Rulesets.Catch.Scoring
|
||||
{
|
||||
public class CatchScoreProcessor : ScoreProcessor
|
||||
{
|
||||
public CatchScoreProcessor()
|
||||
: base(new CatchRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
protected override double ClassicScoreMultiplier => 28;
|
||||
}
|
||||
}
|
||||
|
@ -13,10 +13,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
public class ManiaPerformanceCalculator : PerformanceCalculator
|
||||
{
|
||||
protected new ManiaDifficultyAttributes Attributes => (ManiaDifficultyAttributes)base.Attributes;
|
||||
|
||||
private Mod[] mods;
|
||||
|
||||
// Score after being scaled by non-difficulty-increasing mods
|
||||
private double scaledScore;
|
||||
|
||||
@ -27,39 +23,40 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
private int countMeh;
|
||||
private int countMiss;
|
||||
|
||||
public ManiaPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
|
||||
: base(ruleset, attributes, score)
|
||||
public ManiaPerformanceCalculator()
|
||||
: base(new ManiaRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
public override PerformanceAttributes Calculate()
|
||||
protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes)
|
||||
{
|
||||
mods = Score.Mods;
|
||||
scaledScore = Score.TotalScore;
|
||||
countPerfect = Score.Statistics.GetValueOrDefault(HitResult.Perfect);
|
||||
countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great);
|
||||
countGood = Score.Statistics.GetValueOrDefault(HitResult.Good);
|
||||
countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok);
|
||||
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||
var maniaAttributes = (ManiaDifficultyAttributes)attributes;
|
||||
|
||||
if (Attributes.ScoreMultiplier > 0)
|
||||
scaledScore = score.TotalScore;
|
||||
countPerfect = score.Statistics.GetValueOrDefault(HitResult.Perfect);
|
||||
countGreat = score.Statistics.GetValueOrDefault(HitResult.Great);
|
||||
countGood = score.Statistics.GetValueOrDefault(HitResult.Good);
|
||||
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
||||
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||
|
||||
if (maniaAttributes.ScoreMultiplier > 0)
|
||||
{
|
||||
// Scale score up, so it's comparable to other keymods
|
||||
scaledScore *= 1.0 / Attributes.ScoreMultiplier;
|
||||
scaledScore *= 1.0 / maniaAttributes.ScoreMultiplier;
|
||||
}
|
||||
|
||||
// Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
|
||||
// The specific number has no intrinsic meaning and can be adjusted as needed.
|
||||
double multiplier = 0.8;
|
||||
|
||||
if (mods.Any(m => m is ModNoFail))
|
||||
if (score.Mods.Any(m => m is ModNoFail))
|
||||
multiplier *= 0.9;
|
||||
if (mods.Any(m => m is ModEasy))
|
||||
if (score.Mods.Any(m => m is ModEasy))
|
||||
multiplier *= 0.5;
|
||||
|
||||
double difficultyValue = computeDifficultyValue();
|
||||
double accValue = computeAccuracyValue(difficultyValue);
|
||||
double difficultyValue = computeDifficultyValue(maniaAttributes);
|
||||
double accValue = computeAccuracyValue(difficultyValue, maniaAttributes);
|
||||
double totalValue =
|
||||
Math.Pow(
|
||||
Math.Pow(difficultyValue, 1.1) +
|
||||
@ -75,9 +72,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
};
|
||||
}
|
||||
|
||||
private double computeDifficultyValue()
|
||||
private double computeDifficultyValue(ManiaDifficultyAttributes attributes)
|
||||
{
|
||||
double difficultyValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
|
||||
double difficultyValue = Math.Pow(5 * Math.Max(1, attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
|
||||
|
||||
difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
|
||||
|
||||
@ -97,14 +94,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
return difficultyValue;
|
||||
}
|
||||
|
||||
private double computeAccuracyValue(double difficultyValue)
|
||||
private double computeAccuracyValue(double difficultyValue, ManiaDifficultyAttributes attributes)
|
||||
{
|
||||
if (Attributes.GreatHitWindow <= 0)
|
||||
if (attributes.GreatHitWindow <= 0)
|
||||
return 0;
|
||||
|
||||
// Lots of arbitrary values from testing.
|
||||
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
|
||||
double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
|
||||
double accuracyValue = Math.Max(0.0, 0.2 - (attributes.GreatHitWindow - 34) * 0.006667)
|
||||
* difficultyValue
|
||||
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
|
||||
|
||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
|
||||
|
||||
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new ManiaPerformanceCalculator(this, attributes, score);
|
||||
public override PerformanceCalculator CreatePerformanceCalculator() => new ManiaPerformanceCalculator();
|
||||
|
||||
public const string SHORT_NAME = "mania";
|
||||
|
||||
|
@ -7,6 +7,11 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
||||
{
|
||||
internal class ManiaScoreProcessor : ScoreProcessor
|
||||
{
|
||||
public ManiaScoreProcessor()
|
||||
: base(new ManiaRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
protected override double DefaultAccuracyPortion => 0.99;
|
||||
|
||||
protected override double DefaultComboPortion => 0.01;
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
@ -14,10 +13,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public class OsuPerformanceCalculator : PerformanceCalculator
|
||||
{
|
||||
public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes;
|
||||
|
||||
private Mod[] mods;
|
||||
|
||||
private double accuracy;
|
||||
private int scoreMaxCombo;
|
||||
private int countGreat;
|
||||
@ -27,31 +22,32 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
private double effectiveMissCount;
|
||||
|
||||
public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
|
||||
: base(ruleset, attributes, score)
|
||||
public OsuPerformanceCalculator()
|
||||
: base(new OsuRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
public override PerformanceAttributes Calculate()
|
||||
protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes)
|
||||
{
|
||||
mods = Score.Mods;
|
||||
accuracy = Score.Accuracy;
|
||||
scoreMaxCombo = Score.MaxCombo;
|
||||
countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great);
|
||||
countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok);
|
||||
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||
effectiveMissCount = calculateEffectiveMissCount();
|
||||
var osuAttributes = (OsuDifficultyAttributes)attributes;
|
||||
|
||||
accuracy = score.Accuracy;
|
||||
scoreMaxCombo = score.MaxCombo;
|
||||
countGreat = score.Statistics.GetValueOrDefault(HitResult.Great);
|
||||
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
||||
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||
effectiveMissCount = calculateEffectiveMissCount(osuAttributes);
|
||||
|
||||
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
||||
|
||||
if (mods.Any(m => m is OsuModNoFail))
|
||||
if (score.Mods.Any(m => m is OsuModNoFail))
|
||||
multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount);
|
||||
|
||||
if (mods.Any(m => m is OsuModSpunOut) && totalHits > 0)
|
||||
multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85);
|
||||
if (score.Mods.Any(m => m is OsuModSpunOut) && totalHits > 0)
|
||||
multiplier *= 1.0 - Math.Pow((double)osuAttributes.SpinnerCount / totalHits, 0.85);
|
||||
|
||||
if (mods.Any(h => h is OsuModRelax))
|
||||
if (score.Mods.Any(h => h is OsuModRelax))
|
||||
{
|
||||
// As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it.
|
||||
effectiveMissCount = Math.Min(effectiveMissCount + countOk + countMeh, totalHits);
|
||||
@ -59,10 +55,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
multiplier *= 0.6;
|
||||
}
|
||||
|
||||
double aimValue = computeAimValue();
|
||||
double speedValue = computeSpeedValue();
|
||||
double accuracyValue = computeAccuracyValue();
|
||||
double flashlightValue = computeFlashlightValue();
|
||||
double aimValue = computeAimValue(score, osuAttributes);
|
||||
double speedValue = computeSpeedValue(score, osuAttributes);
|
||||
double accuracyValue = computeAccuracyValue(score, osuAttributes);
|
||||
double flashlightValue = computeFlashlightValue(score, osuAttributes);
|
||||
double totalValue =
|
||||
Math.Pow(
|
||||
Math.Pow(aimValue, 1.1) +
|
||||
@ -82,11 +78,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
};
|
||||
}
|
||||
|
||||
private double computeAimValue()
|
||||
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||
{
|
||||
double rawAim = Attributes.AimDifficulty;
|
||||
double rawAim = attributes.AimDifficulty;
|
||||
|
||||
if (mods.Any(m => m is OsuModTouchDevice))
|
||||
if (score.Mods.Any(m => m is OsuModTouchDevice))
|
||||
rawAim = Math.Pow(rawAim, 0.8);
|
||||
|
||||
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
@ -99,44 +95,44 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (effectiveMissCount > 0)
|
||||
aimValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), effectiveMissCount);
|
||||
|
||||
aimValue *= getComboScalingFactor();
|
||||
aimValue *= getComboScalingFactor(attributes);
|
||||
|
||||
double approachRateFactor = 0.0;
|
||||
if (Attributes.ApproachRate > 10.33)
|
||||
approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33);
|
||||
else if (Attributes.ApproachRate < 8.0)
|
||||
approachRateFactor = 0.1 * (8.0 - Attributes.ApproachRate);
|
||||
if (attributes.ApproachRate > 10.33)
|
||||
approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33);
|
||||
else if (attributes.ApproachRate < 8.0)
|
||||
approachRateFactor = 0.1 * (8.0 - attributes.ApproachRate);
|
||||
|
||||
aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR.
|
||||
|
||||
if (mods.Any(m => m is OsuModBlinds))
|
||||
aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * Attributes.DrainRate * Attributes.DrainRate);
|
||||
else if (mods.Any(h => h is OsuModHidden))
|
||||
if (score.Mods.Any(m => m is OsuModBlinds))
|
||||
aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate);
|
||||
else if (score.Mods.Any(h => h is OsuModHidden))
|
||||
{
|
||||
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
||||
aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
||||
aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate);
|
||||
}
|
||||
|
||||
// We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator.
|
||||
double estimateDifficultSliders = Attributes.SliderCount * 0.15;
|
||||
double estimateDifficultSliders = attributes.SliderCount * 0.15;
|
||||
|
||||
if (Attributes.SliderCount > 0)
|
||||
if (attributes.SliderCount > 0)
|
||||
{
|
||||
double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders);
|
||||
double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor;
|
||||
double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders);
|
||||
double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor;
|
||||
aimValue *= sliderNerfFactor;
|
||||
}
|
||||
|
||||
aimValue *= accuracy;
|
||||
// It is important to consider accuracy difficulty when scaling with accuracy.
|
||||
aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
|
||||
aimValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500;
|
||||
|
||||
return aimValue;
|
||||
}
|
||||
|
||||
private double computeSpeedValue()
|
||||
private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||
{
|
||||
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
|
||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
@ -146,27 +142,27 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (effectiveMissCount > 0)
|
||||
speedValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
|
||||
|
||||
speedValue *= getComboScalingFactor();
|
||||
speedValue *= getComboScalingFactor(attributes);
|
||||
|
||||
double approachRateFactor = 0.0;
|
||||
if (Attributes.ApproachRate > 10.33)
|
||||
approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33);
|
||||
if (attributes.ApproachRate > 10.33)
|
||||
approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33);
|
||||
|
||||
speedValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR.
|
||||
|
||||
if (mods.Any(m => m is OsuModBlinds))
|
||||
if (score.Mods.Any(m => m is OsuModBlinds))
|
||||
{
|
||||
// Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given.
|
||||
speedValue *= 1.12;
|
||||
}
|
||||
else if (mods.Any(m => m is OsuModHidden))
|
||||
else if (score.Mods.Any(m => m is OsuModHidden))
|
||||
{
|
||||
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
||||
speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
||||
speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate);
|
||||
}
|
||||
|
||||
// Scale the speed value with accuracy and OD.
|
||||
speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2);
|
||||
speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2);
|
||||
|
||||
// Scale the speed value with # of 50s to punish doubletapping.
|
||||
speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
||||
@ -174,14 +170,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return speedValue;
|
||||
}
|
||||
|
||||
private double computeAccuracyValue()
|
||||
private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||
{
|
||||
if (mods.Any(h => h is OsuModRelax))
|
||||
if (score.Mods.Any(h => h is OsuModRelax))
|
||||
return 0.0;
|
||||
|
||||
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window.
|
||||
double betterAccuracyPercentage;
|
||||
int amountHitObjectsWithAccuracy = Attributes.HitCircleCount;
|
||||
int amountHitObjectsWithAccuracy = attributes.HitCircleCount;
|
||||
|
||||
if (amountHitObjectsWithAccuracy > 0)
|
||||
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);
|
||||
@ -194,43 +190,43 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
// Lots of arbitrary values from testing.
|
||||
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution.
|
||||
double accuracyValue = Math.Pow(1.52163, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83;
|
||||
double accuracyValue = Math.Pow(1.52163, attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83;
|
||||
|
||||
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer.
|
||||
accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3));
|
||||
|
||||
// Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given.
|
||||
if (mods.Any(m => m is OsuModBlinds))
|
||||
if (score.Mods.Any(m => m is OsuModBlinds))
|
||||
accuracyValue *= 1.14;
|
||||
else if (mods.Any(m => m is OsuModHidden))
|
||||
else if (score.Mods.Any(m => m is OsuModHidden))
|
||||
accuracyValue *= 1.08;
|
||||
|
||||
if (mods.Any(m => m is OsuModFlashlight))
|
||||
if (score.Mods.Any(m => m is OsuModFlashlight))
|
||||
accuracyValue *= 1.02;
|
||||
|
||||
return accuracyValue;
|
||||
}
|
||||
|
||||
private double computeFlashlightValue()
|
||||
private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||
{
|
||||
if (!mods.Any(h => h is OsuModFlashlight))
|
||||
if (!score.Mods.Any(h => h is OsuModFlashlight))
|
||||
return 0.0;
|
||||
|
||||
double rawFlashlight = Attributes.FlashlightDifficulty;
|
||||
double rawFlashlight = attributes.FlashlightDifficulty;
|
||||
|
||||
if (mods.Any(m => m is OsuModTouchDevice))
|
||||
if (score.Mods.Any(m => m is OsuModTouchDevice))
|
||||
rawFlashlight = Math.Pow(rawFlashlight, 0.8);
|
||||
|
||||
double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0;
|
||||
|
||||
if (mods.Any(h => h is OsuModHidden))
|
||||
if (score.Mods.Any(h => h is OsuModHidden))
|
||||
flashlightValue *= 1.3;
|
||||
|
||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||
if (effectiveMissCount > 0)
|
||||
flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
|
||||
|
||||
flashlightValue *= getComboScalingFactor();
|
||||
flashlightValue *= getComboScalingFactor(attributes);
|
||||
|
||||
// Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius.
|
||||
flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, totalHits / 200.0) +
|
||||
@ -239,19 +235,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
// Scale the flashlight value with accuracy _slightly_.
|
||||
flashlightValue *= 0.5 + accuracy / 2.0;
|
||||
// It is important to also consider accuracy difficulty when doing that.
|
||||
flashlightValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
|
||||
flashlightValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500;
|
||||
|
||||
return flashlightValue;
|
||||
}
|
||||
|
||||
private double calculateEffectiveMissCount()
|
||||
private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes)
|
||||
{
|
||||
// Guess the number of misses + slider breaks from combo
|
||||
double comboBasedMissCount = 0.0;
|
||||
|
||||
if (Attributes.SliderCount > 0)
|
||||
if (attributes.SliderCount > 0)
|
||||
{
|
||||
double fullComboThreshold = Attributes.MaxCombo - 0.1 * Attributes.SliderCount;
|
||||
double fullComboThreshold = attributes.MaxCombo - 0.1 * attributes.SliderCount;
|
||||
if (scoreMaxCombo < fullComboThreshold)
|
||||
comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
|
||||
}
|
||||
@ -262,7 +258,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return Math.Max(countMiss, comboBasedMissCount);
|
||||
}
|
||||
|
||||
private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0);
|
||||
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
||||
private int totalSuccessfulHits => countGreat + countOk + countMeh;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override string Description => "No need to chase the circle – the circle chases you!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax) };
|
||||
|
||||
private IFrameStableClock gameplayClock;
|
||||
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
||||
{
|
||||
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModAimAssist) }).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// How early before a hitobject's start time to trigger a hit.
|
||||
|
@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(RulesetInfo, beatmap);
|
||||
|
||||
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new OsuPerformanceCalculator(this, attributes, score);
|
||||
public override PerformanceCalculator CreatePerformanceCalculator() => new OsuPerformanceCalculator();
|
||||
|
||||
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
|
||||
|
||||
|
@ -11,6 +11,11 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
||||
{
|
||||
public class OsuScoreProcessor : ScoreProcessor
|
||||
{
|
||||
public OsuScoreProcessor()
|
||||
: base(new OsuRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
protected override double ClassicScoreMultiplier => 36;
|
||||
|
||||
protected override HitEvent CreateHitEvent(JudgementResult result)
|
||||
|
@ -14,37 +14,35 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
public class TaikoPerformanceCalculator : PerformanceCalculator
|
||||
{
|
||||
protected new TaikoDifficultyAttributes Attributes => (TaikoDifficultyAttributes)base.Attributes;
|
||||
|
||||
private Mod[] mods;
|
||||
private int countGreat;
|
||||
private int countOk;
|
||||
private int countMeh;
|
||||
private int countMiss;
|
||||
|
||||
public TaikoPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
|
||||
: base(ruleset, attributes, score)
|
||||
public TaikoPerformanceCalculator()
|
||||
: base(new TaikoRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
public override PerformanceAttributes Calculate()
|
||||
protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes)
|
||||
{
|
||||
mods = Score.Mods;
|
||||
countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great);
|
||||
countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok);
|
||||
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||
var taikoAttributes = (TaikoDifficultyAttributes)attributes;
|
||||
|
||||
countGreat = score.Statistics.GetValueOrDefault(HitResult.Great);
|
||||
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
||||
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||
|
||||
double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
||||
|
||||
if (mods.Any(m => m is ModNoFail))
|
||||
if (score.Mods.Any(m => m is ModNoFail))
|
||||
multiplier *= 0.90;
|
||||
|
||||
if (mods.Any(m => m is ModHidden))
|
||||
if (score.Mods.Any(m => m is ModHidden))
|
||||
multiplier *= 1.10;
|
||||
|
||||
double difficultyValue = computeDifficultyValue();
|
||||
double accuracyValue = computeAccuracyValue();
|
||||
double difficultyValue = computeDifficultyValue(score, taikoAttributes);
|
||||
double accuracyValue = computeAccuracyValue(score, taikoAttributes);
|
||||
double totalValue =
|
||||
Math.Pow(
|
||||
Math.Pow(difficultyValue, 1.1) +
|
||||
@ -59,30 +57,30 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
};
|
||||
}
|
||||
|
||||
private double computeDifficultyValue()
|
||||
private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
|
||||
{
|
||||
double difficultyValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
|
||||
double difficultyValue = Math.Pow(5.0 * Math.Max(1.0, attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
|
||||
|
||||
double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
|
||||
difficultyValue *= lengthBonus;
|
||||
|
||||
difficultyValue *= Math.Pow(0.985, countMiss);
|
||||
|
||||
if (mods.Any(m => m is ModHidden))
|
||||
if (score.Mods.Any(m => m is ModHidden))
|
||||
difficultyValue *= 1.025;
|
||||
|
||||
if (mods.Any(m => m is ModFlashlight<TaikoHitObject>))
|
||||
if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>))
|
||||
difficultyValue *= 1.05 * lengthBonus;
|
||||
|
||||
return difficultyValue * Score.Accuracy;
|
||||
return difficultyValue * score.Accuracy;
|
||||
}
|
||||
|
||||
private double computeAccuracyValue()
|
||||
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
|
||||
{
|
||||
if (Attributes.GreatHitWindow <= 0)
|
||||
if (attributes.GreatHitWindow <= 0)
|
||||
return 0;
|
||||
|
||||
double accValue = Math.Pow(150.0 / Attributes.GreatHitWindow, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
|
||||
double accValue = Math.Pow(150.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 15) * 22.0;
|
||||
|
||||
// Bonus for many objects - it's harder to keep good accuracy up for longer
|
||||
return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
|
||||
|
@ -7,6 +7,11 @@ namespace osu.Game.Rulesets.Taiko.Scoring
|
||||
{
|
||||
internal class TaikoScoreProcessor : ScoreProcessor
|
||||
{
|
||||
public TaikoScoreProcessor()
|
||||
: base(new TaikoRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
protected override double DefaultAccuracyPortion => 0.75;
|
||||
|
||||
protected override double DefaultComboPortion => 0.25;
|
||||
|
@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap);
|
||||
|
||||
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new TaikoPerformanceCalculator(this, attributes, score);
|
||||
public override PerformanceCalculator CreatePerformanceCalculator() => new TaikoPerformanceCalculator();
|
||||
|
||||
public int LegacyID => 1;
|
||||
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject> { HitObjects = { new HitObject() } };
|
||||
|
||||
var scoreProcessor = new ScoreProcessor();
|
||||
var scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
|
||||
// Apply a miss judgement
|
||||
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject> { HitObjects = { new HitObject() } };
|
||||
|
||||
var scoreProcessor = new ScoreProcessor();
|
||||
var scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
|
||||
// Apply a judgement
|
||||
@ -53,7 +53,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject> { HitObjects = { new HitCircle() } };
|
||||
|
||||
var scoreProcessor = new ScoreProcessor();
|
||||
var scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great });
|
||||
|
@ -133,6 +133,7 @@ namespace osu.Game.Tests.Resources
|
||||
StarRating = diff,
|
||||
Length = length,
|
||||
BPM = bpm,
|
||||
MaxCombo = 1000,
|
||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
Ruleset = rulesetInfo,
|
||||
Metadata = metadata,
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
scoreProcessor = new ScoreProcessor();
|
||||
scoreProcessor = new ScoreProcessor(new TestRuleset());
|
||||
beatmap = new TestBeatmap(new RulesetInfo())
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
|
@ -18,9 +18,11 @@ using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
@ -32,11 +34,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private SkinManager skinManager { get; set; }
|
||||
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
|
||||
[Cached(typeof(HealthProcessor))]
|
||||
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||
|
||||
[Cached]
|
||||
private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset());
|
||||
|
||||
[Cached]
|
||||
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
|
||||
|
||||
protected override bool HasCustomSteps => true;
|
||||
|
||||
[Test]
|
||||
|
@ -8,11 +8,14 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
@ -24,11 +27,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private HUDOverlay hudOverlay;
|
||||
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
|
||||
[Cached(typeof(HealthProcessor))]
|
||||
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||
|
||||
[Cached]
|
||||
private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset());
|
||||
|
||||
[Cached]
|
||||
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
|
||||
|
||||
// best way to check without exposing.
|
||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
|
||||
|
@ -277,6 +277,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private class TestScoreProcessor : ScoreProcessor
|
||||
{
|
||||
public TestScoreProcessor()
|
||||
: base(new OsuRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
public void Reset() => base.Reset(false);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -107,7 +108,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
AddStep("create player", () =>
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(beatmap, storyboard);
|
||||
Beatmap.Value = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), Audio);
|
||||
LoadScreen(player = new LeadInPlayer());
|
||||
});
|
||||
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
gameplayState = new GameplayState(beatmap, ruleset);
|
||||
gameplayState.LastJudgementResult.BindTo(lastJudgementResult);
|
||||
|
||||
scoreProcessor = new ScoreProcessor();
|
||||
scoreProcessor = new ScoreProcessor(ruleset);
|
||||
|
||||
Child = dependencyContainer = new DependencyProvidingContainer
|
||||
{
|
||||
|
@ -2,8 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Skinning.Editor;
|
||||
@ -16,6 +18,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
@ -24,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddStep("reload skin editor", () =>
|
||||
{
|
||||
skinEditor?.Expire();
|
||||
Player.ScaleTo(SkinEditorOverlay.VISIBLE_TARGET_SCALE);
|
||||
Player.ScaleTo(0.8f);
|
||||
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
|
||||
});
|
||||
}
|
||||
|
@ -2,7 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Skinning.Editor;
|
||||
@ -11,13 +13,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneSkinEditorComponentsList : SkinnableTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
[Test]
|
||||
public void TestToggleEditor()
|
||||
{
|
||||
AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox(300)
|
||||
AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Width = 0.6f,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -5,11 +5,13 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning.Editor;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
@ -17,11 +19,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public class TestSceneSkinEditorMultipleSkins : SkinnableTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
|
||||
[Cached(typeof(HealthProcessor))]
|
||||
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||
|
||||
[Cached]
|
||||
private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset());
|
||||
|
||||
[Cached]
|
||||
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
@ -14,7 +15,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public class TestSceneSkinnableAccuracyCounter : SkinnableHUDComponentTestScene
|
||||
{
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
|
||||
protected override Drawable CreateDefaultImplementation() => new DefaultAccuracyCounter();
|
||||
protected override Drawable CreateLegacyImplementation() => new LegacyAccuracyCounter();
|
||||
|
@ -5,6 +5,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
|
||||
@ -13,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public class TestSceneSkinnableComboCounter : SkinnableHUDComponentTestScene
|
||||
{
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
|
||||
protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter();
|
||||
protected override Drawable CreateLegacyImplementation() => new LegacyComboCounter();
|
||||
|
@ -10,11 +10,13 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
@ -24,11 +26,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private HUDOverlay hudOverlay;
|
||||
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
|
||||
[Cached(typeof(HealthProcessor))]
|
||||
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||
|
||||
[Cached]
|
||||
private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset());
|
||||
|
||||
[Cached]
|
||||
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
|
||||
|
||||
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();
|
||||
|
||||
// best way to check without exposing.
|
||||
|
@ -4,6 +4,7 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
@ -13,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public class TestSceneSkinnableScoreCounter : SkinnableHUDComponentTestScene
|
||||
{
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
|
||||
protected override Drawable CreateDefaultImplementation() => new DefaultScoreCounter();
|
||||
protected override Drawable CreateLegacyImplementation() => new LegacyScoreCounter();
|
||||
|
@ -70,6 +70,56 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSeekToGameplayStartFramesArriveAfterPlayerLoad()
|
||||
{
|
||||
const double gameplay_start = 10000;
|
||||
|
||||
loadSpectatingScreen();
|
||||
|
||||
start();
|
||||
|
||||
waitForPlayer();
|
||||
|
||||
sendFrames(startTime: gameplay_start);
|
||||
|
||||
AddAssert("time is greater than seek target", () => currentFrameStableTime > gameplay_start);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the same as <see cref="TestSeekToGameplayStartFramesArriveAfterPlayerLoad"/> but with the frames arriving just as <see cref="Player"/> is transitioning into existence.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestSeekToGameplayStartFramesArriveAsPlayerLoaded()
|
||||
{
|
||||
const double gameplay_start = 10000;
|
||||
|
||||
loadSpectatingScreen();
|
||||
|
||||
start();
|
||||
|
||||
AddUntilStep("wait for player loader", () => (Stack.CurrentScreen as PlayerLoader)?.IsLoaded == true);
|
||||
|
||||
AddUntilStep("queue send frames on player load", () =>
|
||||
{
|
||||
var loadingPlayer = (Stack.CurrentScreen as PlayerLoader)?.CurrentPlayer;
|
||||
|
||||
if (loadingPlayer == null)
|
||||
return false;
|
||||
|
||||
loadingPlayer.OnLoadComplete += _ =>
|
||||
{
|
||||
spectatorClient.SendFramesFromUser(streamingUser.Id, 10, gameplay_start);
|
||||
};
|
||||
return true;
|
||||
});
|
||||
|
||||
waitForPlayer();
|
||||
|
||||
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
||||
AddAssert("time is greater than seek target", () => currentFrameStableTime > gameplay_start);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFrameStarvationAndResume()
|
||||
{
|
||||
@ -319,9 +369,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private void checkPaused(bool state) =>
|
||||
AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType<DrawableRuleset>().First().IsPaused.Value == state);
|
||||
|
||||
private void sendFrames(int count = 10)
|
||||
private void sendFrames(int count = 10, double startTime = 0)
|
||||
{
|
||||
AddStep("send frames", () => spectatorClient.SendFramesFromUser(streamingUser.Id, count));
|
||||
AddStep("send frames", () => spectatorClient.SendFramesFromUser(streamingUser.Id, count, startTime));
|
||||
}
|
||||
|
||||
private void loadSpectatingScreen()
|
||||
|
@ -6,6 +6,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -102,6 +103,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private class TestScoreProcessor : ScoreProcessor
|
||||
{
|
||||
public TestScoreProcessor()
|
||||
: base(new OsuRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
public void Reset() => base.Reset(false);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
@ -129,6 +131,25 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("item 1 not in lists", () => !inHistoryList(0) && !inQueueList(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestQueueTabCount()
|
||||
{
|
||||
assertQueueTabCount(1);
|
||||
|
||||
addItemStep();
|
||||
assertQueueTabCount(2);
|
||||
|
||||
addItemStep();
|
||||
assertQueueTabCount(3);
|
||||
|
||||
AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely());
|
||||
assertQueueTabCount(2);
|
||||
|
||||
AddStep("leave room", () => RoomManager.PartRoom());
|
||||
AddUntilStep("wait for room part", () => !RoomJoined);
|
||||
assertQueueTabCount(0);
|
||||
}
|
||||
|
||||
[Ignore("Expired items are initially removed from the room.")]
|
||||
[Test]
|
||||
public void TestJoinRoomWithMixedItemsAddedInCorrectLists()
|
||||
@ -213,6 +234,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
}
|
||||
|
||||
private void assertQueueTabCount(int count)
|
||||
{
|
||||
string queueTabText = count > 0 ? $"Queue ({count})" : "Queue";
|
||||
AddUntilStep($"Queue tab shows \"{queueTabText}\"", () =>
|
||||
{
|
||||
return this.ChildrenOfType<OsuTabControl<MultiplayerPlaylistDisplayMode>.OsuTabItem>()
|
||||
.Single(t => t.Value == MultiplayerPlaylistDisplayMode.Queue)
|
||||
.ChildrenOfType<OsuSpriteText>().Single().Text == queueTabText;
|
||||
});
|
||||
}
|
||||
|
||||
private void changeDisplayModeStep(MultiplayerPlaylistDisplayMode mode) => AddStep($"change list to {mode}", () => list.DisplayMode.Value = mode);
|
||||
|
||||
private bool inQueueList(int playlistItemId)
|
||||
|
@ -1,9 +1,7 @@
|
||||
// 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 System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
@ -35,8 +33,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private BeatmapManager beatmaps;
|
||||
private RulesetStore rulesets;
|
||||
|
||||
private IDisposable readyClickOperation;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
@ -67,23 +63,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200, 50),
|
||||
OnReadyClick = () =>
|
||||
{
|
||||
readyClickOperation = OngoingOperationTracker.BeginOperation();
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
if (MultiplayerClient.IsHost && MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready)
|
||||
{
|
||||
await MultiplayerClient.StartMatch();
|
||||
return;
|
||||
}
|
||||
|
||||
await MultiplayerClient.ToggleReady();
|
||||
|
||||
readyClickOperation.Dispose();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -169,7 +148,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||
AddUntilStep("user is idle (match not started)", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle);
|
||||
AddAssert("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||
AddUntilStep("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
@ -208,9 +187,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||
AddUntilStep("user waiting for load", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
||||
|
||||
AddAssert("ready button disabled", () => !button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||
AddStep("transitioned to gameplay", () => readyClickOperation.Dispose());
|
||||
|
||||
AddStep("finish gameplay", () =>
|
||||
{
|
||||
MultiplayerClient.ChangeUserState(MultiplayerClient.Room?.Users[0].UserID ?? 0, MultiplayerUserState.Loaded);
|
||||
|
@ -1,9 +1,7 @@
|
||||
// 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 System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
@ -36,8 +34,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private BeatmapManager beatmaps;
|
||||
private RulesetStore rulesets;
|
||||
|
||||
private IDisposable readyClickOperation;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
@ -71,39 +67,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200, 50),
|
||||
OnSpectateClick = () =>
|
||||
{
|
||||
readyClickOperation = OngoingOperationTracker.BeginOperation();
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await MultiplayerClient.ToggleSpectate();
|
||||
readyClickOperation.Dispose();
|
||||
});
|
||||
}
|
||||
},
|
||||
readyButton = new MultiplayerReadyButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200, 50),
|
||||
OnReadyClick = () =>
|
||||
{
|
||||
readyClickOperation = OngoingOperationTracker.BeginOperation();
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
if (MultiplayerClient.IsHost && MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready)
|
||||
{
|
||||
await MultiplayerClient.StartMatch();
|
||||
return;
|
||||
}
|
||||
|
||||
await MultiplayerClient.ToggleReady();
|
||||
|
||||
readyClickOperation.Dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -122,6 +122,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestHideOverlay()
|
||||
{
|
||||
AddStep("Open chat overlay", () => chatOverlay.Show());
|
||||
|
||||
AddAssert("Chat overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
|
||||
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
|
||||
|
||||
@ -134,6 +136,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestChannelSelection()
|
||||
{
|
||||
AddStep("Open chat overlay", () => chatOverlay.Show());
|
||||
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
|
||||
AddStep("Setup get message response", () => onGetMessages = channel =>
|
||||
{
|
||||
@ -169,6 +172,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestSearchInSelector()
|
||||
{
|
||||
AddStep("Open chat overlay", () => chatOverlay.Show());
|
||||
AddStep("Search for 'no. 2'", () => chatOverlay.ChildrenOfType<SearchTextBox>().First().Text = "no. 2");
|
||||
AddUntilStep("Only channel 2 visible", () =>
|
||||
{
|
||||
@ -180,6 +184,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestChannelShortcutKeys()
|
||||
{
|
||||
AddStep("Open chat overlay", () => chatOverlay.Show());
|
||||
AddStep("Join channels", () => channels.ForEach(channel => channelManager.JoinChannel(channel)));
|
||||
AddStep("Close channel selector", () => InputManager.Key(Key.Escape));
|
||||
AddUntilStep("Wait for close", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
|
||||
@ -199,6 +204,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestCloseChannelBehaviour()
|
||||
{
|
||||
AddStep("Open chat overlay", () => chatOverlay.Show());
|
||||
AddUntilStep("Join until dropdown has channels", () =>
|
||||
{
|
||||
if (visibleChannels.Count() < joinedChannels.Count())
|
||||
@ -269,6 +275,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestChannelCloseButton()
|
||||
{
|
||||
AddStep("Open chat overlay", () => chatOverlay.Show());
|
||||
AddStep("Join 2 channels", () =>
|
||||
{
|
||||
channelManager.JoinChannel(channel1);
|
||||
@ -289,6 +296,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestCloseTabShortcut()
|
||||
{
|
||||
AddStep("Open chat overlay", () => chatOverlay.Show());
|
||||
AddStep("Join 2 channels", () =>
|
||||
{
|
||||
channelManager.JoinChannel(channel1);
|
||||
@ -314,6 +322,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestNewTabShortcut()
|
||||
{
|
||||
AddStep("Open chat overlay", () => chatOverlay.Show());
|
||||
AddStep("Join 2 channels", () =>
|
||||
{
|
||||
channelManager.JoinChannel(channel1);
|
||||
@ -330,6 +339,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestRestoreTabShortcut()
|
||||
{
|
||||
AddStep("Open chat overlay", () => chatOverlay.Show());
|
||||
AddStep("Join 3 channels", () =>
|
||||
{
|
||||
channelManager.JoinChannel(channel1);
|
||||
@ -375,6 +385,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestChatCommand()
|
||||
{
|
||||
AddStep("Open chat overlay", () => chatOverlay.Show());
|
||||
AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
|
||||
AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
|
||||
|
||||
@ -398,6 +409,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
Channel multiplayerChannel = null;
|
||||
|
||||
AddStep("open chat overlay", () => chatOverlay.Show());
|
||||
|
||||
AddStep("join multiplayer channel", () => channelManager.JoinChannel(multiplayerChannel = new Channel(new APIUser())
|
||||
{
|
||||
Name = "#mp_1",
|
||||
@ -417,6 +430,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
Message message = null;
|
||||
|
||||
AddStep("Open chat overlay", () => chatOverlay.Show());
|
||||
AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
|
||||
AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
|
||||
|
||||
@ -443,6 +457,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
Message message = null;
|
||||
|
||||
AddStep("Open chat overlay", () => chatOverlay.Show());
|
||||
AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
|
||||
AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
|
||||
|
||||
@ -471,6 +486,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
Message message = null;
|
||||
|
||||
AddStep("Open chat overlay", () => chatOverlay.Show());
|
||||
|
||||
AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
|
||||
AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
|
||||
|
||||
@ -496,14 +513,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHighlightWhileChatHidden()
|
||||
public void TestHighlightWhileChatNeverOpen()
|
||||
{
|
||||
Message message = null;
|
||||
|
||||
AddStep("hide chat", () => chatOverlay.Hide());
|
||||
|
||||
AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
|
||||
AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
|
||||
|
||||
AddStep("Send message in channel 1", () =>
|
||||
{
|
||||
@ -520,7 +534,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("Highlight message and show chat", () =>
|
||||
AddStep("Highlight message and open chat", () =>
|
||||
{
|
||||
chatOverlay.HighlightMessage(message, channel1);
|
||||
chatOverlay.Show();
|
||||
@ -571,8 +585,6 @@ namespace osu.Game.Tests.Visual.Online
|
||||
ChannelManager,
|
||||
ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, },
|
||||
};
|
||||
|
||||
ChatOverlay.Show();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
private int messageIdCounter;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
if (API is DummyAPIAccess daa)
|
||||
{
|
||||
@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
testContainer.ChatOverlay.Show();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
private bool dummyAPIHandleRequest(APIRequest request)
|
||||
{
|
||||
|
@ -40,24 +40,33 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
private int totalCount;
|
||||
private ScoreInfo userScore;
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
lowestScoreId = 1;
|
||||
highestScoreId = 1;
|
||||
requestComplete = false;
|
||||
totalCount = 0;
|
||||
base.SetUpSteps();
|
||||
|
||||
userScore = TestResources.CreateTestScoreInfo();
|
||||
userScore.TotalScore = 0;
|
||||
userScore.Statistics = new Dictionary<HitResult, int>();
|
||||
// Previous test instances of the results screen may still exist at this point so wait for
|
||||
// those screens to be cleaned up by the base SetUpSteps before re-initialising test state.
|
||||
// The the screen also holds a leased Beatmap bindable so reassigning it must happen after
|
||||
// the screen as been exited.
|
||||
AddStep("initialise user scores and beatmap", () =>
|
||||
{
|
||||
lowestScoreId = 1;
|
||||
highestScoreId = 1;
|
||||
requestComplete = false;
|
||||
totalCount = 0;
|
||||
|
||||
bindHandler();
|
||||
userScore = TestResources.CreateTestScoreInfo();
|
||||
userScore.TotalScore = 0;
|
||||
userScore.Statistics = new Dictionary<HitResult, int>();
|
||||
|
||||
// beatmap is required to be an actual beatmap so the scores can get their scores correctly calculated for standardised scoring.
|
||||
// else the tests that rely on ordering will fall over.
|
||||
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||
});
|
||||
bindHandler();
|
||||
|
||||
// Beatmap is required to be an actual beatmap so the scores can get their scores correctly
|
||||
// calculated for standardised scoring, else the tests that rely on ordering will fall over.
|
||||
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowWithUserScore()
|
||||
|
@ -390,7 +390,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
|
||||
private class RulesetWithNoPerformanceCalculator : OsuRuleset
|
||||
{
|
||||
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null;
|
||||
public override PerformanceCalculator CreatePerformanceCalculator() => null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,97 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestSceneEditorSidebar : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
[Test]
|
||||
public void TestSidebars()
|
||||
{
|
||||
AddStep("Add sidebars", () =>
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new EditorSidebar
|
||||
{
|
||||
Children = new[]
|
||||
{
|
||||
new EditorSidebarSection("Section 1")
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Full,
|
||||
Spacing = new Vector2(3),
|
||||
ChildrenEnumerable = Enumerable.Range(0, 10).Select(_ => new Box
|
||||
{
|
||||
Colour = Color4.White,
|
||||
Size = new Vector2(32),
|
||||
})
|
||||
},
|
||||
},
|
||||
new EditorSidebarSection("Section with a really long section header")
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Full,
|
||||
Spacing = new Vector2(3),
|
||||
ChildrenEnumerable = Enumerable.Range(0, 400).Select(_ => new Box
|
||||
{
|
||||
Colour = Color4.Gray,
|
||||
Size = new Vector2(32),
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new EditorSidebar
|
||||
{
|
||||
Children = new[]
|
||||
{
|
||||
new EditorSidebarSection("Section 1"),
|
||||
new EditorSidebarSection("Section 2"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using JetBrains.Annotations;
|
||||
@ -170,6 +171,39 @@ namespace osu.Game.Configuration
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, (SettingSourceAttribute, PropertyInfo)[]> property_info_cache = new ConcurrentDictionary<Type, (SettingSourceAttribute, PropertyInfo)[]>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the underlying value of the given mod setting object.
|
||||
/// Can be used for serialization and equality comparison purposes.
|
||||
/// </summary>
|
||||
/// <param name="setting">A <see cref="SettingSourceAttribute"/> bindable.</param>
|
||||
public static object GetUnderlyingSettingValue(this object setting)
|
||||
{
|
||||
switch (setting)
|
||||
{
|
||||
case Bindable<double> d:
|
||||
return d.Value;
|
||||
|
||||
case Bindable<int> i:
|
||||
return i.Value;
|
||||
|
||||
case Bindable<float> f:
|
||||
return f.Value;
|
||||
|
||||
case Bindable<bool> b:
|
||||
return b.Value;
|
||||
|
||||
case IBindable u:
|
||||
// An unknown (e.g. enum) generic type.
|
||||
var valueMethod = u.GetType().GetProperty(nameof(IBindable<int>.Value));
|
||||
Debug.Assert(valueMethod != null);
|
||||
return valueMethod.GetValue(u);
|
||||
|
||||
default:
|
||||
// fall back for non-bindable cases.
|
||||
return setting;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
|
@ -1,8 +1,11 @@
|
||||
// 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 Humanizer;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
@ -59,8 +62,18 @@ namespace osu.Game.Extensions
|
||||
component.Origin = info.Origin;
|
||||
|
||||
if (component is ISkinnableDrawable skinnable)
|
||||
{
|
||||
skinnable.UsesFixedAnchor = info.UsesFixedAnchor;
|
||||
|
||||
foreach (var (_, property) in component.GetSettingsSourceProperties())
|
||||
{
|
||||
if (!info.Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
|
||||
continue;
|
||||
|
||||
skinnable.CopyAdjustedSetting((IBindable)property.GetValue(component), settingValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (component is Container container)
|
||||
{
|
||||
foreach (var child in info.Children)
|
||||
|
@ -12,7 +12,6 @@ using osu.Framework.Logging;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
@ -43,7 +42,7 @@ namespace osu.Game.Online.API
|
||||
var bindable = (IBindable)property.GetValue(mod);
|
||||
|
||||
if (!bindable.IsDefault)
|
||||
Settings.Add(property.Name.Underscore(), ModUtils.GetSettingUnderlyingValue(bindable));
|
||||
Settings.Add(property.Name.Underscore(), bindable.GetUnderlyingSettingValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,13 +92,13 @@ namespace osu.Game.Online.API
|
||||
|
||||
public bool Equals(KeyValuePair<string, object> x, KeyValuePair<string, object> y)
|
||||
{
|
||||
object xValue = ModUtils.GetSettingUnderlyingValue(x.Value);
|
||||
object yValue = ModUtils.GetSettingUnderlyingValue(y.Value);
|
||||
object xValue = x.Value.GetUnderlyingSettingValue();
|
||||
object yValue = y.Value.GetUnderlyingSettingValue();
|
||||
|
||||
return x.Key == y.Key && EqualityComparer<object>.Default.Equals(xValue, yValue);
|
||||
}
|
||||
|
||||
public int GetHashCode(KeyValuePair<string, object> obj) => HashCode.Combine(obj.Key, ModUtils.GetSettingUnderlyingValue(obj.Value));
|
||||
public int GetHashCode(KeyValuePair<string, object> obj) => HashCode.Combine(obj.Key, obj.Value.GetUnderlyingSettingValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using MessagePack;
|
||||
using MessagePack.Formatters;
|
||||
using osu.Game.Utils;
|
||||
using osu.Game.Configuration;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
@ -23,7 +23,7 @@ namespace osu.Game.Online.API
|
||||
var stringBytes = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(kvp.Key));
|
||||
writer.WriteString(in stringBytes);
|
||||
|
||||
primitiveFormatter.Serialize(ref writer, ModUtils.GetSettingUnderlyingValue(kvp.Value), options);
|
||||
primitiveFormatter.Serialize(ref writer, kvp.Value.GetUnderlyingSettingValue(), options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1184,7 +1184,7 @@ namespace osu.Game
|
||||
BackButton.Hide();
|
||||
}
|
||||
|
||||
skinEditor.SetTarget((Screen)newScreen);
|
||||
skinEditor.SetTarget((OsuScreen)newScreen);
|
||||
}
|
||||
|
||||
private void screenPushed(IScreen lastScreen, IScreen newScreen) => screenChanged(lastScreen, newScreen);
|
||||
|
@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Chat
|
||||
if (highlightedMessage.Value == null)
|
||||
return;
|
||||
|
||||
var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(highlightedMessage.Value));
|
||||
var chatLine = chatLines.FirstOrDefault(c => c.Message.Equals(highlightedMessage.Value));
|
||||
if (chatLine == null)
|
||||
return;
|
||||
|
||||
|
@ -73,6 +73,10 @@ namespace osu.Game.Overlays
|
||||
private Container channelSelectionContainer;
|
||||
protected ChannelSelectionOverlay ChannelSelectionOverlay;
|
||||
|
||||
private readonly IBindableList<Channel> availableChannels = new BindableList<Channel>();
|
||||
private readonly IBindableList<Channel> joinedChannels = new BindableList<Channel>();
|
||||
private readonly Bindable<Channel> currentChannel = new Bindable<Channel>();
|
||||
|
||||
public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos)
|
||||
|| (ChannelSelectionOverlay.State.Value == Visibility.Visible && ChannelSelectionOverlay.ReceivePositionalInputAt(screenSpacePos));
|
||||
|
||||
@ -198,9 +202,13 @@ namespace osu.Game.Overlays
|
||||
},
|
||||
};
|
||||
|
||||
availableChannels.BindTo(channelManager.AvailableChannels);
|
||||
joinedChannels.BindTo(channelManager.JoinedChannels);
|
||||
currentChannel.BindTo(channelManager.CurrentChannel);
|
||||
|
||||
textBox.OnCommit += postMessage;
|
||||
|
||||
ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue;
|
||||
ChannelTabControl.Current.ValueChanged += current => currentChannel.Value = current.NewValue;
|
||||
ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden;
|
||||
ChannelSelectionOverlay.State.ValueChanged += state =>
|
||||
{
|
||||
@ -238,18 +246,12 @@ namespace osu.Game.Overlays
|
||||
Schedule(() =>
|
||||
{
|
||||
// TODO: consider scheduling bindable callbacks to not perform when overlay is not present.
|
||||
channelManager.JoinedChannels.BindCollectionChanged(joinedChannelsChanged, true);
|
||||
|
||||
channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged;
|
||||
availableChannelsChanged(null, null);
|
||||
|
||||
currentChannel = channelManager.CurrentChannel.GetBoundCopy();
|
||||
joinedChannels.BindCollectionChanged(joinedChannelsChanged, true);
|
||||
availableChannels.BindCollectionChanged(availableChannelsChanged, true);
|
||||
currentChannel.BindValueChanged(currentChannelChanged, true);
|
||||
});
|
||||
}
|
||||
|
||||
private Bindable<Channel> currentChannel;
|
||||
|
||||
private void currentChannelChanged(ValueChangedEvent<Channel> e)
|
||||
{
|
||||
if (e.NewValue == null)
|
||||
@ -318,7 +320,7 @@ namespace osu.Game.Overlays
|
||||
if (!channel.Joined.Value)
|
||||
channel = channelManager.JoinChannel(channel);
|
||||
|
||||
channelManager.CurrentChannel.Value = channel;
|
||||
currentChannel.Value = channel;
|
||||
}
|
||||
|
||||
channel.HighlightedMessage.Value = message;
|
||||
@ -407,7 +409,7 @@ namespace osu.Game.Overlays
|
||||
return true;
|
||||
|
||||
case PlatformAction.DocumentClose:
|
||||
channelManager.LeaveChannel(channelManager.CurrentChannel.Value);
|
||||
channelManager.LeaveChannel(currentChannel.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -487,19 +489,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args)
|
||||
{
|
||||
ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (channelManager != null)
|
||||
{
|
||||
channelManager.CurrentChannel.ValueChanged -= currentChannelChanged;
|
||||
channelManager.JoinedChannels.CollectionChanged -= joinedChannelsChanged;
|
||||
channelManager.AvailableChannels.CollectionChanged -= availableChannelsChanged;
|
||||
}
|
||||
ChannelSelectionOverlay.UpdateAvailableChannels(availableChannels);
|
||||
}
|
||||
|
||||
private void postMessage(TextBox textBox, bool newText)
|
||||
|
@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
).ConfigureAwait(false);
|
||||
|
||||
// ScorePerformanceCache is not used to avoid caching multiple copies of essentially identical perfect performance attributes
|
||||
return difficulty == null ? null : ruleset.CreatePerformanceCalculator(difficulty.Value.Attributes, perfectPlay)?.Calculate();
|
||||
return difficulty == null ? null : ruleset.CreatePerformanceCalculator()?.Calculate(perfectPlay, difficulty.Value.Attributes);
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
|
@ -1,41 +1,31 @@
|
||||
// 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 System.Linq;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Difficulty
|
||||
{
|
||||
public abstract class PerformanceCalculator
|
||||
{
|
||||
protected readonly DifficultyAttributes Attributes;
|
||||
|
||||
protected readonly Ruleset Ruleset;
|
||||
protected readonly ScoreInfo Score;
|
||||
|
||||
protected double TimeRate { get; private set; } = 1;
|
||||
|
||||
protected PerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
|
||||
protected PerformanceCalculator(Ruleset ruleset)
|
||||
{
|
||||
Ruleset = ruleset;
|
||||
Score = score;
|
||||
|
||||
Attributes = attributes ?? throw new ArgumentNullException(nameof(attributes));
|
||||
|
||||
ApplyMods(score.Mods);
|
||||
}
|
||||
|
||||
protected virtual void ApplyMods(Mod[] mods)
|
||||
{
|
||||
var track = new TrackVirtual(10000);
|
||||
mods.OfType<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
|
||||
TimeRate = track.Rate;
|
||||
}
|
||||
public PerformanceAttributes Calculate(ScoreInfo score, DifficultyAttributes attributes)
|
||||
=> CreatePerformanceAttributes(score, attributes);
|
||||
|
||||
public abstract PerformanceAttributes Calculate();
|
||||
public PerformanceAttributes Calculate(ScoreInfo score, IWorkingBeatmap beatmap)
|
||||
=> Calculate(score, Ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods));
|
||||
|
||||
/// <summary>
|
||||
/// Creates <see cref="PerformanceAttributes"/> to describe a score's performance.
|
||||
/// </summary>
|
||||
/// <param name="score">The score to create the attributes for.</param>
|
||||
/// <param name="attributes">The difficulty attributes for the beatmap relating to the score.</param>
|
||||
protected abstract PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
protected readonly OsuScrollContainer Scroll;
|
||||
|
||||
protected readonly FillFlowContainer FillFlow;
|
||||
|
||||
protected override Container<Drawable> Content { get; }
|
||||
|
||||
public ScrollingToolboxGroup(string title, float scrollAreaHeight)
|
||||
@ -20,7 +22,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = scrollAreaHeight,
|
||||
Child = Content = new FillFlowContainer
|
||||
Child = Content = FillFlow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
|
@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
hashCode.Add(GetType());
|
||||
|
||||
foreach (var setting in Settings)
|
||||
hashCode.Add(ModUtils.GetSettingUnderlyingValue(setting));
|
||||
hashCode.Add(setting.GetUnderlyingSettingValue());
|
||||
|
||||
return hashCode.ToHashCode();
|
||||
}
|
||||
@ -208,13 +208,13 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public bool Equals(IBindable x, IBindable y)
|
||||
{
|
||||
object xValue = x == null ? null : ModUtils.GetSettingUnderlyingValue(x);
|
||||
object yValue = y == null ? null : ModUtils.GetSettingUnderlyingValue(y);
|
||||
object xValue = x?.GetUnderlyingSettingValue();
|
||||
object yValue = y?.GetUnderlyingSettingValue();
|
||||
|
||||
return EqualityComparer<object>.Default.Equals(xValue, yValue);
|
||||
}
|
||||
|
||||
public int GetHashCode(IBindable obj) => ModUtils.GetSettingUnderlyingValue(obj).GetHashCode();
|
||||
public int GetHashCode(IBindable obj) => obj.GetUnderlyingSettingValue().GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ namespace osu.Game.Rulesets
|
||||
/// Creates a <see cref="ScoreProcessor"/> for this <see cref="Ruleset"/>.
|
||||
/// </summary>
|
||||
/// <returns>The score processor.</returns>
|
||||
public virtual ScoreProcessor CreateScoreProcessor() => new ScoreProcessor();
|
||||
public virtual ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthProcessor"/> for this <see cref="Ruleset"/>.
|
||||
@ -228,25 +228,9 @@ namespace osu.Game.Rulesets
|
||||
/// <summary>
|
||||
/// Optionally creates a <see cref="PerformanceCalculator"/> to generate performance data from the provided score.
|
||||
/// </summary>
|
||||
/// <param name="attributes">Difficulty attributes for the beatmap related to the provided score.</param>
|
||||
/// <param name="score">The score to be processed.</param>
|
||||
/// <returns>A performance calculator instance for the provided score.</returns>
|
||||
[CanBeNull]
|
||||
public virtual PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null;
|
||||
|
||||
/// <summary>
|
||||
/// Optionally creates a <see cref="PerformanceCalculator"/> to generate performance data from the provided score.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap to use as a source for generating <see cref="DifficultyAttributes"/>.</param>
|
||||
/// <param name="score">The score to be processed.</param>
|
||||
/// <returns>A performance calculator instance for the provided score.</returns>
|
||||
[CanBeNull]
|
||||
public PerformanceCalculator CreatePerformanceCalculator(IWorkingBeatmap beatmap, ScoreInfo score)
|
||||
{
|
||||
var difficultyCalculator = CreateDifficultyCalculator(beatmap);
|
||||
var difficultyAttributes = difficultyCalculator.Calculate(score.Mods);
|
||||
return CreatePerformanceCalculator(difficultyAttributes, score);
|
||||
}
|
||||
public virtual PerformanceCalculator CreatePerformanceCalculator() => null;
|
||||
|
||||
public virtual HitObjectComposer CreateHitObjectComposer() => null;
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Utils;
|
||||
@ -14,12 +16,12 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// Invoked when the <see cref="ScoreProcessor"/> is in a failed state.
|
||||
/// Return true if the fail was permitted.
|
||||
/// </summary>
|
||||
public event Func<bool> Failed;
|
||||
public event Func<bool>? Failed;
|
||||
|
||||
/// <summary>
|
||||
/// Additional conditions on top of <see cref="DefaultFailCondition"/> that cause a failing state.
|
||||
/// </summary>
|
||||
public event Func<HealthProcessor, JudgementResult, bool> FailConditions;
|
||||
public event Func<HealthProcessor, JudgementResult, bool>? FailConditions;
|
||||
|
||||
/// <summary>
|
||||
/// The current health.
|
||||
|
@ -122,7 +122,19 @@ namespace osu.Game.Rulesets.Scoring
|
||||
public static class HitResultExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether a <see cref="HitResult"/> increases/decreases the combo, and affects the combo portion of the score.
|
||||
/// Whether a <see cref="HitResult"/> increases the combo.
|
||||
/// </summary>
|
||||
public static bool IncreasesCombo(this HitResult result)
|
||||
=> AffectsCombo(result) && IsHit(result);
|
||||
|
||||
/// <summary>
|
||||
/// Whether a <see cref="HitResult"/> breaks the combo and resets it back to zero.
|
||||
/// </summary>
|
||||
public static bool BreaksCombo(this HitResult result)
|
||||
=> AffectsCombo(result) && !IsHit(result);
|
||||
|
||||
/// <summary>
|
||||
/// Whether a <see cref="HitResult"/> increases/breaks the combo, and affects the combo portion of the score.
|
||||
/// </summary>
|
||||
public static bool AffectsCombo(this HitResult result)
|
||||
{
|
||||
|
@ -1,8 +1,11 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -17,12 +20,12 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// <summary>
|
||||
/// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by this <see cref="JudgementProcessor"/>.
|
||||
/// </summary>
|
||||
public event Action<JudgementResult> NewJudgement;
|
||||
public event Action<JudgementResult>? NewJudgement;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a judgement is reverted, usually due to rewinding gameplay.
|
||||
/// </summary>
|
||||
public event Action<JudgementResult> JudgementReverted;
|
||||
public event Action<JudgementResult>? JudgementReverted;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of hits that can be judged.
|
||||
@ -34,7 +37,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
public int JudgedHits { get; private set; }
|
||||
|
||||
private JudgementResult lastAppliedResult;
|
||||
private JudgementResult? lastAppliedResult;
|
||||
|
||||
private readonly BindableBool hasCompleted = new BindableBool();
|
||||
|
||||
@ -163,7 +166,12 @@ namespace osu.Game.Rulesets.Scoring
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
hasCompleted.Value = JudgedHits == MaxHits && (JudgedHits == 0 || lastAppliedResult.TimeAbsolute < Clock.CurrentTime);
|
||||
|
||||
hasCompleted.Value =
|
||||
JudgedHits == MaxHits
|
||||
&& (JudgedHits == 0
|
||||
// Last applied result is guaranteed to be non-null when JudgedHits > 0.
|
||||
|| lastAppliedResult.AsNonNull().TimeAbsolute < Clock.CurrentTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@ -24,7 +26,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// <summary>
|
||||
/// Invoked when this <see cref="ScoreProcessor"/> was reset from a replay frame.
|
||||
/// </summary>
|
||||
public event Action OnResetFromReplayFrame;
|
||||
public event Action? OnResetFromReplayFrame;
|
||||
|
||||
/// <summary>
|
||||
/// The current total score.
|
||||
@ -82,6 +84,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
protected virtual double ClassicScoreMultiplier => 36;
|
||||
|
||||
private readonly Ruleset ruleset;
|
||||
private readonly double accuracyPortion;
|
||||
private readonly double comboPortion;
|
||||
|
||||
@ -110,12 +113,14 @@ namespace osu.Game.Rulesets.Scoring
|
||||
|
||||
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
|
||||
private readonly List<HitEvent> hitEvents = new List<HitEvent>();
|
||||
private HitObject lastHitObject;
|
||||
private HitObject? lastHitObject;
|
||||
|
||||
private double scoreMultiplier = 1;
|
||||
|
||||
public ScoreProcessor()
|
||||
public ScoreProcessor(Ruleset ruleset)
|
||||
{
|
||||
this.ruleset = ruleset;
|
||||
|
||||
accuracyPortion = DefaultAccuracyPortion;
|
||||
comboPortion = DefaultComboPortion;
|
||||
|
||||
@ -161,20 +166,10 @@ namespace osu.Game.Rulesets.Scoring
|
||||
if (!result.Type.IsScorable())
|
||||
return;
|
||||
|
||||
if (result.Type.AffectsCombo())
|
||||
{
|
||||
switch (result.Type)
|
||||
{
|
||||
case HitResult.Miss:
|
||||
case HitResult.LargeTickMiss:
|
||||
Combo.Value = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
Combo.Value++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result.Type.IncreasesCombo())
|
||||
Combo.Value++;
|
||||
else if (result.Type.BreaksCombo())
|
||||
Combo.Value = 0;
|
||||
|
||||
double scoreIncrease = result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0;
|
||||
|
||||
@ -253,7 +248,10 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// <returns>The total score in the given <see cref="ScoringMode"/>.</returns>
|
||||
public double ComputeFinalScore(ScoringMode mode, ScoreInfo scoreInfo)
|
||||
{
|
||||
extractFromStatistics(scoreInfo.Ruleset.CreateInstance(),
|
||||
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
|
||||
throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
|
||||
|
||||
extractFromStatistics(ruleset,
|
||||
scoreInfo.Statistics,
|
||||
out double extractedBaseScore,
|
||||
out double extractedMaxBaseScore,
|
||||
@ -277,10 +275,13 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// <returns>The total score in the given <see cref="ScoringMode"/>.</returns>
|
||||
public double ComputePartialScore(ScoringMode mode, ScoreInfo scoreInfo)
|
||||
{
|
||||
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
|
||||
throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
|
||||
|
||||
if (!beatmapApplied)
|
||||
throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}.");
|
||||
|
||||
extractFromStatistics(scoreInfo.Ruleset.CreateInstance(),
|
||||
extractFromStatistics(ruleset,
|
||||
scoreInfo.Statistics,
|
||||
out double extractedBaseScore,
|
||||
out _,
|
||||
@ -306,6 +307,9 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// <returns>The total score in the given <see cref="ScoringMode"/>.</returns>
|
||||
public double ComputeFinalLegacyScore(ScoringMode mode, ScoreInfo scoreInfo, int maxAchievableCombo)
|
||||
{
|
||||
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
|
||||
throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
|
||||
|
||||
double accuracyRatio = scoreInfo.Accuracy;
|
||||
double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1;
|
||||
|
||||
@ -315,7 +319,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3)
|
||||
{
|
||||
extractFromStatistics(
|
||||
scoreInfo.Ruleset.CreateInstance(),
|
||||
ruleset,
|
||||
scoreInfo.Statistics,
|
||||
out double computedBaseScore,
|
||||
out double computedMaxBaseScore,
|
||||
|
@ -43,9 +43,7 @@ namespace osu.Game.Scoring
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Value.Attributes, score);
|
||||
|
||||
return calculator?.Calculate();
|
||||
return score.Ruleset.CreateInstance().CreatePerformanceCalculator()?.Calculate(score, attributes.Value.Attributes);
|
||||
}
|
||||
|
||||
public readonly struct PerformanceCacheLookup
|
||||
|
55
osu.Game/Screens/Edit/Components/EditorSidebar.cs
Normal file
55
osu.Game/Screens/Edit/Components/EditorSidebar.cs
Normal file
@ -0,0 +1,55 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A sidebar area that can be attached to the left or right edge of the screen.
|
||||
/// Houses scrolling sectionised content.
|
||||
/// </summary>
|
||||
internal class EditorSidebar : Container<EditorSidebarSection>
|
||||
{
|
||||
private readonly Box background;
|
||||
|
||||
protected override Container<EditorSidebarSection> Content { get; }
|
||||
|
||||
public EditorSidebar()
|
||||
{
|
||||
Width = 250;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
Padding = new MarginPadding { Left = 20 },
|
||||
ScrollbarOverlapsContent = false,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = Content = new FillFlowContainer<EditorSidebarSection>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
background.Colour = colourProvider.Background5;
|
||||
}
|
||||
}
|
||||
}
|
83
osu.Game/Screens/Edit/Components/EditorSidebarSection.cs
Normal file
83
osu.Game/Screens/Edit/Components/EditorSidebarSection.cs
Normal file
@ -0,0 +1,83 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Components
|
||||
{
|
||||
public class EditorSidebarSection : Container
|
||||
{
|
||||
protected override Container<Drawable> Content { get; }
|
||||
|
||||
public EditorSidebarSection(LocalisableString sectionName)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SectionHeader(sectionName),
|
||||
Content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public class SectionHeader : CompositeDrawable
|
||||
{
|
||||
private readonly LocalisableString text;
|
||||
|
||||
public SectionHeader(LocalisableString text)
|
||||
{
|
||||
this.text = text;
|
||||
|
||||
Margin = new MarginPadding { Vertical = 10, Horizontal = 5 };
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(2),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold))
|
||||
{
|
||||
Text = text,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Colour = colourProvider.Highlight1,
|
||||
Size = new Vector2(28, 2),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -334,6 +334,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
// ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not.
|
||||
r.RoomID.Value = null;
|
||||
|
||||
// Null out dates because end date is not supported client-side and the settings overlay will populate a duration.
|
||||
r.EndDate.Value = null;
|
||||
r.Duration.Value = null;
|
||||
|
||||
Open(r);
|
||||
|
||||
joiningRoomOperation?.Dispose();
|
||||
|
@ -1,7 +1,6 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
@ -12,19 +11,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
private const float ready_button_width = 600;
|
||||
private const float spectate_button_width = 200;
|
||||
|
||||
public Action OnReadyClick
|
||||
{
|
||||
set => readyButton.OnReadyClick = value;
|
||||
}
|
||||
|
||||
public Action OnSpectateClick
|
||||
{
|
||||
set => spectateButton.OnSpectateClick = value;
|
||||
}
|
||||
|
||||
private readonly MultiplayerReadyButton readyButton;
|
||||
private readonly MultiplayerSpectateButton spectateButton;
|
||||
|
||||
public MultiplayerMatchFooter()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
@ -37,12 +23,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
new Drawable[]
|
||||
{
|
||||
null,
|
||||
spectateButton = new MultiplayerSpectateButton
|
||||
new MultiplayerSpectateButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
null,
|
||||
readyButton = new MultiplayerReadyButton
|
||||
new MultiplayerReadyButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
|
@ -2,7 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
@ -19,28 +21,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
public class MultiplayerReadyButton : MultiplayerRoomComposite
|
||||
{
|
||||
public Action OnReadyClick
|
||||
{
|
||||
set => button.Action = value;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||
|
||||
private IBindable<bool> operationInProgress;
|
||||
[CanBeNull]
|
||||
private IDisposable clickOperation;
|
||||
|
||||
private Sample sampleReady;
|
||||
private Sample sampleReadyAll;
|
||||
private Sample sampleUnready;
|
||||
|
||||
private readonly ButtonWithTrianglesExposed button;
|
||||
|
||||
private int countReady;
|
||||
|
||||
private ScheduledDelegate readySampleDelegate;
|
||||
private IBindable<bool> operationInProgress;
|
||||
|
||||
public MultiplayerReadyButton()
|
||||
{
|
||||
@ -48,6 +45,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = Vector2.One,
|
||||
Action = onReadyClick,
|
||||
Enabled = { Value = true },
|
||||
};
|
||||
}
|
||||
@ -73,10 +71,56 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
protected override void OnRoomUpdated()
|
||||
{
|
||||
base.OnRoomUpdated();
|
||||
|
||||
updateState();
|
||||
}
|
||||
|
||||
protected override void OnRoomLoadRequested()
|
||||
{
|
||||
base.OnRoomLoadRequested();
|
||||
endOperation();
|
||||
}
|
||||
|
||||
private void onReadyClick()
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(clickOperation == null);
|
||||
clickOperation = ongoingOperationTracker.BeginOperation();
|
||||
|
||||
// Ensure the current user becomes ready before being able to do anything else (start match, stop countdown, unready).
|
||||
if (!isReady() || !Client.IsHost)
|
||||
{
|
||||
toggleReady();
|
||||
return;
|
||||
}
|
||||
|
||||
// And if a countdown isn't running, start the match.
|
||||
startMatch();
|
||||
|
||||
bool isReady() => Client.LocalUser?.State == MultiplayerUserState.Ready || Client.LocalUser?.State == MultiplayerUserState.Spectating;
|
||||
|
||||
void toggleReady() => Client.ToggleReady().ContinueWith(_ => endOperation());
|
||||
|
||||
void startMatch() => Client.StartMatch().ContinueWith(t =>
|
||||
{
|
||||
// accessing Exception here silences any potential errors from the antecedent task
|
||||
if (t.Exception != null)
|
||||
{
|
||||
// gameplay was not started due to an exception; unblock button.
|
||||
endOperation();
|
||||
}
|
||||
|
||||
// gameplay is starting, the button will be unblocked on load requested.
|
||||
});
|
||||
}
|
||||
|
||||
private void endOperation()
|
||||
{
|
||||
clickOperation?.Dispose();
|
||||
clickOperation = null;
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
var localUser = Client.LocalUser;
|
||||
|
@ -1,7 +1,6 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -15,11 +14,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
public class MultiplayerSpectateButton : MultiplayerRoomComposite
|
||||
{
|
||||
public Action OnSpectateClick
|
||||
{
|
||||
set => button.Action = value;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||
|
||||
@ -37,9 +31,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = Vector2.One,
|
||||
Enabled = { Value = true },
|
||||
Action = onClick
|
||||
};
|
||||
}
|
||||
|
||||
private void onClick()
|
||||
{
|
||||
var clickOperation = ongoingOperationTracker.BeginOperation();
|
||||
|
||||
Client.ToggleSpectate().ContinueWith(t => endOperation());
|
||||
|
||||
void endOperation() => clickOperation?.Dispose();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
|
||||
@ -25,6 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
/// </summary>
|
||||
public Action<PlaylistItem> RequestEdit;
|
||||
|
||||
private MultiplayerPlaylistTabControl playlistTabControl;
|
||||
private MultiplayerQueueList queueList;
|
||||
private MultiplayerHistoryList historyList;
|
||||
private bool firstPopulation = true;
|
||||
@ -36,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new OsuTabControl<MultiplayerPlaylistDisplayMode>
|
||||
playlistTabControl = new MultiplayerPlaylistTabControl
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = tab_control_height,
|
||||
@ -64,6 +64,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
playlistTabControl.QueueItems.BindTarget = queueList.Items;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -0,0 +1,39 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Rooms;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
{
|
||||
public class MultiplayerPlaylistTabControl : OsuTabControl<MultiplayerPlaylistDisplayMode>
|
||||
{
|
||||
public readonly IBindableList<PlaylistItem> QueueItems = new BindableList<PlaylistItem>();
|
||||
|
||||
protected override TabItem<MultiplayerPlaylistDisplayMode> CreateTabItem(MultiplayerPlaylistDisplayMode value)
|
||||
{
|
||||
if (value == MultiplayerPlaylistDisplayMode.Queue)
|
||||
return new QueueTabItem { QueueItems = { BindTarget = QueueItems } };
|
||||
|
||||
return base.CreateTabItem(value);
|
||||
}
|
||||
|
||||
private class QueueTabItem : OsuTabItem
|
||||
{
|
||||
public readonly IBindableList<PlaylistItem> QueueItems = new BindableList<PlaylistItem>();
|
||||
|
||||
public QueueTabItem()
|
||||
: base(MultiplayerPlaylistDisplayMode.Queue)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
QueueItems.BindCollectionChanged((_, __) => Text.Text = QueueItems.Count > 0 ? $"Queue ({QueueItems.Count})" : "Queue", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,9 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -46,14 +44,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||
|
||||
private readonly IBindable<bool> isConnected = new Bindable<bool>();
|
||||
|
||||
[CanBeNull]
|
||||
private IDisposable readyClickOperation;
|
||||
|
||||
private AddItemButton addItemButton;
|
||||
|
||||
public MultiplayerMatchSubScreen(Room room)
|
||||
@ -230,11 +222,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit));
|
||||
}
|
||||
|
||||
protected override Drawable CreateFooter() => new MultiplayerMatchFooter
|
||||
{
|
||||
OnReadyClick = onReadyClick,
|
||||
OnSpectateClick = onSpectateClick
|
||||
};
|
||||
protected override Drawable CreateFooter() => new MultiplayerMatchFooter();
|
||||
|
||||
protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new MultiplayerMatchSettingsOverlay(room);
|
||||
|
||||
@ -332,52 +320,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
}
|
||||
}
|
||||
|
||||
private void onReadyClick()
|
||||
{
|
||||
Debug.Assert(readyClickOperation == null);
|
||||
readyClickOperation = ongoingOperationTracker.BeginOperation();
|
||||
|
||||
if (client.IsHost && (client.LocalUser?.State == MultiplayerUserState.Ready || client.LocalUser?.State == MultiplayerUserState.Spectating))
|
||||
{
|
||||
client.StartMatch()
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
// accessing Exception here silences any potential errors from the antecedent task
|
||||
if (t.Exception != null)
|
||||
{
|
||||
// gameplay was not started due to an exception; unblock button.
|
||||
endOperation();
|
||||
}
|
||||
|
||||
// gameplay is starting, the button will be unblocked on load requested.
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
client.ToggleReady()
|
||||
.ContinueWith(t => endOperation());
|
||||
|
||||
void endOperation()
|
||||
{
|
||||
readyClickOperation?.Dispose();
|
||||
readyClickOperation = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void onSpectateClick()
|
||||
{
|
||||
Debug.Assert(readyClickOperation == null);
|
||||
readyClickOperation = ongoingOperationTracker.BeginOperation();
|
||||
|
||||
client.ToggleSpectate().ContinueWith(t => endOperation());
|
||||
|
||||
void endOperation()
|
||||
{
|
||||
readyClickOperation?.Dispose();
|
||||
readyClickOperation = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void onRoomUpdated()
|
||||
{
|
||||
// may happen if the client is kicked or otherwise removed from the room.
|
||||
@ -433,9 +375,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
return;
|
||||
|
||||
StartPlay();
|
||||
|
||||
readyClickOperation?.Dispose();
|
||||
readyClickOperation = null;
|
||||
}
|
||||
|
||||
protected override Screen CreateGameplayScreen()
|
||||
|
@ -21,6 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
base.LoadComplete();
|
||||
|
||||
Client.RoomUpdated += invokeOnRoomUpdated;
|
||||
Client.LoadRequested += invokeOnRoomLoadRequested;
|
||||
Client.UserLeft += invokeUserLeft;
|
||||
Client.UserKicked += invokeUserKicked;
|
||||
Client.UserJoined += invokeUserJoined;
|
||||
@ -38,6 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
private void invokeItemAdded(MultiplayerPlaylistItem item) => Schedule(() => PlaylistItemAdded(item));
|
||||
private void invokeItemRemoved(long item) => Schedule(() => PlaylistItemRemoved(item));
|
||||
private void invokeItemChanged(MultiplayerPlaylistItem item) => Schedule(() => PlaylistItemChanged(item));
|
||||
private void invokeOnRoomLoadRequested() => Scheduler.AddOnce(OnRoomLoadRequested);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a user has joined the room.
|
||||
@ -94,6 +96,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the room requests the local user to load into gameplay.
|
||||
/// </summary>
|
||||
protected virtual void OnRoomLoadRequested()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (Client != null)
|
||||
|
@ -4,12 +4,16 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
@ -19,11 +23,27 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
public class BarHitErrorMeter : HitErrorMeter
|
||||
{
|
||||
private const int judgement_line_width = 14;
|
||||
private const int judgement_line_height = 4;
|
||||
|
||||
[SettingSource("Judgement line thickness", "How thick the individual lines should be.")]
|
||||
public BindableNumber<float> JudgementLineThickness { get; } = new BindableNumber<float>(4)
|
||||
{
|
||||
MinValue = 1,
|
||||
MaxValue = 8,
|
||||
Precision = 0.1f,
|
||||
};
|
||||
|
||||
[SettingSource("Show moving average arrow", "Whether an arrow should move beneath the bar showing the average error.")]
|
||||
public Bindable<bool> ShowMovingAverage { get; } = new BindableBool(true);
|
||||
|
||||
[SettingSource("Centre marker style", "How to signify the centre of the display")]
|
||||
public Bindable<CentreMarkerStyles> CentreMarkerStyle { get; } = new Bindable<CentreMarkerStyles>(CentreMarkerStyles.Circle);
|
||||
|
||||
[SettingSource("Label style", "How to show early/late extremities")]
|
||||
public Bindable<LabelStyles> LabelStyle { get; } = new Bindable<LabelStyles>(LabelStyles.Icons);
|
||||
|
||||
private SpriteIcon arrow;
|
||||
private SpriteIcon iconEarly;
|
||||
private SpriteIcon iconLate;
|
||||
private Drawable labelEarly;
|
||||
private Drawable labelLate;
|
||||
|
||||
private Container colourBarsEarly;
|
||||
private Container colourBarsLate;
|
||||
@ -32,6 +52,18 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
|
||||
private double maxHitWindow;
|
||||
|
||||
private double floatingAverage;
|
||||
private Container colourBars;
|
||||
private Container arrowContainer;
|
||||
|
||||
private (HitResult result, double length)[] hitWindows;
|
||||
|
||||
private const int max_concurrent_judgements = 50;
|
||||
|
||||
private Drawable[] centreMarkerDrawables;
|
||||
|
||||
private const int centre_marker_size = 8;
|
||||
|
||||
public BarHitErrorMeter()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
@ -40,13 +72,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
const int centre_marker_size = 8;
|
||||
const int bar_height = 200;
|
||||
const int bar_width = 2;
|
||||
const float chevron_size = 8;
|
||||
const float icon_size = 14;
|
||||
|
||||
var hitWindows = HitWindows.GetAllAvailableWindows().ToArray();
|
||||
hitWindows = HitWindows.GetAllAvailableWindows().ToArray();
|
||||
|
||||
InternalChild = new Container
|
||||
{
|
||||
@ -65,22 +95,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
iconEarly = new SpriteIcon
|
||||
{
|
||||
Y = -10,
|
||||
Size = new Vector2(icon_size),
|
||||
Icon = FontAwesome.Solid.ShippingFast,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
iconLate = new SpriteIcon
|
||||
{
|
||||
Y = 10,
|
||||
Size = new Vector2(icon_size),
|
||||
Icon = FontAwesome.Solid.Bicycle,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
colourBarsEarly = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
@ -98,14 +112,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Height = 0.5f,
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Name = "middle marker behind",
|
||||
Colour = GetColourForHitResult(hitWindows.Last().result),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(centre_marker_size),
|
||||
},
|
||||
judgementsContainer = new Container
|
||||
{
|
||||
Name = "judgements",
|
||||
@ -114,24 +120,18 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = judgement_line_width,
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Name = "middle marker in front",
|
||||
Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(centre_marker_size),
|
||||
Scale = new Vector2(0.5f),
|
||||
},
|
||||
}
|
||||
},
|
||||
new Container
|
||||
arrowContainer = new Container
|
||||
{
|
||||
Name = "average chevron",
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreRight,
|
||||
Width = chevron_size,
|
||||
X = chevron_size,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Scale = new Vector2(0, 1),
|
||||
Child = arrow = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
@ -155,8 +155,180 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
colourBars.Height = 0;
|
||||
colourBars.ResizeHeightTo(1, 800, Easing.OutQuint);
|
||||
|
||||
arrow.Alpha = 0;
|
||||
arrow.Delay(200).FadeInFromZero(600);
|
||||
CentreMarkerStyle.BindValueChanged(style => recreateCentreMarker(style.NewValue), true);
|
||||
LabelStyle.BindValueChanged(style => recreateLabels(style.NewValue), true);
|
||||
|
||||
// delay the appearance animations for only the initial appearance.
|
||||
using (arrowContainer.BeginDelayedSequence(450))
|
||||
{
|
||||
ShowMovingAverage.BindValueChanged(visible =>
|
||||
{
|
||||
arrowContainer.FadeTo(visible.NewValue ? 1 : 0, 250, Easing.OutQuint);
|
||||
arrowContainer.ScaleTo(visible.NewValue ? new Vector2(1) : new Vector2(0, 1), 250, Easing.OutQuint);
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void recreateCentreMarker(CentreMarkerStyles style)
|
||||
{
|
||||
if (centreMarkerDrawables != null)
|
||||
{
|
||||
foreach (var d in centreMarkerDrawables)
|
||||
{
|
||||
d.ScaleTo(0, 500, Easing.OutQuint)
|
||||
.FadeOut(500, Easing.OutQuint);
|
||||
|
||||
d.Expire();
|
||||
}
|
||||
|
||||
centreMarkerDrawables = null;
|
||||
}
|
||||
|
||||
switch (style)
|
||||
{
|
||||
case CentreMarkerStyles.None:
|
||||
break;
|
||||
|
||||
case CentreMarkerStyles.Circle:
|
||||
centreMarkerDrawables = new Drawable[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
Name = "middle marker behind",
|
||||
Colour = GetColourForHitResult(hitWindows.Last().result),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Depth = float.MaxValue,
|
||||
Size = new Vector2(centre_marker_size),
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Name = "middle marker in front",
|
||||
Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Depth = float.MinValue,
|
||||
Size = new Vector2(centre_marker_size / 2f),
|
||||
},
|
||||
};
|
||||
break;
|
||||
|
||||
case CentreMarkerStyles.Line:
|
||||
const float border_size = 1.5f;
|
||||
|
||||
centreMarkerDrawables = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Name = "middle marker behind",
|
||||
Colour = GetColourForHitResult(hitWindows.Last().result),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Depth = float.MaxValue,
|
||||
Size = new Vector2(judgement_line_width, centre_marker_size / 3f),
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Name = "middle marker in front",
|
||||
Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Depth = float.MinValue,
|
||||
Size = new Vector2(judgement_line_width - border_size, centre_marker_size / 3f - border_size),
|
||||
},
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(style), style, null);
|
||||
}
|
||||
|
||||
if (centreMarkerDrawables != null)
|
||||
{
|
||||
foreach (var d in centreMarkerDrawables)
|
||||
{
|
||||
colourBars.Add(d);
|
||||
|
||||
d.FadeInFromZero(500, Easing.OutQuint)
|
||||
.ScaleTo(0).ScaleTo(1, 1000, Easing.OutElasticHalf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void recreateLabels(LabelStyles style)
|
||||
{
|
||||
const float icon_size = 14;
|
||||
|
||||
labelEarly?.Expire();
|
||||
labelEarly = null;
|
||||
|
||||
labelLate?.Expire();
|
||||
labelLate = null;
|
||||
|
||||
switch (style)
|
||||
{
|
||||
case LabelStyles.None:
|
||||
break;
|
||||
|
||||
case LabelStyles.Icons:
|
||||
labelEarly = new SpriteIcon
|
||||
{
|
||||
Y = -10,
|
||||
Size = new Vector2(icon_size),
|
||||
Icon = FontAwesome.Solid.ShippingFast,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
|
||||
labelLate = new SpriteIcon
|
||||
{
|
||||
Y = 10,
|
||||
Size = new Vector2(icon_size),
|
||||
Icon = FontAwesome.Solid.Bicycle,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
case LabelStyles.Text:
|
||||
labelEarly = new OsuSpriteText
|
||||
{
|
||||
Y = -10,
|
||||
Text = "Early",
|
||||
Font = OsuFont.Default.With(size: 10),
|
||||
Height = 12,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
|
||||
labelLate = new OsuSpriteText
|
||||
{
|
||||
Y = 10,
|
||||
Text = "Late",
|
||||
Font = OsuFont.Default.With(size: 10),
|
||||
Height = 12,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(style), style, null);
|
||||
}
|
||||
|
||||
if (labelEarly != null)
|
||||
{
|
||||
colourBars.Add(labelEarly);
|
||||
labelEarly.FadeInFromZero(500);
|
||||
}
|
||||
|
||||
if (labelLate != null)
|
||||
{
|
||||
colourBars.Add(labelLate);
|
||||
labelLate.FadeInFromZero(500);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -164,8 +336,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
base.Update();
|
||||
|
||||
// undo any layout rotation to display icons in the correct orientation
|
||||
iconEarly.Rotation = -Rotation;
|
||||
iconLate.Rotation = -Rotation;
|
||||
if (labelEarly != null) labelEarly.Rotation = -Rotation;
|
||||
if (labelLate != null) labelLate.Rotation = -Rotation;
|
||||
}
|
||||
|
||||
private void createColourBars((HitResult result, double length)[] windows)
|
||||
@ -224,11 +396,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
}
|
||||
}
|
||||
|
||||
private double floatingAverage;
|
||||
private Container colourBars;
|
||||
|
||||
private const int max_concurrent_judgements = 50;
|
||||
|
||||
protected override void OnNewJudgement(JudgementResult judgement)
|
||||
{
|
||||
const int arrow_move_duration = 800;
|
||||
@ -255,6 +422,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
|
||||
judgementsContainer.Add(new JudgementLine
|
||||
{
|
||||
JudgementLineThickness = { BindTarget = JudgementLineThickness },
|
||||
Y = getRelativeJudgementPosition(judgement.TimeOffset),
|
||||
Colour = GetColourForHitResult(judgement.Type),
|
||||
});
|
||||
@ -268,11 +436,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
|
||||
internal class JudgementLine : CompositeDrawable
|
||||
{
|
||||
public readonly BindableNumber<float> JudgementLineThickness = new BindableFloat();
|
||||
|
||||
public JudgementLine()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
RelativePositionAxes = Axes.Y;
|
||||
Height = judgement_line_height;
|
||||
|
||||
Blending = BlendingParameters.Additive;
|
||||
|
||||
@ -295,6 +464,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
Alpha = 0;
|
||||
Width = 0;
|
||||
|
||||
JudgementLineThickness.BindValueChanged(thickness => Height = thickness.NewValue, true);
|
||||
|
||||
this
|
||||
.FadeTo(0.6f, judgement_fade_in_duration, Easing.OutQuint)
|
||||
.ResizeWidthTo(1, judgement_fade_in_duration, Easing.OutQuint)
|
||||
@ -306,5 +477,19 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
}
|
||||
|
||||
public override void Clear() => judgementsContainer.Clear();
|
||||
|
||||
public enum CentreMarkerStyles
|
||||
{
|
||||
None,
|
||||
Circle,
|
||||
Line
|
||||
}
|
||||
|
||||
public enum LabelStyles
|
||||
{
|
||||
None,
|
||||
Icons,
|
||||
Text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
positionalAdjust = Vector2.Distance(e.ScreenSpaceMousePosition, button.ScreenSpaceDrawQuad.Centre) / 200;
|
||||
positionalAdjust = Vector2.Distance(e.MousePosition, button.ToSpaceOfOtherDrawable(button.DrawRectangle.Centre, Parent)) / 100;
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
@ -41,12 +42,10 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
private const float alpha_when_invalid = 0.3f;
|
||||
|
||||
[CanBeNull]
|
||||
[Resolved(CanBeNull = true)]
|
||||
[Resolved]
|
||||
private ScoreProcessor scoreProcessor { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
[CanBeNull]
|
||||
[Resolved]
|
||||
private GameplayState gameplayState { get; set; }
|
||||
|
||||
[CanBeNull]
|
||||
@ -55,6 +54,8 @@ namespace osu.Game.Screens.Play.HUD
|
||||
private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource();
|
||||
|
||||
private JudgementResult lastJudgement;
|
||||
private PerformanceCalculator performanceCalculator;
|
||||
private ScoreInfo scoreInfo;
|
||||
|
||||
public PerformancePointsCounter()
|
||||
{
|
||||
@ -70,8 +71,11 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
if (gameplayState != null)
|
||||
{
|
||||
performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator();
|
||||
clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray();
|
||||
|
||||
scoreInfo = new ScoreInfo(gameplayState.Score.ScoreInfo.BeatmapInfo, gameplayState.Score.ScoreInfo.Ruleset) { Mods = clonedMods };
|
||||
|
||||
var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap);
|
||||
difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token)
|
||||
.ContinueWith(task => Schedule(() =>
|
||||
@ -120,19 +124,14 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
var attrib = getAttributeAtTime(judgement);
|
||||
|
||||
if (gameplayState == null || attrib == null)
|
||||
if (gameplayState == null || attrib == null || scoreProcessor == null)
|
||||
{
|
||||
IsValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// awkward but we need to make sure the true mods are not passed to PerformanceCalculator as it makes a mess of track applications.
|
||||
var scoreInfo = gameplayState.Score.ScoreInfo.DeepClone();
|
||||
scoreInfo.Mods = clonedMods;
|
||||
|
||||
var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(attrib, scoreInfo);
|
||||
|
||||
Current.Value = (int)Math.Round(calculator?.Calculate().Total ?? 0, MidpointRounding.AwayFromZero);
|
||||
scoreProcessor.PopulateScore(scoreInfo);
|
||||
Current.Value = (int)Math.Round(performanceCalculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero);
|
||||
IsValid = true;
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
@ -34,6 +37,8 @@ namespace osu.Game.Screens.Play.HUD
|
||||
/// <inheritdoc cref="ISkinnableDrawable.UsesFixedAnchor"/>
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
public Dictionary<string, object> Settings { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
public List<SkinnableInfo> Children { get; } = new List<SkinnableInfo>();
|
||||
|
||||
[JsonConstructor]
|
||||
@ -58,6 +63,14 @@ namespace osu.Game.Screens.Play.HUD
|
||||
if (component is ISkinnableDrawable skinnable)
|
||||
UsesFixedAnchor = skinnable.UsesFixedAnchor;
|
||||
|
||||
foreach (var (_, property) in component.GetSettingsSourceProperties())
|
||||
{
|
||||
var bindable = (IBindable)property.GetValue(component);
|
||||
|
||||
if (!bindable.IsDefault)
|
||||
Settings.Add(property.Name.Underscore(), bindable.GetUnderlyingSettingValue());
|
||||
}
|
||||
|
||||
if (component is Container<Drawable> container)
|
||||
{
|
||||
foreach (var child in container.OfType<ISkinnableDrawable>().OfType<Drawable>())
|
||||
|
@ -615,16 +615,22 @@ namespace osu.Game.Screens.Play
|
||||
/// <param name="time">The destination time to seek to.</param>
|
||||
internal void NonFrameStableSeek(double time)
|
||||
{
|
||||
if (frameStablePlaybackResetDelegate?.Cancelled == false && !frameStablePlaybackResetDelegate.Completed)
|
||||
frameStablePlaybackResetDelegate.RunTask();
|
||||
// TODO: This schedule should not be required and is a temporary hotfix.
|
||||
// See https://github.com/ppy/osu/issues/17267 for the issue.
|
||||
// See https://github.com/ppy/osu/pull/17302 for a better fix which needs some more time.
|
||||
ScheduleAfterChildren(() =>
|
||||
{
|
||||
if (frameStablePlaybackResetDelegate?.Cancelled == false && !frameStablePlaybackResetDelegate.Completed)
|
||||
frameStablePlaybackResetDelegate.RunTask();
|
||||
|
||||
bool wasFrameStable = DrawableRuleset.FrameStablePlayback;
|
||||
DrawableRuleset.FrameStablePlayback = false;
|
||||
bool wasFrameStable = DrawableRuleset.FrameStablePlayback;
|
||||
DrawableRuleset.FrameStablePlayback = false;
|
||||
|
||||
Seek(time);
|
||||
Seek(time);
|
||||
|
||||
// Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek.
|
||||
frameStablePlaybackResetDelegate = ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable);
|
||||
// Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek.
|
||||
frameStablePlaybackResetDelegate = ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,10 +1,11 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using ManagedBass.Fx;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
@ -48,31 +49,31 @@ namespace osu.Game.Screens.Play
|
||||
public override bool HandlePositionalInput => true;
|
||||
|
||||
// We show the previous screen status
|
||||
protected override UserActivity InitialActivity => null;
|
||||
protected override UserActivity? InitialActivity => null;
|
||||
|
||||
protected override bool PlayResumeSound => false;
|
||||
|
||||
protected BeatmapMetadataDisplay MetadataInfo { get; private set; }
|
||||
protected BeatmapMetadataDisplay MetadataInfo { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// A fill flow containing the player settings groups, exposed for the ability to hide it from inheritors of the player loader.
|
||||
/// </summary>
|
||||
protected FillFlowContainer<PlayerSettingsGroup> PlayerSettings { get; private set; }
|
||||
protected FillFlowContainer<PlayerSettingsGroup> PlayerSettings { get; private set; } = null!;
|
||||
|
||||
protected VisualSettings VisualSettings { get; private set; }
|
||||
protected VisualSettings VisualSettings { get; private set; } = null!;
|
||||
|
||||
protected AudioSettings AudioSettings { get; private set; }
|
||||
protected AudioSettings AudioSettings { get; private set; } = null!;
|
||||
|
||||
protected Task LoadTask { get; private set; }
|
||||
protected Task? LoadTask { get; private set; }
|
||||
|
||||
protected Task DisposalTask { get; private set; }
|
||||
protected Task? DisposalTask { get; private set; }
|
||||
|
||||
private bool backgroundBrightnessReduction;
|
||||
|
||||
private readonly BindableDouble volumeAdjustment = new BindableDouble(1);
|
||||
|
||||
private AudioFilter lowPassFilter;
|
||||
private AudioFilter highPassFilter;
|
||||
private AudioFilter lowPassFilter = null!;
|
||||
private AudioFilter highPassFilter = null!;
|
||||
|
||||
protected bool BackgroundBrightnessReduction
|
||||
{
|
||||
@ -90,47 +91,49 @@ namespace osu.Game.Screens.Play
|
||||
private bool readyForPush =>
|
||||
!playerConsumed
|
||||
// don't push unless the player is completely loaded
|
||||
&& player?.LoadState == LoadState.Ready
|
||||
&& CurrentPlayer?.LoadState == LoadState.Ready
|
||||
// don't push if the user is hovering one of the panes, unless they are idle.
|
||||
&& (IsHovered || idleTracker.IsIdle.Value)
|
||||
// don't push if the user is dragging a slider or otherwise.
|
||||
&& inputManager?.DraggedDrawable == null
|
||||
&& inputManager.DraggedDrawable == null
|
||||
// don't push if a focused overlay is visible, like settings.
|
||||
&& inputManager?.FocusedDrawable == null;
|
||||
&& inputManager.FocusedDrawable == null;
|
||||
|
||||
private readonly Func<Player> createPlayer;
|
||||
|
||||
private Player player;
|
||||
/// <summary>
|
||||
/// The <see cref="Player"/> instance being loaded by this screen.
|
||||
/// </summary>
|
||||
public Player? CurrentPlayer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the curent player instance has been consumed via <see cref="consumePlayer"/>.
|
||||
/// Whether the current player instance has been consumed via <see cref="consumePlayer"/>.
|
||||
/// </summary>
|
||||
private bool playerConsumed;
|
||||
|
||||
private LogoTrackingContainer content;
|
||||
private LogoTrackingContainer content = null!;
|
||||
|
||||
private bool hideOverlays;
|
||||
|
||||
private InputManager inputManager;
|
||||
private InputManager inputManager = null!;
|
||||
|
||||
private IdleTracker idleTracker;
|
||||
private IdleTracker idleTracker = null!;
|
||||
|
||||
private ScheduledDelegate scheduledPushPlayer;
|
||||
private ScheduledDelegate? scheduledPushPlayer;
|
||||
|
||||
[CanBeNull]
|
||||
private EpilepsyWarning epilepsyWarning;
|
||||
private EpilepsyWarning? epilepsyWarning;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private NotificationOverlay notificationOverlay { get; set; }
|
||||
private NotificationOverlay? notificationOverlay { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private VolumeOverlay volumeOverlay { get; set; }
|
||||
private VolumeOverlay? volumeOverlay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private AudioManager audioManager { get; set; }
|
||||
private AudioManager audioManager { get; set; } = null!;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private BatteryInfo batteryInfo { get; set; }
|
||||
private BatteryInfo? batteryInfo { get; set; }
|
||||
|
||||
public PlayerLoader(Func<Player> createPlayer)
|
||||
{
|
||||
@ -237,12 +240,14 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
base.OnResuming(last);
|
||||
|
||||
var lastScore = player.Score;
|
||||
Debug.Assert(CurrentPlayer != null);
|
||||
|
||||
var lastScore = CurrentPlayer.Score;
|
||||
|
||||
AudioSettings.ReferenceScore.Value = lastScore?.ScoreInfo;
|
||||
|
||||
// prepare for a retry.
|
||||
player = null;
|
||||
CurrentPlayer = null;
|
||||
playerConsumed = false;
|
||||
cancelLoad();
|
||||
|
||||
@ -344,9 +349,10 @@ namespace osu.Game.Screens.Play
|
||||
private Player consumePlayer()
|
||||
{
|
||||
Debug.Assert(!playerConsumed);
|
||||
Debug.Assert(CurrentPlayer != null);
|
||||
|
||||
playerConsumed = true;
|
||||
return player;
|
||||
return CurrentPlayer;
|
||||
}
|
||||
|
||||
private void prepareNewPlayer()
|
||||
@ -354,11 +360,11 @@ namespace osu.Game.Screens.Play
|
||||
if (!this.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
player = createPlayer();
|
||||
player.RestartCount = restartCount++;
|
||||
player.RestartRequested = restartRequested;
|
||||
CurrentPlayer = createPlayer();
|
||||
CurrentPlayer.RestartCount = restartCount++;
|
||||
CurrentPlayer.RestartRequested = restartRequested;
|
||||
|
||||
LoadTask = LoadComponentAsync(player, _ => MetadataInfo.Loading = false);
|
||||
LoadTask = LoadComponentAsync(CurrentPlayer, _ => MetadataInfo.Loading = false);
|
||||
}
|
||||
|
||||
private void restartRequested()
|
||||
@ -472,7 +478,7 @@ namespace osu.Game.Screens.Play
|
||||
if (isDisposing)
|
||||
{
|
||||
// if the player never got pushed, we should explicitly dispose it.
|
||||
DisposalTask = LoadTask?.ContinueWith(_ => player?.Dispose());
|
||||
DisposalTask = LoadTask?.ContinueWith(_ => CurrentPlayer?.Dispose());
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,7 +486,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
#region Mute warning
|
||||
|
||||
private Bindable<bool> muteWarningShownOnce;
|
||||
private Bindable<bool> muteWarningShownOnce = null!;
|
||||
|
||||
private int restartCount;
|
||||
|
||||
@ -535,7 +541,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
#region Low battery warning
|
||||
|
||||
private Bindable<bool> batteryWarningShownOnce;
|
||||
private Bindable<bool> batteryWarningShownOnce = null!;
|
||||
|
||||
private void showBatteryWarningIfNeeded()
|
||||
{
|
||||
|
@ -73,9 +73,12 @@ namespace osu.Game.Screens.Play
|
||||
[Resolved(canBeNull: true)]
|
||||
private Player player { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
[Resolved]
|
||||
private GameplayClock gameplayClock { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private DrawableRuleset drawableRuleset { get; set; }
|
||||
|
||||
private IClock referenceClock;
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
@ -113,7 +116,7 @@ namespace osu.Game.Screens.Play
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuColour colours, OsuConfigManager config, DrawableRuleset drawableRuleset)
|
||||
private void load(OsuColour colours, OsuConfigManager config)
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
|
@ -68,7 +68,7 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
var topStatistics = new List<StatisticDisplay>
|
||||
{
|
||||
new AccuracyStatistic(score.Accuracy),
|
||||
new ComboStatistic(score.MaxCombo, !score.Statistics.TryGetValue(HitResult.Miss, out int missCount) || missCount == 0),
|
||||
new ComboStatistic(score.MaxCombo, beatmap.MaxCombo, score.Statistics.All(stat => !stat.Key.BreaksCombo() || stat.Value == 0)),
|
||||
new PerformanceStatistic(score),
|
||||
};
|
||||
|
||||
|
@ -25,9 +25,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
|
||||
/// Creates a new <see cref="ComboStatistic"/>.
|
||||
/// </summary>
|
||||
/// <param name="combo">The combo to be displayed.</param>
|
||||
/// <param name="maxCombo">The maximum value of <paramref name="combo"/>.</param>
|
||||
/// <param name="isPerfect">Whether this is a perfect combo.</param>
|
||||
public ComboStatistic(int combo, bool isPerfect)
|
||||
: base("combo", combo)
|
||||
public ComboStatistic(int combo, int? maxCombo, bool isPerfect)
|
||||
: base("combo", combo, maxCombo)
|
||||
{
|
||||
this.isPerfect = isPerfect;
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using System.Diagnostics;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Screens.Select
|
||||
{
|
||||
@ -235,6 +236,10 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
}
|
||||
},
|
||||
new SkinnableTargetContainer(SkinnableTarget.SongSelect)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
});
|
||||
|
||||
if (ShowFooter)
|
||||
|
92
osu.Game/Skinning/Components/BigBlackBox.cs
Normal file
92
osu.Game/Skinning/Components/BigBlackBox.cs
Normal file
@ -0,0 +1,92 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Skinning.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Intended to be a test bed for skinning. May be removed at some point in the future.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class BigBlackBox : CompositeDrawable, ISkinnableDrawable
|
||||
{
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
[SettingSource("Spinning text", "Whether the big text should spin")]
|
||||
public Bindable<bool> TextSpin { get; } = new BindableBool();
|
||||
|
||||
[SettingSource("Alpha", "The alpha value of this box")]
|
||||
public BindableNumber<float> BoxAlpha { get; } = new BindableNumber<float>(1)
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
Precision = 0.01f,
|
||||
};
|
||||
|
||||
private readonly Box box;
|
||||
private readonly OsuSpriteText text;
|
||||
private readonly OsuTextFlowContainer disclaimer;
|
||||
|
||||
public BigBlackBox()
|
||||
{
|
||||
Size = new Vector2(250);
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 20;
|
||||
CornerExponent = 5;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
box = new Box
|
||||
{
|
||||
Colour = Color4.Black,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Text = "Big Black Box",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.Default.With(size: 40)
|
||||
},
|
||||
disclaimer = new OsuTextFlowContainer(st => st.Font = OsuFont.Default.With(size: 10))
|
||||
{
|
||||
Text = "This is intended to be a test component and may disappear in the future!",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding(10),
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
TextAnchor = Anchor.TopCentre,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
BoxAlpha.BindValueChanged(alpha => box.Alpha = alpha.NewValue, true);
|
||||
TextSpin.BindValueChanged(spin =>
|
||||
{
|
||||
if (spin.NewValue)
|
||||
text.Spin(1000, RotationDirection.Clockwise);
|
||||
else
|
||||
text.ClearTransforms();
|
||||
}, true);
|
||||
|
||||
disclaimer.FadeOutFromOne(5000, Easing.InQuint);
|
||||
}
|
||||
}
|
||||
}
|
@ -70,6 +70,14 @@ namespace osu.Game.Skinning
|
||||
case SkinnableTargetComponent target:
|
||||
switch (target.Target)
|
||||
{
|
||||
case SkinnableTarget.SongSelect:
|
||||
var songSelectComponents = new SkinnableTargetComponentsContainer(container =>
|
||||
{
|
||||
// do stuff when we need to.
|
||||
});
|
||||
|
||||
return songSelectComponents;
|
||||
|
||||
case SkinnableTarget.MainHUDComponents:
|
||||
var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container =>
|
||||
{
|
||||
|
@ -5,78 +5,62 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
public class SkinComponentToolbox : ScrollingToolboxGroup
|
||||
public class SkinComponentToolbox : EditorSidebarSection
|
||||
{
|
||||
public const float WIDTH = 200;
|
||||
|
||||
public Action<Type> RequestPlacement;
|
||||
|
||||
private const float component_display_scale = 0.8f;
|
||||
private readonly CompositeDrawable target;
|
||||
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor
|
||||
public SkinComponentToolbox(CompositeDrawable target = null)
|
||||
: base("Components")
|
||||
{
|
||||
Combo = { Value = RNG.Next(1, 1000) },
|
||||
TotalScore = { Value = RNG.Next(1000, 10000000) }
|
||||
};
|
||||
|
||||
[Cached(typeof(HealthProcessor))]
|
||||
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||
|
||||
public SkinComponentToolbox(float height)
|
||||
: base("Components", height)
|
||||
{
|
||||
RelativeSizeAxes = Axes.None;
|
||||
Width = WIDTH;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
private FillFlowContainer fill;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
FillFlowContainer fill;
|
||||
|
||||
Child = fill = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(20)
|
||||
Spacing = new Vector2(2)
|
||||
};
|
||||
|
||||
reloadComponents();
|
||||
}
|
||||
|
||||
private void reloadComponents()
|
||||
{
|
||||
fill.Clear();
|
||||
|
||||
var skinnableTypes = typeof(OsuGame).Assembly.GetTypes()
|
||||
.Where(t => !t.IsInterface)
|
||||
.Where(t => !t.IsInterface && !t.IsAbstract)
|
||||
.Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t))
|
||||
.OrderBy(t => t.Name)
|
||||
.ToArray();
|
||||
|
||||
foreach (var type in skinnableTypes)
|
||||
{
|
||||
var component = attemptAddComponent(type);
|
||||
|
||||
if (component != null)
|
||||
{
|
||||
component.RequestPlacement = t => RequestPlacement?.Invoke(t);
|
||||
fill.Add(component);
|
||||
}
|
||||
}
|
||||
attemptAddComponent(type);
|
||||
}
|
||||
|
||||
private static ToolboxComponentButton attemptAddComponent(Type type)
|
||||
private void attemptAddComponent(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -84,14 +68,21 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
Debug.Assert(instance != null);
|
||||
|
||||
if (!((ISkinnableDrawable)instance).IsEditable)
|
||||
return null;
|
||||
if (!((ISkinnableDrawable)instance).IsEditable) return;
|
||||
|
||||
return new ToolboxComponentButton(instance);
|
||||
fill.Add(new ToolboxComponentButton(instance, target)
|
||||
{
|
||||
RequestPlacement = t => RequestPlacement?.Invoke(t)
|
||||
});
|
||||
}
|
||||
catch
|
||||
catch (DependencyNotRegisteredException)
|
||||
{
|
||||
return null;
|
||||
// This loading code relies on try-catching any dependency injection errors to know which components are valid for the current target screen.
|
||||
// If a screen can't provide the required dependencies, a skinnable component should not be displayed in the list.
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, $"Skin component {type} could not be loaded in the editor component list due to an error");
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,51 +93,65 @@ namespace osu.Game.Skinning.Editor
|
||||
public override bool PropagateNonPositionalInputSubTree => false;
|
||||
|
||||
private readonly Drawable component;
|
||||
private readonly CompositeDrawable dependencySource;
|
||||
|
||||
public Action<Type> RequestPlacement;
|
||||
|
||||
private Container innerContainer;
|
||||
|
||||
public ToolboxComponentButton(Drawable component)
|
||||
private const float contracted_size = 60;
|
||||
private const float expanded_size = 120;
|
||||
|
||||
public ToolboxComponentButton(Drawable component, CompositeDrawable dependencySource)
|
||||
{
|
||||
this.component = component;
|
||||
this.dependencySource = dependencySource;
|
||||
|
||||
Enabled.Value = true;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 70;
|
||||
Height = contracted_size;
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
this.Delay(300).ResizeHeightTo(expanded_size, 500, Easing.OutQuint);
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
base.OnHoverLost(e);
|
||||
this.ResizeHeightTo(contracted_size, 500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
private void load(OverlayColourProvider colourProvider, OsuColour colours)
|
||||
{
|
||||
BackgroundColour = colours.Gray3;
|
||||
Content.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 2,
|
||||
Offset = new Vector2(0, 1),
|
||||
Colour = Color4.Black.Opacity(0.5f)
|
||||
};
|
||||
BackgroundColour = colourProvider.Background3;
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(10) { Bottom = 20 },
|
||||
Masking = true,
|
||||
Child = innerContainer = new DependencyBorrowingContainer(dependencySource)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Child = component
|
||||
},
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = component.GetType().Name,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Margin = new MarginPadding(5),
|
||||
},
|
||||
innerContainer = new Container
|
||||
{
|
||||
Y = 10,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Scale = new Vector2(component_display_scale),
|
||||
Masking = true,
|
||||
Child = component
|
||||
}
|
||||
});
|
||||
|
||||
// adjust provided component to fit / display in a known state.
|
||||
@ -154,14 +159,17 @@ namespace osu.Game.Skinning.Editor
|
||||
component.Origin = Anchor.Centre;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
protected override void Update()
|
||||
{
|
||||
base.LoadComplete();
|
||||
base.Update();
|
||||
|
||||
if (component.RelativeSizeAxes != Axes.None)
|
||||
if (component.DrawSize != Vector2.Zero)
|
||||
{
|
||||
innerContainer.AutoSizeAxes = Axes.None;
|
||||
innerContainer.Height = 100;
|
||||
float bestScale = Math.Min(
|
||||
innerContainer.DrawWidth / component.DrawWidth,
|
||||
innerContainer.DrawHeight / component.DrawHeight);
|
||||
|
||||
innerContainer.Scale = new Vector2(bestScale);
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,5 +179,18 @@ namespace osu.Game.Skinning.Editor
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class DependencyBorrowingContainer : Container
|
||||
{
|
||||
private readonly CompositeDrawable donor;
|
||||
|
||||
public DependencyBorrowingContainer(CompositeDrawable donor)
|
||||
{
|
||||
this.donor = donor;
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||
new DependencyContainer(donor?.Dependencies ?? base.CreateChildDependencies(parent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osu.Game.Screens.Edit.Components.Menus;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
@ -40,87 +42,121 @@ namespace osu.Game.Skinning.Editor
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
private bool hasBegunMutating;
|
||||
|
||||
private Container content;
|
||||
|
||||
private EditorSidebar componentsSidebar;
|
||||
private EditorSidebar settingsSidebar;
|
||||
|
||||
public SkinEditor()
|
||||
{
|
||||
}
|
||||
|
||||
public SkinEditor(Drawable targetScreen)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
UpdateTargetScreen(targetScreen);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
const float menu_height = 40;
|
||||
|
||||
InternalChild = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
Child = new GridContainer
|
||||
{
|
||||
new Container
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
Name = "Top bar",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Depth = float.MinValue,
|
||||
Height = 40,
|
||||
Children = new Drawable[]
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
},
|
||||
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new EditorMenuBar
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Items = new[]
|
||||
Name = "Menu container",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Depth = float.MinValue,
|
||||
Height = menu_height,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new MenuItem("File")
|
||||
new EditorMenuBar
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Items = new[]
|
||||
{
|
||||
new EditorMenuItem("Save", MenuItemType.Standard, Save),
|
||||
new EditorMenuItem("Revert to default", MenuItemType.Destructive, revert),
|
||||
new EditorMenuItemSpacer(),
|
||||
new EditorMenuItem("Exit", MenuItemType.Standard, Hide),
|
||||
},
|
||||
new MenuItem("File")
|
||||
{
|
||||
Items = new[]
|
||||
{
|
||||
new EditorMenuItem("Save", MenuItemType.Standard, Save),
|
||||
new EditorMenuItem("Revert to default", MenuItemType.Destructive, revert),
|
||||
new EditorMenuItemSpacer(),
|
||||
new EditorMenuItem("Exit", MenuItemType.Standard, Hide),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
headerText = new OsuTextFlowContainer
|
||||
{
|
||||
TextAnchor = Anchor.TopRight,
|
||||
Padding = new MarginPadding(5),
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new SkinEditorSceneLibrary
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
componentsSidebar = new EditorSidebar(),
|
||||
content = new Container
|
||||
{
|
||||
Depth = float.MaxValue,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
settingsSidebar = new EditorSidebar(),
|
||||
}
|
||||
}
|
||||
},
|
||||
headerText = new OsuTextFlowContainer
|
||||
{
|
||||
TextAnchor = Anchor.TopRight,
|
||||
Padding = new MarginPadding(5),
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
},
|
||||
},
|
||||
},
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new SkinComponentToolbox(600)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RequestPlacement = placeComponent
|
||||
},
|
||||
content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -143,19 +179,26 @@ namespace osu.Game.Skinning.Editor
|
||||
hasBegunMutating = false;
|
||||
Scheduler.AddOnce(skinChanged);
|
||||
}, true);
|
||||
|
||||
SelectedComponents.BindCollectionChanged((_, __) => Scheduler.AddOnce(populateSettings), true);
|
||||
}
|
||||
|
||||
public void UpdateTargetScreen(Drawable targetScreen)
|
||||
{
|
||||
this.targetScreen = targetScreen;
|
||||
|
||||
SelectedComponents.Clear();
|
||||
|
||||
Scheduler.AddOnce(loadBlueprintContainer);
|
||||
Scheduler.AddOnce(populateSettings);
|
||||
|
||||
void loadBlueprintContainer()
|
||||
{
|
||||
content.Children = new Drawable[]
|
||||
content.Child = new SkinBlueprintContainer(targetScreen);
|
||||
|
||||
componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable)
|
||||
{
|
||||
new SkinBlueprintContainer(targetScreen),
|
||||
RequestPlacement = placeComponent
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -184,12 +227,7 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
private void placeComponent(Type type)
|
||||
{
|
||||
var target = availableTargets.FirstOrDefault()?.Target;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
var targetContainer = getTarget(target.Value);
|
||||
var targetContainer = getFirstTarget();
|
||||
|
||||
if (targetContainer == null)
|
||||
return;
|
||||
@ -210,8 +248,18 @@ namespace osu.Game.Skinning.Editor
|
||||
SelectedComponents.Add(component);
|
||||
}
|
||||
|
||||
private void populateSettings()
|
||||
{
|
||||
settingsSidebar.Clear();
|
||||
|
||||
foreach (var component in SelectedComponents.OfType<Drawable>())
|
||||
settingsSidebar.Add(new SkinSettingsToolbox(component));
|
||||
}
|
||||
|
||||
private IEnumerable<ISkinnableTarget> availableTargets => targetScreen.ChildrenOfType<ISkinnableTarget>();
|
||||
|
||||
private ISkinnableTarget getFirstTarget() => availableTargets.FirstOrDefault();
|
||||
|
||||
private ISkinnableTarget getTarget(SkinnableTarget target)
|
||||
{
|
||||
return availableTargets.FirstOrDefault(c => c.Target == target);
|
||||
|
@ -3,15 +3,15 @@
|
||||
|
||||
using System.Diagnostics;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Screens;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
@ -21,16 +21,21 @@ namespace osu.Game.Skinning.Editor
|
||||
/// </summary>
|
||||
public class SkinEditorOverlay : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private readonly ScalingContainer target;
|
||||
private readonly ScalingContainer scalingContainer;
|
||||
|
||||
[CanBeNull]
|
||||
private SkinEditor skinEditor;
|
||||
|
||||
public const float VISIBLE_TARGET_SCALE = 0.8f;
|
||||
|
||||
public SkinEditorOverlay(ScalingContainer target)
|
||||
[Resolved(canBeNull: true)]
|
||||
private OsuGame game { get; set; }
|
||||
|
||||
private OsuScreen lastTargetScreen;
|
||||
|
||||
public SkinEditorOverlay(ScalingContainer scalingContainer)
|
||||
{
|
||||
this.target = target;
|
||||
this.scalingContainer = scalingContainer;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
@ -77,8 +82,8 @@ namespace osu.Game.Skinning.Editor
|
||||
return;
|
||||
}
|
||||
|
||||
var editor = new SkinEditor(target);
|
||||
editor.State.BindValueChanged(editorVisibilityChanged);
|
||||
var editor = new SkinEditor();
|
||||
editor.State.BindValueChanged(visibility => updateComponentVisibility());
|
||||
|
||||
skinEditor = editor;
|
||||
|
||||
@ -95,19 +100,31 @@ namespace osu.Game.Skinning.Editor
|
||||
return;
|
||||
|
||||
AddInternal(editor);
|
||||
|
||||
SetTarget(lastTargetScreen);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void editorVisibilityChanged(ValueChangedEvent<Visibility> visibility)
|
||||
private void updateComponentVisibility()
|
||||
{
|
||||
if (visibility.NewValue == Visibility.Visible)
|
||||
Debug.Assert(skinEditor != null);
|
||||
|
||||
const float toolbar_padding_requirement = 0.18f;
|
||||
|
||||
if (skinEditor.State.Value == Visibility.Visible)
|
||||
{
|
||||
target.SetCustomRect(new RectangleF(0.18f, 0.1f, VISIBLE_TARGET_SCALE, VISIBLE_TARGET_SCALE), true);
|
||||
scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.2f, 0.8f - toolbar_padding_requirement, 0.7f), true);
|
||||
|
||||
game?.Toolbar.Hide();
|
||||
game?.CloseAllOverlays();
|
||||
}
|
||||
else
|
||||
{
|
||||
target.SetCustomRect(null);
|
||||
scalingContainer.SetCustomRect(null);
|
||||
|
||||
if (lastTargetScreen?.HideOverlaysOnEnter != true)
|
||||
game?.Toolbar.Show();
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,23 +135,25 @@ namespace osu.Game.Skinning.Editor
|
||||
/// <summary>
|
||||
/// Set a new target screen which will be used to find skinnable components.
|
||||
/// </summary>
|
||||
public void SetTarget(Screen screen)
|
||||
public void SetTarget(OsuScreen screen)
|
||||
{
|
||||
lastTargetScreen = screen;
|
||||
|
||||
if (skinEditor == null) return;
|
||||
|
||||
skinEditor.Save();
|
||||
|
||||
// ensure the toolbar is re-hidden even if a new screen decides to try and show it.
|
||||
updateComponentVisibility();
|
||||
|
||||
// AddOnce with parameter will ensure the newest target is loaded if there is any overlap.
|
||||
Scheduler.AddOnce(setTarget, screen);
|
||||
}
|
||||
|
||||
private void setTarget(Screen target)
|
||||
private void setTarget(OsuScreen target)
|
||||
{
|
||||
Debug.Assert(skinEditor != null);
|
||||
|
||||
if (!target.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
if (!target.IsLoaded)
|
||||
{
|
||||
Scheduler.AddOnce(setTarget, target);
|
||||
|
123
osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs
Normal file
123
osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs
Normal file
@ -0,0 +1,123 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
public class SkinEditorSceneLibrary : CompositeDrawable
|
||||
{
|
||||
public const float BUTTON_HEIGHT = 40;
|
||||
|
||||
private const float padding = 10;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private OsuGame game { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; }
|
||||
|
||||
public SkinEditorSceneLibrary()
|
||||
{
|
||||
Height = BUTTON_HEIGHT + padding * 2;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider overlayColourProvider)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = overlayColourProvider.Background6,
|
||||
},
|
||||
new OsuScrollContainer(Direction.Horizontal)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
Name = "Scene library",
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(padding),
|
||||
Padding = new MarginPadding(padding),
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Scene library",
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding(10),
|
||||
},
|
||||
new SceneButton
|
||||
{
|
||||
Text = "Song Select",
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Action = () => game?.PerformFromScreen(screen =>
|
||||
{
|
||||
if (screen is SongSelect)
|
||||
return;
|
||||
|
||||
screen.Push(new PlaySongSelect());
|
||||
}, new[] { typeof(SongSelect) })
|
||||
},
|
||||
new SceneButton
|
||||
{
|
||||
Text = "Gameplay",
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Action = () => game?.PerformFromScreen(screen =>
|
||||
{
|
||||
if (screen is Player)
|
||||
return;
|
||||
|
||||
var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod();
|
||||
if (replayGeneratingMod != null)
|
||||
screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods)));
|
||||
}, new[] { typeof(Player), typeof(SongSelect) })
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class SceneButton : OsuButton
|
||||
{
|
||||
public SceneButton()
|
||||
{
|
||||
Width = 100;
|
||||
Height = BUTTON_HEIGHT;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours)
|
||||
{
|
||||
BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3;
|
||||
Content.CornerRadius = 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -199,6 +199,12 @@ namespace osu.Game.Skinning.Editor
|
||||
Items = createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray()
|
||||
};
|
||||
|
||||
yield return new OsuMenuItem("Reset position", MenuItemType.Standard, () =>
|
||||
{
|
||||
foreach (var blueprint in SelectedBlueprints)
|
||||
((Drawable)blueprint.Item).Position = Vector2.Zero;
|
||||
});
|
||||
|
||||
foreach (var item in base.GetContextMenuItemsForSelection(selection))
|
||||
yield return item;
|
||||
|
||||
|
30
osu.Game/Skinning/Editor/SkinSettingsToolbox.cs
Normal file
30
osu.Game/Skinning/Editor/SkinSettingsToolbox.cs
Normal file
@ -0,0 +1,30 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
internal class SkinSettingsToolbox : EditorSidebarSection
|
||||
{
|
||||
protected override Container<Drawable> Content { get; }
|
||||
|
||||
public SkinSettingsToolbox(Drawable component)
|
||||
: base($"Settings ({component.GetType().Name})")
|
||||
{
|
||||
base.Content.Add(Content = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(10),
|
||||
Children = component.CreateSettingsControls().ToArray()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
@ -21,5 +24,22 @@ namespace osu.Game.Skinning
|
||||
/// If <see langword="true"/>, a fixed anchor point has been defined.
|
||||
/// </summary>
|
||||
bool UsesFixedAnchor { get; set; }
|
||||
|
||||
void CopyAdjustedSetting(IBindable target, object source)
|
||||
{
|
||||
if (source is IBindable sourceBindable)
|
||||
{
|
||||
// copy including transfer of default values.
|
||||
target.BindTo(sourceBindable);
|
||||
target.UnbindFrom(sourceBindable);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(target is IParseable parseable))
|
||||
throw new InvalidOperationException($"Bindable type {target.GetType().ReadableName()} is not {nameof(IParseable)}.");
|
||||
|
||||
parseable.Parse(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
public enum SkinnableTarget
|
||||
{
|
||||
MainHUDComponents
|
||||
MainHUDComponents,
|
||||
SongSelect
|
||||
}
|
||||
}
|
||||
|
@ -127,9 +127,9 @@ namespace osu.Game.Tests.Visual
|
||||
where T : Drawable
|
||||
{
|
||||
if (typeof(T) == typeof(Button))
|
||||
AddUntilStep($"wait for {typeof(T).Name} enabled", () => (this.ChildrenOfType<T>().Single() as Button)?.Enabled.Value == true);
|
||||
AddUntilStep($"wait for {typeof(T).Name} enabled", () => (this.ChildrenOfType<T>().Single() as ClickableContainer)?.Enabled.Value == true);
|
||||
else
|
||||
AddUntilStep($"wait for {typeof(T).Name} enabled", () => this.ChildrenOfType<T>().Single().ChildrenOfType<Button>().Single().Enabled.Value);
|
||||
AddUntilStep($"wait for {typeof(T).Name} enabled", () => this.ChildrenOfType<T>().Single().ChildrenOfType<ClickableContainer>().Single().Enabled.Value);
|
||||
|
||||
AddStep($"click {typeof(T).Name}", () =>
|
||||
{
|
||||
|
@ -88,7 +88,8 @@ namespace osu.Game.Tests.Visual.Spectator
|
||||
/// </summary>
|
||||
/// <param name="userId">The user to send frames for.</param>
|
||||
/// <param name="count">The total number of frames to send.</param>
|
||||
public void SendFramesFromUser(int userId, int count)
|
||||
/// <param name="startTime">The time to start gameplay frames from.</param>
|
||||
public void SendFramesFromUser(int userId, int count, double startTime = 0)
|
||||
{
|
||||
var frames = new List<LegacyReplayFrame>();
|
||||
|
||||
@ -102,7 +103,7 @@ namespace osu.Game.Tests.Visual.Spectator
|
||||
flush();
|
||||
|
||||
var buttonState = currentFrameIndex == lastFrameIndex ? ReplayButtonState.None : ReplayButtonState.Left1;
|
||||
frames.Add(new LegacyReplayFrame(currentFrameIndex * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState));
|
||||
frames.Add(new LegacyReplayFrame(currentFrameIndex * 100 + startTime, RNG.Next(0, 512), RNG.Next(0, 512), buttonState));
|
||||
}
|
||||
|
||||
flush();
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -82,7 +83,8 @@ namespace osu.Game.Updater
|
||||
break;
|
||||
|
||||
case RuntimeInfo.Platform.macOS:
|
||||
bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip", StringComparison.Ordinal));
|
||||
string arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "Apple.Silicon" : "Intel";
|
||||
bestAsset = release.Assets?.Find(f => f.Name.EndsWith($".app.{arch}.zip", StringComparison.Ordinal));
|
||||
break;
|
||||
|
||||
case RuntimeInfo.Platform.Linux:
|
||||
|
@ -1,18 +1,16 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Utils
|
||||
{
|
||||
/// <summary>
|
||||
@ -154,39 +152,6 @@ namespace osu.Game.Utils
|
||||
yield return mod;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the underlying value of the given mod setting object.
|
||||
/// Used in <see cref="APIMod"/> for serialization and equality comparison purposes.
|
||||
/// </summary>
|
||||
/// <param name="setting">The mod setting.</param>
|
||||
public static object GetSettingUnderlyingValue(object setting)
|
||||
{
|
||||
switch (setting)
|
||||
{
|
||||
case Bindable<double> d:
|
||||
return d.Value;
|
||||
|
||||
case Bindable<int> i:
|
||||
return i.Value;
|
||||
|
||||
case Bindable<float> f:
|
||||
return f.Value;
|
||||
|
||||
case Bindable<bool> b:
|
||||
return b.Value;
|
||||
|
||||
case IBindable u:
|
||||
// A mod with unknown (e.g. enum) generic type.
|
||||
var valueMethod = u.GetType().GetProperty(nameof(IBindable<int>.Value));
|
||||
Debug.Assert(valueMethod != null);
|
||||
return valueMethod.GetValue(u);
|
||||
|
||||
default:
|
||||
// fall back for non-bindable cases.
|
||||
return setting;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies all proposed mods are valid for a given ruleset and returns instantiated <see cref="Mod"/>s for further processing.
|
||||
/// </summary>
|
||||
@ -201,15 +166,15 @@ namespace osu.Game.Utils
|
||||
|
||||
foreach (var apiMod in proposedMods)
|
||||
{
|
||||
try
|
||||
{
|
||||
// will throw if invalid
|
||||
valid.Add(apiMod.ToMod(ruleset));
|
||||
}
|
||||
catch
|
||||
var mod = apiMod.ToMod(ruleset);
|
||||
|
||||
if (mod is UnknownMod)
|
||||
{
|
||||
proposedWereValid = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
valid.Add(mod);
|
||||
}
|
||||
|
||||
return proposedWereValid;
|
||||
|
Reference in New Issue
Block a user