1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-20 06:12:55 +08:00

Merge remote-tracking branch 'origin/master' into editor-timeline-rework

This commit is contained in:
smoogipoo 2018-05-22 18:30:29 +09:00
commit 1dfa3ff995
72 changed files with 1357 additions and 507 deletions

View File

@ -84,10 +84,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
yield break;
foreach (ManiaHitObject obj in objects)
{
obj.HitWindows = original.HitWindows;
yield return obj;
}
}
private readonly List<double> prevNoteTimes = new List<double>(max_notes_for_density);

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mania.Beatmaps;
@ -49,18 +50,17 @@ namespace osu.Game.Rulesets.Mania.Difficulty
int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7;
foreach (var hitObject in Beatmap.HitObjects)
difficultyHitObjects.Add(new ManiaHitObjectDifficulty((ManiaHitObject)hitObject, columnCount));
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
// Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change
difficultyHitObjects.AddRange(Beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime));
if (!calculateStrainValues())
return 0;
double starRating = calculateDifficulty() * star_scaling_factor;
categoryDifficulty?.Add("Strain", starRating);
if (categoryDifficulty != null)
categoryDifficulty["Strain"] = starRating;
return starRating;
}

View File

@ -0,0 +1,126 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaPerformanceCalculator : PerformanceCalculator
{
private Mod[] mods;
// Score after being scaled by non-difficulty-increasing mods
private double scaledScore;
private int countPerfect;
private int countGreat;
private int countGood;
private int countOk;
private int countMeh;
private int countMiss;
public ManiaPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
: base(ruleset, beatmap, score)
{
}
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{
mods = Score.Mods;
scaledScore = Score.TotalScore;
countPerfect = Convert.ToInt32(Score.Statistics[HitResult.Perfect]);
countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
countOk = Convert.ToInt32(Score.Statistics[HitResult.Ok]);
countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
if (mods.Any(m => !m.Ranked))
return 0;
IEnumerable<Mod> scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease);
double scoreMultiplier = 1.0;
foreach (var m in mods.Where(m => !scoreIncreaseMods.Contains(m)))
scoreMultiplier *= m.ScoreMultiplier;
// Scale score up, so it's comparable to other keymods
scaledScore *= 1.0 / 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))
multiplier *= 0.9;
if (mods.Any(m => m is ModEasy))
multiplier *= 0.5;
double strainValue = computeStrainValue();
double accValue = computeAccuracyValue(strainValue);
double totalValue =
Math.Pow(
Math.Pow(strainValue, 1.1) +
Math.Pow(accValue, 1.1), 1.0 / 1.1
) * multiplier;
if (categoryDifficulty != null)
{
categoryDifficulty["Strain"] = strainValue;
categoryDifficulty["Accuracy"] = accValue;
}
return totalValue;
}
private double computeStrainValue()
{
// Obtain strain difficulty
double strainValue = Math.Pow(5 * Math.Max(1, Attributes["Strain"] / 0.2) - 4.0, 2.2) / 135.0;
// Longer maps are worth more
strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
if (scaledScore <= 500000)
strainValue = 0;
else if (scaledScore <= 600000)
strainValue *= (scaledScore - 500000) / 100000 * 0.3;
else if (scaledScore <= 700000)
strainValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25;
else if (scaledScore <= 800000)
strainValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20;
else if (scaledScore <= 900000)
strainValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15;
else
strainValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1;
return strainValue;
}
private double computeAccuracyValue(double strainValue)
{
double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate;
if (hitWindowGreat <= 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 - (hitWindowGreat - 34) * 0.006667)
* strainValue
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
// accuracyValue *= Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
return accuracyValue;
}
private double totalHits => countPerfect + countOk + countGreat + countGood + countMeh + countMiss;
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements
{
public class HoldNoteJudgement : ManiaJudgement
{
public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result) => 0;
}
}

View File

@ -18,6 +18,7 @@ using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania
{
@ -25,6 +26,7 @@ namespace osu.Game.Rulesets.Mania
{
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score);
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
{

View File

@ -99,6 +99,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void UpdateState(ArmedState state)
{
switch (state)
{
case ArmedState.Hit:
// Good enough for now, we just want them to have a lifetime end
this.Delay(2000).Expire();
break;
}
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (tail.AllJudged)
AddJudgement(new HoldNoteJudgement { Result = HitResult.Perfect });
}
protected override void Update()
@ -191,6 +204,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
private class DrawableTailNote : DrawableNote
{
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
/// </summary>
private const double release_window_lenience = 1.5;
private readonly DrawableHoldNote holdNote;
public DrawableTailNote(DrawableHoldNote holdNote, ManiaAction action)
@ -203,6 +223,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
// Factor in the release lenience
timeOffset /= release_window_lenience;
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))

View File

@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Objects
/// <summary>
/// The tail note of the hold.
/// </summary>
public readonly Note Tail = new TailNote();
public readonly Note Tail = new Note();
/// <summary>
/// The time between ticks of this hold.
@ -94,25 +94,5 @@ namespace osu.Game.Rulesets.Mania.Objects
});
}
}
/// <summary>
/// The tail of the hold note.
/// </summary>
private class TailNote : Note
{
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
/// </summary>
private const double release_window_lenience = 1.5;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
HitWindows *= release_window_lenience;
}
}
}
}

View File

@ -1,8 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects.Types;
using osu.Game.Rulesets.Objects;
@ -12,12 +10,6 @@ namespace osu.Game.Rulesets.Mania.Objects
{
public virtual int Column { get; set; }
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
HitWindows.AllowsPerfect = true;
HitWindows.AllowsOk = true;
}
protected override HitWindows CreateHitWindows() => new ManiaHitWindows();
}
}

View File

@ -3,11 +3,12 @@
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Objects.Legacy.Mania
namespace osu.Game.Rulesets.Mania.Objects
{
public class ConvertHitWindows : HitWindows
public class ManiaHitWindows : HitWindows
{
private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
{
@ -21,6 +22,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public override void SetDifficulty(double difficulty)
{
AllowsPerfect = true;
AllowsOk = true;
Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);

View File

@ -40,8 +40,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount,
Position = positionData?.Position ?? Vector2.Zero,
NewCombo = comboData?.NewCombo ?? false,
HitWindows = original.HitWindows
NewCombo = comboData?.NewCombo ?? false
};
}
else if (endTimeData != null)
@ -51,8 +50,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
StartTime = original.StartTime,
Samples = original.Samples,
EndTime = endTimeData.EndTime,
Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2,
HitWindows = original.HitWindows
Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2
};
}
else
@ -62,8 +60,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
StartTime = original.StartTime,
Samples = original.Samples,
Position = positionData?.Position ?? Vector2.Zero,
NewCombo = comboData?.NewCombo ?? false,
HitWindows = original.HitWindows
NewCombo = comboData?.NewCombo ?? false
};
}
}

View File

@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring
namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuPerformanceCalculator : PerformanceCalculator
{

View File

@ -71,5 +71,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public virtual void OffsetPosition(Vector2 offset) => Position += offset;
protected override HitWindows CreateHitWindows() => new OsuHitWindows();
}
}

View File

