mirror of
https://github.com/ppy/osu.git
synced 2025-03-04 03:13:11 +08:00
Merge branch 'master' into fix-conversion-hitwindows
This commit is contained in:
commit
8e784d9104
13
osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
Normal file
13
osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -99,6 +99,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
protected override void UpdateState(ArmedState state)
|
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()
|
protected override void Update()
|
||||||
|
@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Mods;
|
|||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Scoring
|
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||||
{
|
{
|
||||||
public class OsuPerformanceCalculator : PerformanceCalculator
|
public class OsuPerformanceCalculator : PerformanceCalculator
|
||||||
{
|
{
|
@ -12,7 +12,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
|
||||||
using osu.Game.Rulesets.Osu.Edit;
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Replays;
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
|
@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
|
||||||
|
|
||||||
[NonParallelizable]
|
[NonParallelizable]
|
||||||
[TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2152")]
|
[TestCase("basic")]
|
||||||
[TestCase("slider-generating-drumroll", false)]
|
[TestCase("slider-generating-drumroll")]
|
||||||
public new void Test(string name)
|
public new void Test(string name)
|
||||||
{
|
{
|
||||||
base.Test(name);
|
base.Test(name);
|
||||||
|
@ -8,7 +8,6 @@ using osu.Game.Rulesets.Taiko.Objects;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.IO.Serialization;
|
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
|
||||||
@ -51,8 +50,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original)
|
protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original)
|
||||||
{
|
{
|
||||||
// Rewrite the beatmap info to add the slider velocity multiplier
|
// Rewrite the beatmap info to add the slider velocity multiplier
|
||||||
BeatmapInfo info = original.BeatmapInfo.DeepClone();
|
original.BeatmapInfo = original.BeatmapInfo.Clone();
|
||||||
info.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier;
|
original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone();
|
||||||
|
original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier;
|
||||||
|
|
||||||
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original);
|
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original);
|
||||||
|
|
||||||
@ -98,12 +98,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
double distance = distanceData.Distance * spans * legacy_velocity_multiplier;
|
double distance = distanceData.Distance * spans * legacy_velocity_multiplier;
|
||||||
|
|
||||||
// The velocity of the taiko hit object - calculated as the velocity of a drum roll
|
// 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
|
// The duration of the taiko hit object
|
||||||
double taikoDuration = distance / taikoVelocity;
|
double taikoDuration = distance / taikoVelocity;
|
||||||
|
|
||||||
// The velocity of the osu! hit object - calculated as the velocity of a slider
|
// 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
|
// The duration of the osu! hit object
|
||||||
double osuDuration = distance / osuVelocity;
|
double osuDuration = distance / osuVelocity;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
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)
|
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||||
{
|
{
|
||||||
// Fill our custom DifficultyHitObject class, that carries additional information
|
// 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;
|
double starRating = calculateDifficulty() * star_scaling_factor;
|
||||||
|
|
||||||
if (categoryDifficulty != null)
|
if (categoryDifficulty != null)
|
||||||
{
|
categoryDifficulty["Strain"] = starRating;
|
||||||
categoryDifficulty.Add("Strain", starRating);
|
|
||||||
categoryDifficulty.Add("Hit window 300", 35 /*HitObjectManager.HitWindow300*/ / TimeRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return starRating;
|
return starRating;
|
||||||
}
|
}
|
||||||
|
111
osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
Normal file
111
osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ using osu.Game.Rulesets.Replays.Types;
|
|||||||
using osu.Game.Rulesets.Taiko.Replays;
|
using osu.Game.Rulesets.Taiko.Replays;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty;
|
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 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;
|
public override int? LegacyID => 1;
|
||||||
|
|
||||||
|
27
osu.Game.Tests/Visual/TestCaseMultiHeader.cs
Normal file
27
osu.Game.Tests/Visual/TestCaseMultiHeader.cs
Normal 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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
osu.Game.Tests/Visual/TestCaseMultiScreen.cs
Normal file
21
osu.Game.Tests/Visual/TestCaseMultiScreen.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,6 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.IO.Serialization;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Game.IO.Serialization.Converters;
|
using osu.Game.IO.Serialization.Converters;
|
||||||
|
|
||||||
@ -55,17 +54,11 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
IBeatmap IBeatmap.Clone() => Clone();
|
IBeatmap IBeatmap.Clone() => Clone();
|
||||||
|
|
||||||
public Beatmap<T> Clone()
|
public Beatmap<T> Clone() => (Beatmap<T>)MemberwiseClone();
|
||||||
{
|
|
||||||
var newInstance = (Beatmap<T>)MemberwiseClone();
|
|
||||||
newInstance.BeatmapInfo = BeatmapInfo.DeepClone();
|
|
||||||
|
|
||||||
return newInstance;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Beatmap : Beatmap<HitObject>
|
public class Beatmap : Beatmap<HitObject>
|
||||||
{
|
{
|
||||||
public Beatmap Clone() => (Beatmap)base.Clone();
|
public new Beatmap Clone() => (Beatmap)base.Clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,8 +53,6 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
var beatmap = CreateBeatmap();
|
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.BeatmapInfo = original.BeatmapInfo;
|
||||||
beatmap.ControlPointInfo = original.ControlPointInfo;
|
beatmap.ControlPointInfo = original.ControlPointInfo;
|
||||||
beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList();
|
beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList();
|
||||||
|
@ -32,6 +32,11 @@ namespace osu.Game.Beatmaps
|
|||||||
public double SliderMultiplier { get; set; } = 1;
|
public double SliderMultiplier { get; set; } = 1;
|
||||||
public double SliderTickRate { 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>
|
/// <summary>
|
||||||
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
|
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -143,5 +143,10 @@ namespace osu.Game.Beatmaps
|
|||||||
public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
|
public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
|
||||||
BeatmapSet.Hash == other.BeatmapSet.Hash &&
|
BeatmapSet.Hash == other.BeatmapSet.Hash &&
|
||||||
(Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile;
|
(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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 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>
|
/// <summary>
|
||||||
/// Creates the default <see cref="JsonSerializerSettings"/> that should be used for all <see cref="IJsonSerializable"/>s.
|
/// Creates the default <see cref="JsonSerializerSettings"/> that should be used for all <see cref="IJsonSerializable"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
@ -56,10 +57,10 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public HitWindows HitWindows { get; set; }
|
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]
|
[JsonIgnore]
|
||||||
public IReadOnlyList<HitObject> NestedHitObjects => nestedHitObjects;
|
public IReadOnlyList<HitObject> NestedHitObjects => nestedHitObjects.Value;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies default values to this HitObject.
|
/// Applies default values to this HitObject.
|
||||||
@ -70,13 +71,19 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
{
|
{
|
||||||
ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
|
||||||
nestedHitObjects.Clear();
|
if (nestedHitObjects.IsValueCreated)
|
||||||
|
nestedHitObjects.Value.Clear();
|
||||||
|
|
||||||
CreateNestedHitObjects();
|
CreateNestedHitObjects();
|
||||||
nestedHitObjects.ForEach(h =>
|
|
||||||
|
if (nestedHitObjects.IsValueCreated)
|
||||||
{
|
{
|
||||||
h.HitWindows = HitWindows;
|
nestedHitObjects.Value.ForEach(h =>
|
||||||
h.ApplyDefaults(controlPointInfo, difficulty);
|
{
|
||||||
});
|
h.HitWindows = HitWindows;
|
||||||
|
h.ApplyDefaults(controlPointInfo, difficulty);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty 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>
|
/// <summary>
|
||||||
/// Creates the <see cref="HitWindows"/> for this <see cref="HitObject"/>.
|
/// Creates the <see cref="HitWindows"/> for this <see cref="HitObject"/>.
|
||||||
|
@ -8,7 +8,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
|||||||
using osu.Framework.Lists;
|
using osu.Framework.Lists;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.IO.Serialization;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Timing;
|
using osu.Game.Rulesets.Timing;
|
||||||
@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
if (index < 0)
|
if (index < 0)
|
||||||
return new MultiplierControlPoint(time);
|
return new MultiplierControlPoint(time);
|
||||||
|
|
||||||
return new MultiplierControlPoint(time, DefaultControlPoints[index].DeepClone());
|
return new MultiplierControlPoint(time, DefaultControlPoints[index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ using osu.Game.Screens.Backgrounds;
|
|||||||
using osu.Game.Screens.Charts;
|
using osu.Game.Screens.Charts;
|
||||||
using osu.Game.Screens.Direct;
|
using osu.Game.Screens.Direct;
|
||||||
using osu.Game.Screens.Edit;
|
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.Select;
|
||||||
using osu.Game.Screens.Tournament;
|
using osu.Game.Screens.Tournament;
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
OnDirect = delegate { Push(new OnlineListing()); },
|
OnDirect = delegate { Push(new OnlineListing()); },
|
||||||
OnEdit = delegate { Push(new Editor()); },
|
OnEdit = delegate { Push(new Editor()); },
|
||||||
OnSolo = delegate { Push(consumeSongSelect()); },
|
OnSolo = delegate { Push(consumeSongSelect()); },
|
||||||
OnMulti = delegate { Push(new Lobby()); },
|
OnMulti = delegate { Push(new Multiplayer()); },
|
||||||
OnExit = Exit,
|
OnExit = Exit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
112
osu.Game/Screens/Multi/Header.cs
Normal file
112
osu.Game/Screens/Multi/Header.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
osu.Game/Screens/Multi/Multiplayer.cs
Normal file
100
osu.Game/Screens/Multi/Multiplayer.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,12 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
protected override bool OnSelectionFinalised()
|
protected override bool OnSelectionFinalised()
|
||||||
{
|
{
|
||||||
Exit();
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
// needs to be scheduled else we enter an infinite feedback loop.
|
||||||
|
if (IsCurrentScreen) Exit();
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user