@ -3,11 +3,12 @@
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Objects.Legacy.Osu
namespace osu.Game.Rulesets.Osu.Objects
{
public class ConvertHitWindows : HitWindows
public class OsuHitWindows : HitWindows
{
private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
{

View File

@ -12,7 +12,6 @@ using osu.Framework.Graphics;
using osu.Game.Overlays.Settings;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Replays;

View File

@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
[NonParallelizable]
[TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2152")]
[TestCase("slider-generating-drumroll", false)]
[TestCase("basic")]
[TestCase("slider-generating-drumroll")]
public new void Test(string name)
{
base.Test(name);

View File

@ -8,7 +8,6 @@ using osu.Game.Rulesets.Taiko.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.IO.Serialization;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
@ -51,8 +50,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original)
{
// Rewrite the beatmap info to add the slider velocity multiplier
BeatmapInfo info = original.BeatmapInfo.DeepClone();
info.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier;
original.BeatmapInfo = original.BeatmapInfo.Clone();
original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone();
original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier;
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original);
@ -98,12 +98,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
double distance = distanceData.Distance * spans * legacy_velocity_multiplier;
// The velocity of the taiko hit object - calculated as the velocity of a drum roll
double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
// The duration of the taiko hit object
double taikoDuration = distance / taikoVelocity;
// The velocity of the osu! hit object - calculated as the velocity of a slider
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;
@ -132,8 +132,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
StartTime = j,
Samples = currentSamples,
IsStrong = strong,
HitWindows = obj.HitWindows
IsStrong = strong
};
}
else
@ -142,8 +141,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
StartTime = j,
Samples = currentSamples,
IsStrong = strong,
HitWindows = obj.HitWindows
IsStrong = strong
};
}
@ -158,8 +156,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
Samples = obj.Samples,
IsStrong = strong,
Duration = taikoDuration,
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4,
HitWindows = obj.HitWindows
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
};
}
}
@ -173,8 +170,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
Samples = obj.Samples,
IsStrong = strong,
Duration = endTimeData.Duration,
RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier),
HitWindows = obj.HitWindows
RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier)
};
}
else
@ -187,8 +183,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
StartTime = obj.StartTime,
Samples = obj.Samples,
IsStrong = strong,
HitWindows = obj.HitWindows
IsStrong = strong
};
}
else
@ -197,8 +192,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
StartTime = obj.StartTime,
Samples = obj.Samples,
IsStrong = strong,
HitWindows = obj.HitWindows
IsStrong = strong
};
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty
@ -35,6 +36,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
}
public TaikoDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
: base(beatmap, mods)
{
}
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{
// Fill our custom DifficultyHitObject class, that carries additional information
@ -51,10 +57,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double starRating = calculateDifficulty() * star_scaling_factor;
if (categoryDifficulty != null)
{
categoryDifficulty.Add("Strain", starRating);
categoryDifficulty.Add("Hit window 300", 35 /*HitObjectManager.HitWindow300*/ / TimeRate);
}
categoryDifficulty["Strain"] = starRating;
return starRating;
}

View File

@ -0,0 +1,111 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
public class TaikoPerformanceCalculator : PerformanceCalculator
{
private readonly int beatmapMaxCombo;
private Mod[] mods;
private int countGreat;
private int countGood;
private int countMeh;
private int countMiss;
public TaikoPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
: base(ruleset, beatmap, score)
{
beatmapMaxCombo = beatmap.HitObjects.Count(h => h is Hit);
}
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{
mods = Score.Mods;
countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
return 0;
// Custom multipliers for NoFail and SpunOut.
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))
multiplier *= 0.90;
if (mods.Any(m => m is ModHidden))
multiplier *= 1.10;
double strainValue = computeStrainValue();
double accuracyValue = computeAccuracyValue();
double totalValue =
Math.Pow(
Math.Pow(strainValue, 1.1) +
Math.Pow(accuracyValue, 1.1), 1.0 / 1.1
) * multiplier;
if (categoryDifficulty != null)
{
categoryDifficulty["Strain"] = strainValue;
categoryDifficulty["Accuracy"] = accuracyValue;
}
return totalValue;
}
private double computeStrainValue()
{
double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes["Strain"] / 0.0075) - 4.0, 2.0) / 100000.0;
// Longer maps are worth more
double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0);
strainValue *= lengthBonus;
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
strainValue *= Math.Pow(0.985, countMiss);
// Combo scaling
if (beatmapMaxCombo > 0)
strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(beatmapMaxCombo, 0.5), 1.0);
if (mods.Any(m => m is ModHidden))
strainValue *= 1.025;
if (mods.Any(m => m is ModFlashlight))
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
strainValue *= 1.05 * lengthBonus;
// Scale the speed value with accuracy _slightly_
return strainValue * Score.Accuracy;
}
private double computeAccuracyValue()
{
double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate;
if (hitWindowGreat <= 0)
return 0;
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accValue = Math.Pow(150.0 / hitWindowGreat, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
}
private int totalHits => countGreat + countGood + countMeh + countMiss;
}
}

View File

@ -27,5 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// Strong hit objects give more points for hitting the hit object with both keys.
/// </summary>
public bool IsStrong;
protected override HitWindows CreateHitWindows() => new TaikoHitWindows();
}
}

View File

@ -3,11 +3,12 @@
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Objects.Legacy.Taiko
namespace osu.Game.Rulesets.Taiko.Objects
{
public class ConvertHitWindows : HitWindows
public class TaikoHitWindows : HitWindows
{
private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
{

View File

@ -14,6 +14,7 @@ using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Difficulty;
@ -144,7 +145,9 @@ namespace osu.Game.Rulesets.Taiko
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap);
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap, mods);
public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new TaikoPerformanceCalculator(this, beatmap, score);
public override int? LegacyID => 1;

View File

@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Screens.Multi.Components;
@ -111,6 +112,7 @@ namespace osu.Game.Tests.Visual
}
});
AddStep(@"select", () => first.State = SelectionState.Selected);
AddStep(@"change title", () => first.Room.Name.Value = @"I Changed Name");
AddStep(@"change host", () => first.Room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } });
AddStep(@"change status", () => first.Room.Status.Value = new RoomStatusPlaying());
@ -121,6 +123,7 @@ namespace osu.Game.Tests.Visual
new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 1254 } } },
new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 123189 } } },
});
AddStep(@"deselect", () => first.State = SelectionState.NotSelected);
}
[BackgroundDependencyLoader]

View File

@ -0,0 +1,27 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Screens.Multi;
using osu.Game.Screens.Multi.Screens;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseMultiHeader : OsuTestCase
{
public TestCaseMultiHeader()
{
Lobby lobby;
Children = new Drawable[]
{
lobby = new Lobby
{
Padding = new MarginPadding { Top = Header.HEIGHT },
},
new Header(lobby),
};
}
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Game.Screens.Multi;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseMultiScreen : OsuTestCase
{
public TestCaseMultiScreen()
{
Multiplayer multi = new Multiplayer();
AddStep(@"show", () => Add(multi));
AddWaitStep(5);
AddStep(@"exit", multi.Exit);
}
}
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual
{
base.LoadComplete();
var room = new Room
Room room = new Room
{
Name = { Value = @"My Awesome Room" },
Host = { Value = new User { Username = @"flyte", Id = 3103765, Country = new Country { FlagName = @"JP" } } },
@ -71,9 +71,13 @@ namespace osu.Game.Tests.Visual
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Room = room,
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
});
AddStep(@"set room", () => inspector.Room = room);
AddStep(@"null room", () => inspector.Room = null);
AddStep(@"set room", () => inspector.Room = room);
AddStep(@"change title", () => room.Name.Value = @"A Better Room Than The Above");
AddStep(@"change host", () => room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } });
AddStep(@"change status", () => room.Status.Value = new RoomStatusPlaying());
@ -88,7 +92,7 @@ namespace osu.Game.Tests.Visual
AddStep(@"change room", () =>
{
var newRoom = new Room
Room newRoom = new Room
{
Name = { Value = @"My New, Better Than Ever Room" },
Host = { Value = new User { Username = @"Angelsim", Id = 1777162, Country = new Country { FlagName = @"KR" } } },

View File

@ -0,0 +1,145 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens;
using OpenTK;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseScreenBreadcrumbControl : OsuTestCase
{
private readonly ScreenBreadcrumbControl breadcrumbs;
private Screen currentScreen, changedScreen;
public TestCaseScreenBreadcrumbControl()
{
TestScreen startScreen;
OsuSpriteText titleText;
Children = new Drawable[]
{
currentScreen = startScreen = new TestScreenOne(),
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(10),
Children = new Drawable[]
{
breadcrumbs = new ScreenBreadcrumbControl(startScreen)
{
RelativeSizeAxes = Axes.X,
},
titleText = new OsuSpriteText(),
},
},
};
breadcrumbs.Current.ValueChanged += s =>
{
titleText.Text = $"Changed to {s.ToString()}";
changedScreen = s;
};
breadcrumbs.Current.TriggerChange();
assertCurrent();
pushNext();
assertCurrent();
pushNext();
assertCurrent();
AddStep(@"make start current", () =>
{
startScreen.MakeCurrent();
currentScreen = startScreen;
});
assertCurrent();
pushNext();
AddAssert(@"only 2 items", () => breadcrumbs.Items.Count() == 2);
AddStep(@"exit current", () => changedScreen.Exit());
AddAssert(@"current screen is first", () => startScreen == changedScreen);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
breadcrumbs.StripColour = colours.Blue;
}
private void pushNext() => AddStep(@"push next screen", () => currentScreen = ((TestScreen)currentScreen).PushNext());
private void assertCurrent() => AddAssert(@"changedScreen correct", () => currentScreen == changedScreen);
private abstract class TestScreen : OsuScreen
{
protected abstract string Title { get; }
protected abstract string NextTitle { get; }
protected abstract TestScreen CreateNextScreen();
public override string ToString() => Title;
public TestScreen PushNext()
{
TestScreen screen = CreateNextScreen();
Push(screen);
return screen;
}
protected TestScreen()
{
Child = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(10),
Children = new Drawable[]
{
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = Title,
},
new TriangleButton
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 100,
Text = $"Push {NextTitle}",
Action = () => PushNext(),
},
},
};
}
}
private class TestScreenOne : TestScreen
{
protected override string Title => @"Screen One";
protected override string NextTitle => @"Two";
protected override TestScreen CreateNextScreen() => new TestScreenTwo();
}
private class TestScreenTwo : TestScreen
{
protected override string Title => @"Screen Two";
protected override string NextTitle => @"One";
protected override TestScreen CreateNextScreen() => new TestScreenOne();
}
}
}

View File

@ -6,7 +6,6 @@ using osu.Game.Rulesets.Objects;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.IO.Serialization;
using Newtonsoft.Json;
using osu.Game.IO.Serialization.Converters;
@ -55,17 +54,11 @@ namespace osu.Game.Beatmaps
IBeatmap IBeatmap.Clone() => Clone();
public Beatmap<T> Clone()
{
var newInstance = (Beatmap<T>)MemberwiseClone();
newInstance.BeatmapInfo = BeatmapInfo.DeepClone();
return newInstance;
}
public Beatmap<T> Clone() => (Beatmap<T>)MemberwiseClone();
}
public class Beatmap : Beatmap<HitObject>
{
public Beatmap Clone() => (Beatmap)base.Clone();
public new Beatmap Clone() => (Beatmap)base.Clone();
}
}

View File

@ -53,8 +53,6 @@ namespace osu.Game.Beatmaps
{
var beatmap = CreateBeatmap();
// todo: this *must* share logic (or directly use) Beatmap<T>'s constructor.
// right now this isn't easily possible due to generic entanglement.
beatmap.BeatmapInfo = original.BeatmapInfo;
beatmap.ControlPointInfo = original.ControlPointInfo;
beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList();

View File

@ -32,6 +32,11 @@ namespace osu.Game.Beatmaps
public double SliderMultiplier { get; set; } = 1;
public double SliderTickRate { get; set; } = 1;
/// <summary>
/// Returns a shallow-clone of this <see cref="BeatmapDifficulty"/>.
/// </summary>
public BeatmapDifficulty Clone() => (BeatmapDifficulty)MemberwiseClone();
/// <summary>
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
/// </summary>

View File

@ -143,5 +143,10 @@ namespace osu.Game.Beatmaps
public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
BeatmapSet.Hash == other.BeatmapSet.Hash &&
(Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile;
/// <summary>
/// Returns a shallow-clone of this <see cref="BeatmapInfo"/>.
/// </summary>
public BeatmapInfo Clone() => (BeatmapInfo)MemberwiseClone();
}
}

View File

@ -54,9 +54,11 @@ namespace osu.Game.Beatmaps.Formats
base.ParseStreamInto(stream, beatmap);
// objects may be out of order *only* if a user has manually edited an .osu file.
// unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828).
this.beatmap.HitObjects.Sort((x, y) => x.StartTime.CompareTo(y.StartTime));
// Objects may be out of order *only* if a user has manually edited an .osu file.
// Unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828).
// OrderBy is used to guarantee that the parsing order of hitobjects with equal start times is maintained (stably-sorted)
// The parsing order of hitobjects matters in mania difficulty calculation
this.beatmap.HitObjects = this.beatmap.HitObjects.OrderBy(h => h.StartTime).ToList();
foreach (var hitObject in this.beatmap.HitObjects)
hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty);

View File

@ -107,8 +107,14 @@ namespace osu.Game.Beatmaps
IBeatmap converted = converter.Convert();
// Apply difficulty mods
foreach (var mod in Mods.Value.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty);
if (Mods.Value.Any(m => m is IApplicableToDifficulty))
{
converted.BeatmapInfo = converted.BeatmapInfo.Clone();
converted.BeatmapInfo.BaseDifficulty = converted.BeatmapInfo.BaseDifficulty.Clone();
foreach (var mod in Mods.Value.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty);
}
// Post-process
rulesetInstance.CreateBeatmapProcessor(converted)?.PostProcess();

View File

@ -7,6 +7,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using OpenTK;
using osu.Framework.Configuration;
namespace osu.Game.Graphics.Containers
{
@ -15,9 +16,14 @@ namespace osu.Game.Graphics.Containers
private SampleChannel samplePopIn;
private SampleChannel samplePopOut;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
private readonly BindableBool allowOpeningOverlays = new BindableBool(true);
[BackgroundDependencyLoader(true)]
private void load(OsuGame osuGame, AudioManager audio)
{
if (osuGame != null)
allowOpeningOverlays.BindTo(osuGame.AllowOpeningOverlays);
samplePopIn = audio.Sample.Get(@"UI/overlay-pop-in");
samplePopOut = audio.Sample.Get(@"UI/overlay-pop-out");
@ -46,15 +52,20 @@ namespace osu.Game.Graphics.Containers
private void onStateChanged(Visibility visibility)
{
switch (visibility)
if (allowOpeningOverlays)
{
case Visibility.Visible:
samplePopIn?.Play();
break;
case Visibility.Hidden:
samplePopOut?.Play();
break;
switch (visibility)
{
case Visibility.Visible:
samplePopIn?.Play();
break;
case Visibility.Hidden:
samplePopOut?.Play();
break;
}
}
else
State = Visibility.Hidden;
}
}
}

View File

@ -0,0 +1,54 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Screens;
namespace osu.Game.Graphics.UserInterface
{
/// <summary>
/// A <see cref="BreadcrumbControl"/> which follows the active screen (and allows navigation) in a <see cref="Screen"/> stack.
/// </summary>
public class ScreenBreadcrumbControl : BreadcrumbControl<Screen>
{
private Screen last;
public ScreenBreadcrumbControl(Screen initialScreen)
{
Current.ValueChanged += newScreen =>
{
if (last != newScreen && !newScreen.IsCurrentScreen)
newScreen.MakeCurrent();
};
onPushed(initialScreen);
}
private void screenChanged(Screen newScreen)
{
if (newScreen == null) return;
if (last != null)
{
last.Exited -= screenChanged;
last.ModePushed -= onPushed;
}
last = newScreen;
newScreen.Exited += screenChanged;
newScreen.ModePushed += onPushed;
Current.Value = newScreen;
}
private void onPushed(Screen screen)
{
Items.ToList().SkipWhile(i => i != Current.Value).Skip(1).ForEach(RemoveItem);
AddItem(screen);
screenChanged(screen);
}
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Graphics.UserInterface
{
public enum SelectionState
{
NotSelected,
Selected
}
}

View File

@ -18,8 +18,6 @@ namespace osu.Game.IO.Serialization
public static void DeserializeInto<T>(this string objString, T target) => JsonConvert.PopulateObject(objString, target, CreateGlobalSettings());
public static T DeepClone<T>(this T obj) where T : IJsonSerializable => Deserialize<T>(Serialize(obj));
/// <summary>
/// Creates the default <see cref="JsonSerializerSettings"/> that should be used for all <see cref="IJsonSerializable"/>s.
/// </summary>

View File

@ -37,6 +37,9 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume),
new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume),
new KeyBinding(InputKey.F4, GlobalAction.ToggleMute),
new KeyBinding(InputKey.Escape, GlobalAction.Back),
new KeyBinding(InputKey.MouseButton1, GlobalAction.Back)
};
public IEnumerable<KeyBinding> InGameKeyBindings => new[]
@ -80,5 +83,8 @@ namespace osu.Game.Input.Bindings
TakeScreenshot,
[Description("Toggle gameplay mouse buttons")]
ToggleGameplayMouseButtons,
[Description("Go back")]
Back
}
}

View File

@ -77,7 +77,8 @@ namespace osu.Game
public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight;
public readonly BindableBool ShowOverlays = new BindableBool();
public readonly BindableBool HideOverlaysOnEnter = new BindableBool();
public readonly BindableBool AllowOpeningOverlays = new BindableBool(true);
private OsuScreen screenStack;
@ -367,12 +368,12 @@ namespace osu.Game
settings.StateChanged += _ => updateScreenOffset();
notifications.StateChanged += _ => updateScreenOffset();
notifications.Enabled.BindTo(ShowOverlays);
notifications.Enabled.BindTo(AllowOpeningOverlays);
ShowOverlays.ValueChanged += show =>
HideOverlaysOnEnter.ValueChanged += hide =>
{
//central game screen change logic.
if (!show)
if (hide)
{
hideAllOverlays();
musicController.State = Visibility.Hidden;

View File

@ -6,9 +6,11 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Input.Bindings;
using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections;
using osu.Game.Screens.Ranking;
@ -96,7 +98,7 @@ namespace osu.Game.Overlays
});
}
private class BackButton : OsuClickableContainer
private class BackButton : OsuClickableContainer, IKeyBindingHandler<GlobalAction>
{
private AspectContainer aspect;
@ -146,6 +148,20 @@ namespace osu.Game.Overlays
aspect.ScaleTo(1, 1000, Easing.OutElastic);
return base.OnMouseUp(state, args);
}
public bool OnPressed(GlobalAction action)
{
switch (action)
{
case GlobalAction.Back:
TriggerOnClick();
return true;
}
return false;
}
public bool OnReleased(GlobalAction action) => false;
}
}
}

View File

@ -6,7 +6,9 @@ using osu.Framework.Allocation;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using OpenTK;
using OpenTK.Input;
using OpenTK.Graphics;
using osu.Framework.Configuration;
using osu.Framework.Graphics.Shapes;
@ -22,6 +24,7 @@ namespace osu.Game.Overlays.Toolbar
private readonly Drawable modeButtonLine;
private ToolbarModeButton activeButton;
private RulesetStore rulesets;
private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
public ToolbarModeSelector()
@ -67,26 +70,42 @@ namespace osu.Game.Overlays.Toolbar
[BackgroundDependencyLoader(true)]
private void load(RulesetStore rulesets, OsuGame game)
{
this.rulesets = rulesets;
foreach (var r in rulesets.AvailableRulesets)
{
modeButtons.Add(new ToolbarModeButton
{
Ruleset = r,
Action = delegate
{
ruleset.Value = r;
}
Action = delegate { ruleset.Value = r; }
});
}
ruleset.ValueChanged += rulesetChanged;
ruleset.DisabledChanged += disabledChanged;
if (game != null)
ruleset.BindTo(game.Ruleset);
else
ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault();
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
base.OnKeyDown(state, args);
if (state.Keyboard.ControlPressed && !args.Repeat && args.Key >= Key.Number1 && args.Key <= Key.Number9)
{
int requested = args.Key - Key.Number1;
RulesetInfo found = rulesets.AvailableRulesets.Skip(requested).FirstOrDefault();
if (found != null)
ruleset.Value = found;
return true;
}
return false;
}
public override bool HandleKeyboardInput => !ruleset.Disabled && base.HandleKeyboardInput;
public override bool HandleMouseInput => !ruleset.Disabled && base.HandleMouseInput;

View File

@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Difficulty
private readonly Dictionary<string, double> attributes = new Dictionary<string, double>();
protected IDictionary<string, double> Attributes => attributes;
protected readonly Ruleset Ruleset;
protected readonly IBeatmap Beatmap;
protected readonly Score Score;
@ -23,9 +24,9 @@ namespace osu.Game.Rulesets.Difficulty
protected PerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
{
Score = score;
Ruleset = ruleset;
Beatmap = beatmap;
Score = score;
var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods);
diffCalc.Calculate(attributes);

View File

@ -6,6 +6,7 @@ using osu.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Input;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
@ -137,10 +138,4 @@ namespace osu.Game.Rulesets.Edit
/// </summary>
public virtual Quad SelectionQuad => ScreenSpaceDrawQuad;
}
public enum SelectionState
{
NotSelected,
Selected
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Framework.Extensions.IEnumerableExtensions;
@ -56,10 +57,10 @@ namespace osu.Game.Rulesets.Objects
/// </summary>
public HitWindows HitWindows { get; set; }
private readonly SortedList<HitObject> nestedHitObjects = new SortedList<HitObject>((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
private readonly Lazy<SortedList<HitObject>> nestedHitObjects = new Lazy<SortedList<HitObject>>(() => new SortedList<HitObject>((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)));
[JsonIgnore]
public IReadOnlyList<HitObject> NestedHitObjects => nestedHitObjects;
public IReadOnlyList<HitObject> NestedHitObjects => nestedHitObjects.Value;
/// <summary>
/// Applies default values to this HitObject.
@ -70,13 +71,19 @@ namespace osu.Game.Rulesets.Objects
{
ApplyDefaultsToSelf(controlPointInfo, difficulty);
nestedHitObjects.Clear();
if (nestedHitObjects.IsValueCreated)
nestedHitObjects.Value.Clear();
CreateNestedHitObjects();
nestedHitObjects.ForEach(h =>
if (nestedHitObjects.IsValueCreated)
{
h.HitWindows = HitWindows;
h.ApplyDefaults(controlPointInfo, difficulty);
});
nestedHitObjects.Value.ForEach(h =>
{
h.HitWindows = HitWindows;
h.ApplyDefaults(controlPointInfo, difficulty);
});
}
}
protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
@ -96,7 +103,7 @@ namespace osu.Game.Rulesets.Objects
{
}
protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject);
protected void AddNested(HitObject hitObject) => nestedHitObjects.Value.Add(hitObject);
/// <summary>
/// Creates the <see cref="HitWindows"/> for this <see cref="HitObject"/>.

View File

@ -135,39 +135,5 @@ namespace osu.Game.Rulesets.Objects
/// <param name="timeOffset">The time offset.</param>
/// <returns>Whether the <see cref="HitObject"/> can be hit at any point in the future from this time offset.</returns>
public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(HitResult.Meh);
/// <summary>
/// Multiplies all hit windows by a value.
/// </summary>
/// <param name="windows">The hit windows to multiply.</param>
/// <param name="value">The value to multiply each hit window by.</param>
public static HitWindows operator *(HitWindows windows, double value)
{
windows.Perfect *= value;
windows.Great *= value;
windows.Good *= value;
windows.Ok *= value;
windows.Meh *= value;
windows.Miss *= value;
return windows;
}
/// <summary>
/// Divides all hit windows by a value.
/// </summary>
/// <param name="windows">The hit windows to divide.</param>
/// <param name="value">The value to divide each hit window by.</param>
public static HitWindows operator /(HitWindows windows, double value)
{
windows.Perfect /= value;
windows.Great /= value;
windows.Good /= value;
windows.Ok /= value;
windows.Meh /= value;
windows.Miss /= value;
return windows;
}
}
}

View File

@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public bool NewCombo { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
protected override HitWindows CreateHitWindows() => null;
}
}

View File

@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public double Duration => EndTime - StartTime;
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
protected override HitWindows CreateHitWindows() => null;
}
}

View File

@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public bool NewCombo { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
protected override HitWindows CreateHitWindows() => null;
}
}

View File

@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public float X { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
protected override HitWindows CreateHitWindows() => null;
}
}

View File

@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public bool NewCombo { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
protected override HitWindows CreateHitWindows() => null;
}
}

View File

@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public bool NewCombo { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
protected override HitWindows CreateHitWindows() => null;
}
}

View File

@ -21,6 +21,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public float Y => Position.Y;
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
protected override HitWindows CreateHitWindows() => null;
}
}

View File

@ -12,6 +12,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
{
public bool NewCombo { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
protected override HitWindows CreateHitWindows() => null;
}
}

View File

@ -12,6 +12,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
{
public bool NewCombo { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
protected override HitWindows CreateHitWindows() => null;
}
}

View File

@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
public double Duration => EndTime - StartTime;
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
protected override HitWindows CreateHitWindows() => null;
}
}

View File

@ -8,7 +8,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Lists;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Timing;
@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
if (index < 0)
return new MultiplierControlPoint(time);
return new MultiplierControlPoint(time, DefaultControlPoints[index].DeepClone());
return new MultiplierControlPoint(time, DefaultControlPoints[index]);
}
}
}

View File

@ -26,7 +26,7 @@ namespace osu.Game.Screens.Edit
{
protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4");
public override bool ShowOverlaysOnEnter => false;
protected override bool HideOverlaysOnEnter => true;
public override bool AllowBeatmapRulesetChange => false;
private Box bottomBackground;

View File

@ -17,7 +17,9 @@ namespace osu.Game.Screens
{
private bool showDisclaimer;
public override bool ShowOverlaysOnEnter => false;
protected override bool HideOverlaysOnEnter => true;
protected override bool AllowBackButton => false;
public Loader()
{

View File

@ -6,26 +6,29 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Input.Bindings;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio;
using osu.Framework.Configuration;
using osu.Framework.Threading;
namespace osu.Game.Screens.Menu
{
public class ButtonSystem : Container, IStateful<MenuState>
public class ButtonSystem : Container, IStateful<MenuState>, IKeyBindingHandler<GlobalAction>
{
public event Action<MenuState> StateChanged;
private readonly BindableBool showOverlays = new BindableBool();
private readonly BindableBool hideOverlaysOnEnter = new BindableBool();
private readonly BindableBool allowOpeningOverlays = new BindableBool();
public Action OnEdit;
public Action OnExit;
@ -133,7 +136,12 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader(true)]
private void load(AudioManager audio, OsuGame game)
{
if (game != null) showOverlays.BindTo(game.ShowOverlays);
if (game != null)
{
hideOverlaysOnEnter.BindTo(game.HideOverlaysOnEnter);
allowOpeningOverlays.BindTo(game.AllowOpeningOverlays);
}
sampleBack = audio.Sample.Get(@"Menu/button-back-select");
}
@ -146,7 +154,16 @@ namespace osu.Game.Screens.Menu
case Key.Space:
logo?.TriggerOnClick(state);
return true;
case Key.Escape:
}
return false;
}
public bool OnPressed(GlobalAction action)
{
switch (action)
{
case GlobalAction.Back:
switch (State)
{
case MenuState.TopLevel:
@ -155,12 +172,23 @@ namespace osu.Game.Screens.Menu
case MenuState.Play:
backButton.TriggerOnClick();
return true;
default:
return false;
}
default:
return false;
}
}
return false;
public bool OnReleased(GlobalAction action)
{
switch (action)
{
case GlobalAction.Back:
return true;
default:
return false;
}
}
private void onPlay()
@ -300,8 +328,6 @@ namespace osu.Game.Screens.Menu
logoDelayedAction = Scheduler.AddDelayed(() =>
{
showOverlays.Value = false;
logo.ClearTransforms(targetMember: nameof(Position));
logo.RelativePositionAxes = Axes.Both;
@ -329,7 +355,8 @@ namespace osu.Game.Screens.Menu
logoTracking = true;
logo.Impact();
showOverlays.Value = true;
hideOverlaysOnEnter.Value = false;
allowOpeningOverlays.Value = true;
}, 200);
break;
default:
@ -337,6 +364,7 @@ namespace osu.Game.Screens.Menu
logo.ScaleTo(0.5f, 200, Easing.OutQuint);
break;
}
break;
case MenuState.EnteringMode:
logoTracking = true;

View File

@ -18,7 +18,8 @@ namespace osu.Game.Screens.Menu
private readonly SpriteIcon icon;
private Color4 iconColour;
public override bool ShowOverlaysOnEnter => false;
protected override bool HideOverlaysOnEnter => true;
public override bool CursorVisible => false;
public Disclaimer()

View File

@ -31,7 +31,9 @@ namespace osu.Game.Screens.Menu
private SampleChannel welcome;
private SampleChannel seeya;
public override bool ShowOverlaysOnEnter => false;
protected override bool HideOverlaysOnEnter => true;
protected override bool AllowOpeningOverlays => false;
public override bool CursorVisible => false;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenEmpty();

View File

@ -14,7 +14,7 @@ using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Charts;
using osu.Game.Screens.Direct;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Multi.Screens;
using osu.Game.Screens.Multi;
using osu.Game.Screens.Select;
using osu.Game.Screens.Tournament;
@ -24,7 +24,10 @@ namespace osu.Game.Screens.Menu
{
private readonly ButtonSystem buttons;
public override bool ShowOverlaysOnEnter => buttons.State != MenuState.Initial;
protected override bool HideOverlaysOnEnter => buttons.State == MenuState.Initial;
protected override bool AllowOpeningOverlays => buttons.State != MenuState.Initial;
protected override bool AllowBackButton => buttons.State != MenuState.Initial;
private readonly BackgroundScreenDefault background;
private Screen songSelect;
@ -54,7 +57,7 @@ namespace osu.Game.Screens.Menu
OnDirect = delegate { Push(new OnlineListing()); },
OnEdit = delegate { Push(new Editor()); },
OnSolo = delegate { Push(consumeSongSelect()); },
OnMulti = delegate { Push(new Lobby()); },
OnMulti = delegate { Push(new Multiplayer()); },
OnExit = Exit,
}
}

View File

@ -1,6 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
@ -13,6 +15,7 @@ using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Users;
using OpenTK;
@ -20,20 +23,17 @@ using OpenTK.Graphics;
namespace osu.Game.Screens.Multi.Components
{
public class DrawableRoom : OsuClickableContainer
public class DrawableRoom : OsuClickableContainer, IStateful<SelectionState>
{
private const float corner_radius = 5;
private const float selection_border_width = 4;
private const float transition_duration = 100;
private const float content_padding = 10;
private const float height = 100;
private const float side_strip_width = 5;
private const float cover_width = 145;
private readonly Box sideStrip;
private readonly Container coverContainer;
private readonly OsuSpriteText name, status, beatmapTitle, beatmapDash, beatmapArtist;
private readonly FillFlowContainer<OsuSpriteText> beatmapInfoFlow;
private readonly ParticipantInfo participantInfo;
private readonly ModeTypeInfo modeTypeInfo;
private readonly Box selectionBox;
private readonly Bindable<string> nameBind = new Bindable<string>();
private readonly Bindable<User> hostBind = new Bindable<User>();
@ -42,136 +42,228 @@ namespace osu.Game.Screens.Multi.Components
private readonly Bindable<BeatmapInfo> beatmapBind = new Bindable<BeatmapInfo>();
private readonly Bindable<User[]> participantsBind = new Bindable<User[]>();
private OsuColour colours;
private LocalisationEngine localisation;
public readonly Room Room;
private SelectionState state;
public SelectionState State
{
get { return state; }
set
{
if (value == state) return;
state = value;
if (state == SelectionState.Selected)
selectionBox.FadeIn(transition_duration);
else
selectionBox.FadeOut(transition_duration);
StateChanged?.Invoke(State);
}
}
public event Action<SelectionState> StateChanged;
public DrawableRoom(Room room)
{
Room = room;
RelativeSizeAxes = Axes.X;
Height = height;
CornerRadius = 5;
Height = height + selection_border_width * 2;
CornerRadius = corner_radius + selection_border_width / 2;
Masking = true;
EdgeEffect = new EdgeEffectParameters
// create selectionBox here so State can be set before being loaded
selectionBox = new Box
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(40),
Radius = 5,
RelativeSizeAxes = Axes.Both,
Alpha = 0f,
};
Action += () => State = SelectionState.Selected;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, LocalisationEngine localisation)
{
Box sideStrip;
Container coverContainer;
OsuSpriteText name, status, beatmapTitle, beatmapDash, beatmapArtist;
ParticipantInfo participantInfo;
ModeTypeInfo modeTypeInfo;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"212121"),
},
sideStrip = new Box
{
RelativeSizeAxes = Axes.Y,
Width = side_strip_width,
},
new Container
{
Width = cover_width,
RelativeSizeAxes = Axes.Y,
Masking = true,
Margin = new MarginPadding { Left = side_strip_width },
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
coverContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
},
},
selectionBox,
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
Padding = new MarginPadding(selection_border_width),
Child = new Container
{
Vertical = content_padding,
Left = side_strip_width + cover_width + content_padding,
Right = content_padding,
},
Children = new Drawable[]
{
new FillFlowContainer
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = corner_radius,
EdgeEffect = new EdgeEffectParameters
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(5f),
Children = new Drawable[]
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(40),
Radius = 5,
},
Children = new Drawable[]
{
new Box
{
name = new OsuSpriteText
{
TextSize = 18,
},
participantInfo = new ParticipantInfo(),
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"212121"),
},
},
new FillFlowContainer
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
sideStrip = new Box
{
status = new OsuSpriteText
RelativeSizeAxes = Axes.Y,
Width = side_strip_width,
},
new Container
{
Width = cover_width,
RelativeSizeAxes = Axes.Y,
Masking = true,
Margin = new MarginPadding { Left = side_strip_width },
Children = new Drawable[]
{
TextSize = 14,
Font = @"Exo2.0-Bold",
},
beatmapInfoFlow = new FillFlowContainer<OsuSpriteText>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Children = new[]
new Box
{
beatmapTitle = new OsuSpriteText
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
coverContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
},
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
Vertical = content_padding,
Left = side_strip_width + cover_width + content_padding,
Right = content_padding,
},
Children = new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(5f),
Children = new Drawable[]
{
TextSize = 14,
Font = @"Exo2.0-BoldItalic",
name = new OsuSpriteText
{
TextSize = 18,
},
participantInfo = new ParticipantInfo(),
},
beatmapDash = new OsuSpriteText
},
new FillFlowContainer
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
TextSize = 14,
Font = @"Exo2.0-BoldItalic",
},
beatmapArtist = new OsuSpriteText
{
TextSize = 14,
Font = @"Exo2.0-RegularItalic",
status = new OsuSpriteText
{
TextSize = 14,
Font = @"Exo2.0-Bold",
},
new FillFlowContainer<OsuSpriteText>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Colour = colours.Gray9,
Direction = FillDirection.Horizontal,
Children = new[]
{
beatmapTitle = new OsuSpriteText
{
TextSize = 14,
Font = @"Exo2.0-BoldItalic",
},
beatmapDash = new OsuSpriteText
{
TextSize = 14,
Font = @"Exo2.0-BoldItalic",
},
beatmapArtist = new OsuSpriteText
{
TextSize = 14,
Font = @"Exo2.0-RegularItalic",
},
},
},
},
},
modeTypeInfo = new ModeTypeInfo
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
},
},
},
},
modeTypeInfo = new ModeTypeInfo
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
},
},
},
};
nameBind.ValueChanged += displayName;
hostBind.ValueChanged += displayUser;
typeBind.ValueChanged += displayGameType;
participantsBind.ValueChanged += displayParticipants;
nameBind.ValueChanged += n => name.Text = n;
hostBind.ValueChanged += h => participantInfo.Host = h;
typeBind.ValueChanged += m => modeTypeInfo.Type = m;
participantsBind.ValueChanged += p => participantInfo.Participants = p;
statusBind.ValueChanged += s =>
{
status.Text = s.Message;
foreach (Drawable d in new Drawable[] { selectionBox, sideStrip, status })
d.FadeColour(s.GetAppropriateColour(colours), 100);
};
beatmapBind.ValueChanged += b =>
{
modeTypeInfo.Beatmap = b;
if (b != null)
{
coverContainer.FadeIn(transition_duration);
LoadComponentAsync(new BeatmapSetCover(b.BeatmapSet)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
}, coverContainer.Add);
beatmapTitle.Current = localisation.GetUnicodePreference(b.Metadata.TitleUnicode, b.Metadata.Title);
beatmapDash.Text = @" - ";
beatmapArtist.Current = localisation.GetUnicodePreference(b.Metadata.ArtistUnicode, b.Metadata.Artist);
}
else
{
coverContainer.FadeOut(transition_duration);
beatmapTitle.Current = null;
beatmapArtist.Current = null;
beatmapTitle.Text = "Changing map";
beatmapDash.Text = beatmapArtist.Text = string.Empty;
}
};
nameBind.BindTo(Room.Name);
hostBind.BindTo(Room.Host);
@ -180,83 +272,5 @@ namespace osu.Game.Screens.Multi.Components
beatmapBind.BindTo(Room.Beatmap);
participantsBind.BindTo(Room.Participants);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, LocalisationEngine localisation)
{
this.localisation = localisation;
this.colours = colours;
beatmapInfoFlow.Colour = colours.Gray9;
//binded here instead of ctor because dependencies are needed
statusBind.ValueChanged += displayStatus;
beatmapBind.ValueChanged += displayBeatmap;
statusBind.TriggerChange();
beatmapBind.TriggerChange();
}
private void displayName(string value)
{
name.Text = value;
}
private void displayUser(User value)
{
participantInfo.Host = value;
}
private void displayStatus(RoomStatus value)
{
if (value == null) return;
status.Text = value.Message;
foreach (Drawable d in new Drawable[] { sideStrip, status })
d.FadeColour(value.GetAppropriateColour(colours), 100);
}
private void displayGameType(GameType value)
{
modeTypeInfo.Type = value;
}
private void displayBeatmap(BeatmapInfo value)
{
modeTypeInfo.Beatmap = value;
if (value != null)
{
coverContainer.FadeIn(transition_duration);
LoadComponentAsync(new BeatmapSetCover(value.BeatmapSet)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
},
coverContainer.Add);
beatmapTitle.Current = localisation.GetUnicodePreference(value.Metadata.TitleUnicode, value.Metadata.Title);
beatmapDash.Text = @" - ";
beatmapArtist.Current = localisation.GetUnicodePreference(value.Metadata.ArtistUnicode, value.Metadata.Artist);
}
else
{
coverContainer.FadeOut(transition_duration);
beatmapTitle.Current = null;
beatmapArtist.Current = null;
beatmapTitle.Text = "Changing map";
beatmapDash.Text = beatmapArtist.Text = string.Empty;
}
}
private void displayParticipants(User[] value)
{
participantInfo.Participants = value;
}
}
}

View File

@ -25,17 +25,9 @@ namespace osu.Game.Screens.Multi.Components
{
public class RoomInspector : Container
{
private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 };
private const float transition_duration = 100;
private readonly Box statusStrip;
private readonly Container coverContainer;
private readonly FillFlowContainer topFlow, participantsFlow;
private readonly ModeTypeInfo modeTypeInfo;
private readonly OsuSpriteText participants, participantsSlash, maxParticipants, name, status, beatmapTitle, beatmapDash, beatmapArtist, beatmapAuthor;
private readonly ParticipantInfo participantInfo;
private readonly ScrollContainer participantsScroll;
private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 };
private readonly Bindable<string> nameBind = new Bindable<string>();
private readonly Bindable<User> hostBind = new Bindable<User>();
private readonly Bindable<RoomStatus> statusBind = new Bindable<RoomStatus>();
@ -45,10 +37,14 @@ namespace osu.Game.Screens.Multi.Components
private readonly Bindable<User[]> participantsBind = new Bindable<User[]>();
private OsuColour colours;
private LocalisationEngine localisation;
private Box statusStrip;
private Container coverContainer;
private FillFlowContainer topFlow, participantsFlow, participantNumbersFlow, infoPanelFlow;
private OsuSpriteText name, status;
private ScrollContainer participantsScroll;
private ParticipantInfo participantInfo;
private Room room;
public Room Room
{
get { return room; }
@ -57,20 +53,36 @@ namespace osu.Game.Screens.Multi.Components
if (value == room) return;
room = value;
nameBind.BindTo(Room.Name);
hostBind.BindTo(Room.Host);
statusBind.BindTo(Room.Status);
typeBind.BindTo(Room.Type);
beatmapBind.BindTo(Room.Beatmap);
maxParticipantsBind.BindTo(Room.MaxParticipants);
participantsBind.BindTo(Room.Participants);
nameBind.UnbindBindings();
hostBind.UnbindBindings();
statusBind.UnbindBindings();
typeBind.UnbindBindings();
beatmapBind.UnbindBindings();
maxParticipantsBind.UnbindBindings();
participantsBind.UnbindBindings();
if (room != null)
{
nameBind.BindTo(room.Name);
hostBind.BindTo(room.Host);
statusBind.BindTo(room.Status);
typeBind.BindTo(room.Type);
beatmapBind.BindTo(room.Beatmap);
maxParticipantsBind.BindTo(room.MaxParticipants);
participantsBind.BindTo(room.Participants);
}
updateState();
}
}
public RoomInspector()
[BackgroundDependencyLoader]
private void load(OsuColour colours, LocalisationEngine localisation)
{
Width = 520;
RelativeSizeAxes = Axes.Y;
this.colours = colours;
ModeTypeInfo modeTypeInfo;
OsuSpriteText participants, participantsSlash, maxParticipants, beatmapTitle, beatmapDash, beatmapArtist, beatmapAuthor;
Children = new Drawable[]
{
@ -120,7 +132,7 @@ namespace osu.Game.Screens.Multi.Components
Padding = new MarginPadding(20),
Children = new Drawable[]
{
new FillFlowContainer
participantNumbersFlow = new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
@ -178,6 +190,7 @@ namespace osu.Game.Screens.Multi.Components
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
LayoutDuration = transition_duration,
Padding = contentPadding,
Spacing = new Vector2(0f, 5f),
Children = new Drawable[]
@ -187,7 +200,7 @@ namespace osu.Game.Screens.Multi.Components
TextSize = 14,
Font = @"Exo2.0-Bold",
},
new FillFlowContainer
infoPanelFlow = new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 30,
@ -229,6 +242,7 @@ namespace osu.Game.Screens.Multi.Components
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
TextSize = 14,
Colour = colours.Gray9,
},
},
},
@ -269,27 +283,68 @@ namespace osu.Game.Screens.Multi.Components
},
};
nameBind.ValueChanged += displayName;
hostBind.ValueChanged += displayUser;
typeBind.ValueChanged += displayGameType;
maxParticipantsBind.ValueChanged += displayMaxParticipants;
participantsBind.ValueChanged += displayParticipants;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, LocalisationEngine localisation)
{
this.localisation = localisation;
this.colours = colours;
beatmapAuthor.Colour = colours.Gray9;
//binded here instead of ctor because dependencies are needed
nameBind.ValueChanged += n => name.Text = n;
hostBind.ValueChanged += h => participantInfo.Host = h;
typeBind.ValueChanged += t => modeTypeInfo.Type = t;
statusBind.ValueChanged += displayStatus;
beatmapBind.ValueChanged += displayBeatmap;
statusBind.TriggerChange();
beatmapBind.TriggerChange();
beatmapBind.ValueChanged += b =>
{
modeTypeInfo.Beatmap = b;
if (b != null)
{
coverContainer.FadeIn(transition_duration);
LoadComponentAsync(new BeatmapSetCover(b.BeatmapSet)
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
}, coverContainer.Add);
beatmapTitle.Current = localisation.GetUnicodePreference(b.Metadata.TitleUnicode, b.Metadata.Title);
beatmapDash.Text = @" - ";
beatmapArtist.Current = localisation.GetUnicodePreference(b.Metadata.ArtistUnicode, b.Metadata.Artist);
beatmapAuthor.Text = $"mapped by {b.Metadata.Author}";
}
else
{
coverContainer.FadeOut(transition_duration);
beatmapTitle.Current = null;
beatmapArtist.Current = null;
beatmapTitle.Text = "Changing map";
beatmapDash.Text = beatmapArtist.Text = beatmapAuthor.Text = string.Empty;
}
};
maxParticipantsBind.ValueChanged += m =>
{
if (m == null)
{
participantsSlash.FadeOut(transition_duration);
maxParticipants.FadeOut(transition_duration);
}
else
{
participantsSlash.FadeIn(transition_duration);
maxParticipants.FadeIn(transition_duration);
maxParticipants.Text = m.ToString();
}
};
participantsBind.ValueChanged += p =>
{
participants.Text = p.Length.ToString();
participantInfo.Participants = p;
participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u));
};
updateState();
}
protected override void UpdateAfterChildren()
@ -299,84 +354,39 @@ namespace osu.Game.Screens.Multi.Components
participantsScroll.Height = DrawHeight - topFlow.DrawHeight;
}
private void displayName(string value)
private void displayStatus(RoomStatus s)
{
name.Text = value;
status.Text = s.Message;
Color4 c = s.GetAppropriateColour(colours);
statusStrip.FadeColour(c, transition_duration);
status.FadeColour(c, transition_duration);
}
private void displayUser(User value)
private void updateState()
{
participantInfo.Host = value;
}
private void displayStatus(RoomStatus value)
{
status.Text = value.Message;
foreach (Drawable d in new Drawable[] { statusStrip, status })
d.FadeColour(value.GetAppropriateColour(colours), transition_duration);
}
private void displayGameType(GameType value)
{
modeTypeInfo.Type = value;
}
private void displayBeatmap(BeatmapInfo value)
{
modeTypeInfo.Beatmap = value;
if (value != null)
{
coverContainer.FadeIn(transition_duration);
LoadComponentAsync(new BeatmapSetCover(value.BeatmapSet)
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
},
coverContainer.Add);
beatmapTitle.Current = localisation.GetUnicodePreference(value.Metadata.TitleUnicode, value.Metadata.Title);
beatmapDash.Text = @" - ";
beatmapArtist.Current = localisation.GetUnicodePreference(value.Metadata.ArtistUnicode, value.Metadata.Artist);
beatmapAuthor.Text = $"mapped by {value.Metadata.Author}";
}
else
if (Room == null)
{
coverContainer.FadeOut(transition_duration);
participantsFlow.FadeOut(transition_duration);
participantNumbersFlow.FadeOut(transition_duration);
infoPanelFlow.FadeOut(transition_duration);
name.FadeOut(transition_duration);
participantInfo.FadeOut(transition_duration);
beatmapTitle.Current = null;
beatmapArtist.Current = null;
beatmapTitle.Text = "Changing map";
beatmapDash.Text = beatmapArtist.Text = beatmapAuthor.Text = string.Empty;
}
}
private void displayMaxParticipants(int? value)
{
if (value == null)
{
participantsSlash.FadeOut(transition_duration);
maxParticipants.FadeOut(transition_duration);
displayStatus(new RoomStatusNoneSelected());
}
else
{
participantsSlash.FadeIn(transition_duration);
maxParticipants.FadeIn(transition_duration);
maxParticipants.Text = value.ToString();
}
}
participantsFlow.FadeIn(transition_duration);
participantNumbersFlow.FadeIn(transition_duration);
infoPanelFlow.FadeIn(transition_duration);
name.FadeIn(transition_duration);
participantInfo.FadeIn(transition_duration);
private void displayParticipants(User[] value)
{
participants.Text = value.Length.ToString();
participantInfo.Participants = value;
participantsFlow.ChildrenEnumerable = value.Select(u => new UserTile(u));
statusBind.TriggerChange();
beatmapBind.TriggerChange();
}
}
private class UserTile : Container, IHasTooltip
@ -407,5 +417,11 @@ namespace osu.Game.Screens.Multi.Components
};
}
}
private class RoomStatusNoneSelected : RoomStatus
{
public override string Message => @"No Room Selected";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray8;
}
}
}

View File

@ -0,0 +1,112 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
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.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.SearchableList;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Screens.Multi
{
public class Header : Container
{
public const float HEIGHT = 121;
private readonly OsuSpriteText screenTitle;
private readonly HeaderBreadcrumbControl breadcrumbs;
public Header(Screen initialScreen)
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"2f2043"),
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.BottomLeft,
Position = new Vector2(-35f, 5f),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10f, 0f),
Children = new Drawable[]
{
new SpriteIcon
{
Size = new Vector2(25),
Icon = FontAwesome.fa_osu_multi,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
new OsuSpriteText
{
Text = "multiplayer ",
TextSize = 25,
},
screenTitle = new OsuSpriteText
{
TextSize = 25,
Font = @"Exo2.0-Light",
},
},
},
},
},
breadcrumbs = new HeaderBreadcrumbControl(initialScreen)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
},
},
},
};
breadcrumbs.Current.ValueChanged += s => screenTitle.Text = s.ToString();
breadcrumbs.Current.TriggerChange();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
screenTitle.Colour = colours.Yellow;
breadcrumbs.StripColour = colours.Green;
}
private class HeaderBreadcrumbControl : ScreenBreadcrumbControl
{
public HeaderBreadcrumbControl(Screen initialScreen) : base(initialScreen)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
AccentColour = Color4.White;
}
}
}
}

View File

@ -0,0 +1,100 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
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.Backgrounds;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.Multi.Screens;
namespace osu.Game.Screens.Multi
{
public class Multiplayer : OsuScreen
{
private readonly MultiplayerWaveContainer waves;
protected override Container<Drawable> Content => waves;
public Multiplayer()
{
InternalChild = waves = new MultiplayerWaveContainer
{
RelativeSizeAxes = Axes.Both,
};
Lobby lobby;
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"3e3a44"),
},
new Triangles
{
RelativeSizeAxes = Axes.Both,
ColourLight = OsuColour.FromHex(@"3c3842"),
ColourDark = OsuColour.FromHex(@"393540"),
TriangleScale = 5,
},
},
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = Header.HEIGHT },
Child = lobby = new Lobby(),
},
new Header(lobby),
};
lobby.Exited += s => Exit();
}
protected override void OnEntering(Screen last)
{
base.OnEntering(last);
waves.Show();
}
protected override bool OnExiting(Screen next)
{
waves.Hide();
return base.OnExiting(next);
}
protected override void OnResuming(Screen last)
{
base.OnResuming(last);
waves.Show();
}
protected override void OnSuspending(Screen next)
{
base.OnSuspending(next);
waves.Hide();
}
private class MultiplayerWaveContainer : WaveContainer
{
protected override bool StartHidden => true;
public MultiplayerWaveContainer()
{
FirstWaveColour = OsuColour.FromHex(@"654d8c");
SecondWaveColour = OsuColour.FromHex(@"554075");
ThirdWaveColour = OsuColour.FromHex(@"44325e");
FourthWaveColour = OsuColour.FromHex(@"392850");
}
}
}
}

View File

@ -3,37 +3,48 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
using OpenTK;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
using osu.Game.Screens.Menu;
using osu.Framework.Input;
using OpenTK;
using OpenTK.Input;
namespace osu.Game.Screens
{
public abstract class OsuScreen : Screen
public abstract class OsuScreen : Screen, IKeyBindingHandler<GlobalAction>
{
public BackgroundScreen Background { get; private set; }
protected virtual bool AllowBackButton => true;
/// <summary>
/// Override to create a BackgroundMode for the current screen.
/// Note that the instance created may not be the used instance if it matches the BackgroundMode equality clause.
/// </summary>
protected virtual BackgroundScreen CreateBackground() => null;
protected BindableBool ShowOverlays = new BindableBool();
private readonly BindableBool hideOverlaysOnEnter = new BindableBool();
/// <summary>
/// Whether overlays should be shown when this screen is entered or resumed.
/// Whether overlays should be hidden when this screen is entered or resumed.
/// </summary>
public virtual bool ShowOverlaysOnEnter => true;
protected virtual bool HideOverlaysOnEnter => hideOverlaysOnEnter;
private readonly BindableBool allowOpeningOverlays = new BindableBool();
/// <summary>
/// Whether overlays should be able to be opened while this screen is active.
/// </summary>
protected virtual bool AllowOpeningOverlays => allowOpeningOverlays;
/// <summary>
/// Whether this <see cref="OsuScreen"/> allows the cursor to be displayed.
@ -84,12 +95,26 @@ namespace osu.Game.Screens
if (osuGame != null)
{
Ruleset.BindTo(osuGame.Ruleset);
ShowOverlays.BindTo(osuGame.ShowOverlays);
hideOverlaysOnEnter.BindTo(osuGame.HideOverlaysOnEnter);
allowOpeningOverlays.BindTo(osuGame.AllowOpeningOverlays);
}
sampleExit = audio.Sample.Get(@"UI/screen-back");
}
public bool OnPressed(GlobalAction action)
{
if (action == GlobalAction.Back && AllowBackButton)
{
Exit();
return true;
}
return false;
}
public bool OnReleased(GlobalAction action) => action == GlobalAction.Back && AllowBackButton;
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (args.Repeat || !IsCurrentScreen) return false;
@ -203,7 +228,8 @@ namespace osu.Game.Screens
if (backgroundParallaxContainer != null)
backgroundParallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * BackgroundParallaxAmount;
ShowOverlays.Value = ShowOverlaysOnEnter;
hideOverlaysOnEnter.Value = HideOverlaysOnEnter;
allowOpeningOverlays.Value = AllowOpeningOverlays;
}
private void onExitingLogo()

View File

@ -35,7 +35,7 @@ namespace osu.Game.Screens.Play
{
protected override float BackgroundParallaxAmount => 0.1f;
public override bool ShowOverlaysOnEnter => false;
protected override bool HideOverlaysOnEnter => true;
public Action RestartRequested;
@ -45,6 +45,8 @@ namespace osu.Game.Screens.Play
public bool AllowLeadIn { get; set; } = true;
public bool AllowResults { get; set; } = true;
protected override bool AllowBackButton => false;
private Bindable<bool> mouseWheelDisabled;
private Bindable<double> userAudioOffset;

View File

@ -25,8 +25,8 @@ namespace osu.Game.Screens.Play
private BeatmapMetadataDisplay info;
private bool showOverlays = true;
public override bool ShowOverlaysOnEnter => showOverlays;
private bool hideOverlays;
protected override bool HideOverlaysOnEnter => hideOverlays;
private Task loadTask;
@ -36,7 +36,7 @@ namespace osu.Game.Screens.Play
player.RestartRequested = () =>
{
showOverlays = false;
hideOverlays = true;
ValidForResume = true;
};
}

View File

@ -190,7 +190,5 @@ namespace osu.Game.Screens.Select
protected override bool OnMouseMove(InputState state) => true;
protected override bool OnClick(InputState state) => true;
protected override bool OnDragStart(InputState state) => true;
}
}

View File

@ -141,7 +141,5 @@ namespace osu.Game.Screens.Select
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
protected override bool OnClick(InputState state) => true;
protected override bool OnDragStart(InputState state) => true;
}
}

View File

@ -7,7 +7,12 @@ namespace osu.Game.Screens.Select
{
protected override bool OnSelectionFinalised()
{
Exit();
Schedule(() =>
{
// needs to be scheduled else we enter an infinite feedback loop.
if (IsCurrentScreen) Exit();
});
return true;
}
}

View File

@ -29,7 +29,7 @@ namespace osu.Game.Screens.Tournament
{
private const string results_filename = "drawings_results.txt";
public override bool ShowOverlaysOnEnter => false;
protected override bool HideOverlaysOnEnter => true;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault();