mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 09:07:52 +08:00
Merge branch 'master' into spinner-glow
This commit is contained in:
commit
874a3706bc
@ -59,7 +59,7 @@ The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of
|
|||||||
|
|
||||||
In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive.
|
In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive.
|
||||||
|
|
||||||
If you'd like to propose a subjective change to one of the visual aspects of the game, or there is a bigger task you'd like to work on, but there is no corresponding issue or discussion thread yet for it, **please open a discussion or issue first** to avoid wasted effort. This in particular applies if you want to work on [one of the available designs from the osu! public Figma library](https://www.figma.com/file/6m10GiGEncVFWmgOoSyakH/osu!-Figma-Library).
|
If you'd like to propose a subjective change to one of the visual aspects of the game, or there is a bigger task you'd like to work on, but there is no corresponding issue or discussion thread yet for it, **please open a discussion or issue first** to avoid wasted effort. This in particular applies if you want to work on [one of the available designs from the osu! Figma master library](https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Master-Library).
|
||||||
|
|
||||||
Aside from the above, below is a brief checklist of things to watch out when you're preparing your code changes:
|
Aside from the above, below is a brief checklist of things to watch out when you're preparing your code changes:
|
||||||
|
|
||||||
@ -85,4 +85,4 @@ If you're uncertain about some part of the codebase or some inner workings of th
|
|||||||
- [Development roadmap](https://github.com/orgs/ppy/projects/7/views/6): What the core team is currently working on
|
- [Development roadmap](https://github.com/orgs/ppy/projects/7/views/6): What the core team is currently working on
|
||||||
- [`ppy/osu-framework` wiki](https://github.com/ppy/osu-framework/wiki): Contains introductory information about osu!framework, the bespoke 2D game framework we use for the game
|
- [`ppy/osu-framework` wiki](https://github.com/ppy/osu-framework/wiki): Contains introductory information about osu!framework, the bespoke 2D game framework we use for the game
|
||||||
- [`ppy/osu` wiki](https://github.com/ppy/osu/wiki): Contains articles about various technical aspects of the game
|
- [`ppy/osu` wiki](https://github.com/ppy/osu/wiki): Contains articles about various technical aspects of the game
|
||||||
- [Public Figma library](https://www.figma.com/file/6m10GiGEncVFWmgOoSyakH/osu!-Figma-Library): Contains finished and draft designs for osu!
|
- [Figma master library](https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Master-Library): Contains finished and draft designs for osu!
|
||||||
|
@ -39,6 +39,8 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor();
|
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor();
|
||||||
|
|
||||||
|
public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new CatchHealthProcessor(drainStartTime);
|
||||||
|
|
||||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this);
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this);
|
||||||
|
|
||||||
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
|
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
|
||||||
|
57
osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs
Normal file
57
osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Scoring
|
||||||
|
{
|
||||||
|
public partial class CatchHealthProcessor : LegacyDrainingHealthProcessor
|
||||||
|
{
|
||||||
|
public CatchHealthProcessor(double drainStartTime)
|
||||||
|
: base(drainStartTime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<HitObject> EnumerateTopLevelHitObjects() => EnumerateHitObjects(Beatmap).Where(h => h is Fruit || h is Droplet || h is Banana);
|
||||||
|
|
||||||
|
protected override IEnumerable<HitObject> EnumerateNestedHitObjects(HitObject hitObject) => Enumerable.Empty<HitObject>();
|
||||||
|
|
||||||
|
protected override double GetHealthIncreaseFor(HitObject hitObject, HitResult result)
|
||||||
|
{
|
||||||
|
double increase = 0;
|
||||||
|
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.SmallTickMiss:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case HitResult.LargeTickMiss:
|
||||||
|
case HitResult.Miss:
|
||||||
|
return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.03, -0.125, -0.2);
|
||||||
|
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
increase = 0.0015;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
increase = 0.015;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.Great:
|
||||||
|
increase = 0.03;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.LargeBonus:
|
||||||
|
increase = 0.0025;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HpMultiplierNormal * increase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
93
osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs
Normal file
93
osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneOsuHealthProcessor
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestNoBreak()
|
||||||
|
{
|
||||||
|
OsuHealthProcessor hp = new OsuHealthProcessor(-1000);
|
||||||
|
hp.ApplyBeatmap(new Beatmap<OsuHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0 },
|
||||||
|
new HitCircle { StartTime = 2000 }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(hp.DrainRate, Is.EqualTo(1.4E-5).Within(0.1E-5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleBreak()
|
||||||
|
{
|
||||||
|
OsuHealthProcessor hp = new OsuHealthProcessor(-1000);
|
||||||
|
hp.ApplyBeatmap(new Beatmap<OsuHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0 },
|
||||||
|
new HitCircle { StartTime = 2000 }
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new BreakPeriod(500, 1500)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(hp.DrainRate, Is.EqualTo(4.3E-5).Within(0.1E-5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOverlappingBreak()
|
||||||
|
{
|
||||||
|
OsuHealthProcessor hp = new OsuHealthProcessor(-1000);
|
||||||
|
hp.ApplyBeatmap(new Beatmap<OsuHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0 },
|
||||||
|
new HitCircle { StartTime = 2000 }
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new BreakPeriod(500, 1400),
|
||||||
|
new BreakPeriod(750, 1500),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(hp.DrainRate, Is.EqualTo(4.3E-5).Within(0.1E-5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSequentialBreak()
|
||||||
|
{
|
||||||
|
OsuHealthProcessor hp = new OsuHealthProcessor(-1000);
|
||||||
|
hp.ApplyBeatmap(new Beatmap<OsuHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0 },
|
||||||
|
new HitCircle { StartTime = 2000 }
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new BreakPeriod(500, 1000),
|
||||||
|
new BreakPeriod(1000, 1500),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(hp.DrainRate, Is.EqualTo(4.3E-5).Within(0.1E-5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -175,6 +175,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
if (controlPointCount > 2 || (controlPointCount == 2 && HitObject.Path.ControlPoints.Last() != cursor))
|
if (controlPointCount > 2 || (controlPointCount == 2 && HitObject.Path.ControlPoints.Last() != cursor))
|
||||||
return base.OnDragStart(e);
|
return base.OnDragStart(e);
|
||||||
|
|
||||||
|
bSplineBuilder.AddLinearPoint(Vector2.Zero);
|
||||||
bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position);
|
bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position);
|
||||||
state = SliderPlacementState.Drawing;
|
state = SliderPlacementState.Drawing;
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,215 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Beatmaps.Timing;
|
|
||||||
using osu.Game.Rulesets.Judgements;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Scoring
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Reference implementation for osu!stable's HP drain.
|
|
||||||
/// Cannot be used for gameplay.
|
|
||||||
/// </summary>
|
|
||||||
public partial class LegacyOsuHealthProcessor : DrainingHealthProcessor
|
|
||||||
{
|
|
||||||
private const double hp_bar_maximum = 200;
|
|
||||||
private const double hp_combo_geki = 14;
|
|
||||||
private const double hp_hit_300 = 6;
|
|
||||||
private const double hp_slider_repeat = 4;
|
|
||||||
private const double hp_slider_tick = 3;
|
|
||||||
|
|
||||||
public Action<string>? OnIterationFail;
|
|
||||||
public Action<string>? OnIterationSuccess;
|
|
||||||
public bool ApplyComboEndBonus { get; set; } = true;
|
|
||||||
|
|
||||||
private double lowestHpEver;
|
|
||||||
private double lowestHpEnd;
|
|
||||||
private double lowestHpComboEnd;
|
|
||||||
private double hpRecoveryAvailable;
|
|
||||||
private double hpMultiplierNormal;
|
|
||||||
private double hpMultiplierComboEnd;
|
|
||||||
|
|
||||||
public LegacyOsuHealthProcessor(double drainStartTime)
|
|
||||||
: base(drainStartTime)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ApplyBeatmap(IBeatmap beatmap)
|
|
||||||
{
|
|
||||||
lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 195, 160, 60);
|
|
||||||
lowestHpComboEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 198, 170, 80);
|
|
||||||
lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 198, 180, 80);
|
|
||||||
hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 8, 4, 0);
|
|
||||||
|
|
||||||
base.ApplyBeatmap(beatmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ApplyResultInternal(JudgementResult result)
|
|
||||||
{
|
|
||||||
if (!IsSimulating)
|
|
||||||
throw new NotSupportedException("The legacy osu! health processor is not supported for gameplay.");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void RevertResultInternal(JudgementResult result)
|
|
||||||
{
|
|
||||||
if (!IsSimulating)
|
|
||||||
throw new NotSupportedException("The legacy osu! health processor is not supported for gameplay.");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Reset(bool storeResults)
|
|
||||||
{
|
|
||||||
hpMultiplierNormal = 1;
|
|
||||||
hpMultiplierComboEnd = 1;
|
|
||||||
|
|
||||||
base.Reset(storeResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ComputeDrainRate()
|
|
||||||
{
|
|
||||||
double testDrop = 0.05;
|
|
||||||
double currentHp;
|
|
||||||
double currentHpUncapped;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
currentHp = hp_bar_maximum;
|
|
||||||
currentHpUncapped = hp_bar_maximum;
|
|
||||||
|
|
||||||
double lowestHp = currentHp;
|
|
||||||
double lastTime = DrainStartTime;
|
|
||||||
int currentBreak = 0;
|
|
||||||
bool fail = false;
|
|
||||||
int comboTooLowCount = 0;
|
|
||||||
string failReason = string.Empty;
|
|
||||||
|
|
||||||
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
|
|
||||||
{
|
|
||||||
HitObject h = Beatmap.HitObjects[i];
|
|
||||||
|
|
||||||
// Find active break (between current and lastTime)
|
|
||||||
double localLastTime = lastTime;
|
|
||||||
double breakTime = 0;
|
|
||||||
|
|
||||||
// Subtract any break time from the duration since the last object
|
|
||||||
if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count)
|
|
||||||
{
|
|
||||||
BreakPeriod e = Beatmap.Breaks[currentBreak];
|
|
||||||
|
|
||||||
if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime)
|
|
||||||
{
|
|
||||||
// consider break start equal to object end time for version 8+ since drain stops during this time
|
|
||||||
breakTime = (Beatmap.BeatmapInfo.BeatmapVersion < 8) ? (e.EndTime - e.StartTime) : e.EndTime - localLastTime;
|
|
||||||
currentBreak++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reduceHp(testDrop * (h.StartTime - lastTime - breakTime));
|
|
||||||
|
|
||||||
lastTime = h.GetEndTime();
|
|
||||||
|
|
||||||
if (currentHp < lowestHp)
|
|
||||||
lowestHp = currentHp;
|
|
||||||
|
|
||||||
if (currentHp <= lowestHpEver)
|
|
||||||
{
|
|
||||||
fail = true;
|
|
||||||
testDrop *= 0.96;
|
|
||||||
failReason = $"hp too low ({currentHp / hp_bar_maximum} < {lowestHpEver / hp_bar_maximum})";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
double hpReduction = testDrop * (h.GetEndTime() - h.StartTime);
|
|
||||||
double hpOverkill = Math.Max(0, hpReduction - currentHp);
|
|
||||||
reduceHp(hpReduction);
|
|
||||||
|
|
||||||
if (h is Slider slider)
|
|
||||||
{
|
|
||||||
for (int j = 0; j < slider.RepeatCount + 2; j++)
|
|
||||||
increaseHp(hpMultiplierNormal * hp_slider_repeat);
|
|
||||||
foreach (var _ in slider.NestedHitObjects.OfType<SliderTick>())
|
|
||||||
increaseHp(hpMultiplierNormal * hp_slider_tick);
|
|
||||||
}
|
|
||||||
else if (h is Spinner spinner)
|
|
||||||
{
|
|
||||||
foreach (var _ in spinner.NestedHitObjects.Where(t => t is not SpinnerBonusTick))
|
|
||||||
increaseHp(hpMultiplierNormal * 1.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hpOverkill > 0 && currentHp - hpOverkill <= lowestHpEver)
|
|
||||||
{
|
|
||||||
fail = true;
|
|
||||||
testDrop *= 0.96;
|
|
||||||
failReason = $"overkill ({currentHp / hp_bar_maximum} - {hpOverkill / hp_bar_maximum} <= {lowestHpEver / hp_bar_maximum})";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ApplyComboEndBonus && (i == Beatmap.HitObjects.Count - 1 || ((OsuHitObject)Beatmap.HitObjects[i + 1]).NewCombo))
|
|
||||||
{
|
|
||||||
increaseHp(hpMultiplierComboEnd * hp_combo_geki + hpMultiplierNormal * hp_hit_300);
|
|
||||||
|
|
||||||
if (currentHp < lowestHpComboEnd)
|
|
||||||
{
|
|
||||||
if (++comboTooLowCount > 2)
|
|
||||||
{
|
|
||||||
hpMultiplierComboEnd *= 1.07;
|
|
||||||
hpMultiplierNormal *= 1.03;
|
|
||||||
fail = true;
|
|
||||||
failReason = $"combo end hp too low ({currentHp / hp_bar_maximum} < {lowestHpComboEnd / hp_bar_maximum})";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
increaseHp(hpMultiplierNormal * hp_hit_300);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fail && currentHp < lowestHpEnd)
|
|
||||||
{
|
|
||||||
fail = true;
|
|
||||||
testDrop *= 0.94;
|
|
||||||
hpMultiplierComboEnd *= 1.01;
|
|
||||||
hpMultiplierNormal *= 1.01;
|
|
||||||
failReason = $"end hp too low ({currentHp / hp_bar_maximum} < {lowestHpEnd / hp_bar_maximum})";
|
|
||||||
}
|
|
||||||
|
|
||||||
double recovery = (currentHpUncapped - hp_bar_maximum) / Beatmap.HitObjects.Count;
|
|
||||||
|
|
||||||
if (!fail && recovery < hpRecoveryAvailable)
|
|
||||||
{
|
|
||||||
fail = true;
|
|
||||||
testDrop *= 0.96;
|
|
||||||
hpMultiplierComboEnd *= 1.02;
|
|
||||||
hpMultiplierNormal *= 1.01;
|
|
||||||
failReason = $"recovery too low ({recovery / hp_bar_maximum} < {hpRecoveryAvailable / hp_bar_maximum})";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fail)
|
|
||||||
{
|
|
||||||
OnIterationFail?.Invoke($"FAILED drop {testDrop / hp_bar_maximum}: {failReason}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
OnIterationSuccess?.Invoke($"PASSED drop {testDrop / hp_bar_maximum}");
|
|
||||||
return testDrop / hp_bar_maximum;
|
|
||||||
} while (true);
|
|
||||||
|
|
||||||
void reduceHp(double amount)
|
|
||||||
{
|
|
||||||
currentHpUncapped = Math.Max(0, currentHpUncapped - amount);
|
|
||||||
currentHp = Math.Max(0, currentHp - amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void increaseHp(double amount)
|
|
||||||
{
|
|
||||||
currentHpUncapped += amount;
|
|
||||||
currentHp = Math.Max(0, Math.Min(hp_bar_maximum, currentHp + amount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,177 +1,43 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Timing;
|
|
||||||
using osu.Game.Rulesets.Judgements;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
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.Scoring
|
||||||
{
|
{
|
||||||
public partial class OsuHealthProcessor : DrainingHealthProcessor
|
public partial class OsuHealthProcessor : LegacyDrainingHealthProcessor
|
||||||
{
|
{
|
||||||
public Action<string>? OnIterationFail;
|
|
||||||
public Action<string>? OnIterationSuccess;
|
|
||||||
|
|
||||||
private double lowestHpEver;
|
|
||||||
private double lowestHpEnd;
|
|
||||||
private double hpRecoveryAvailable;
|
|
||||||
private double hpMultiplierNormal;
|
|
||||||
|
|
||||||
public OsuHealthProcessor(double drainStartTime)
|
public OsuHealthProcessor(double drainStartTime)
|
||||||
: base(drainStartTime)
|
: base(drainStartTime)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ApplyBeatmap(IBeatmap beatmap)
|
protected override IEnumerable<HitObject> EnumerateTopLevelHitObjects() => Beatmap.HitObjects;
|
||||||
|
|
||||||
|
protected override IEnumerable<HitObject> EnumerateNestedHitObjects(HitObject hitObject)
|
||||||
{
|
{
|
||||||
lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.975, 0.8, 0.3);
|
switch (hitObject)
|
||||||
lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.99, 0.9, 0.4);
|
|
||||||
hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.04, 0.02, 0);
|
|
||||||
|
|
||||||
base.ApplyBeatmap(beatmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Reset(bool storeResults)
|
|
||||||
{
|
|
||||||
hpMultiplierNormal = 1;
|
|
||||||
base.Reset(storeResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ComputeDrainRate()
|
|
||||||
{
|
|
||||||
double testDrop = 0.00025;
|
|
||||||
double currentHp;
|
|
||||||
double currentHpUncapped;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
currentHp = 1;
|
case Slider slider:
|
||||||
currentHpUncapped = 1;
|
foreach (var nested in slider.NestedHitObjects)
|
||||||
|
yield return nested;
|
||||||
|
|
||||||
double lowestHp = currentHp;
|
break;
|
||||||
double lastTime = DrainStartTime;
|
|
||||||
int currentBreak = 0;
|
|
||||||
bool fail = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
|
case Spinner spinner:
|
||||||
{
|
foreach (var nested in spinner.NestedHitObjects.Where(t => t is not SpinnerBonusTick))
|
||||||
HitObject h = Beatmap.HitObjects[i];
|
yield return nested;
|
||||||
|
|
||||||
// Find active break (between current and lastTime)
|
break;
|
||||||
double localLastTime = lastTime;
|
|
||||||
double breakTime = 0;
|
|
||||||
|
|
||||||
// TODO: This doesn't handle overlapping/sequential breaks correctly (/b/614).
|
|
||||||
// Subtract any break time from the duration since the last object
|
|
||||||
// Note that this method is a bit convoluted, but matches stable code for compatibility.
|
|
||||||
if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count)
|
|
||||||
{
|
|
||||||
BreakPeriod e = Beatmap.Breaks[currentBreak];
|
|
||||||
|
|
||||||
if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime)
|
|
||||||
{
|
|
||||||
// consider break start equal to object end time for version 8+ since drain stops during this time
|
|
||||||
breakTime = (Beatmap.BeatmapInfo.BeatmapVersion < 8) ? (e.EndTime - e.StartTime) : e.EndTime - localLastTime;
|
|
||||||
currentBreak++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reduceHp(testDrop * (h.StartTime - lastTime - breakTime));
|
|
||||||
|
|
||||||
lastTime = h.GetEndTime();
|
|
||||||
|
|
||||||
if (currentHp < lowestHp)
|
|
||||||
lowestHp = currentHp;
|
|
||||||
|
|
||||||
if (currentHp <= lowestHpEver)
|
|
||||||
{
|
|
||||||
fail = true;
|
|
||||||
testDrop *= 0.96;
|
|
||||||
OnIterationFail?.Invoke($"FAILED drop {testDrop}: hp too low ({currentHp} < {lowestHpEver})");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
double hpReduction = testDrop * (h.GetEndTime() - h.StartTime);
|
|
||||||
double hpOverkill = Math.Max(0, hpReduction - currentHp);
|
|
||||||
reduceHp(hpReduction);
|
|
||||||
|
|
||||||
switch (h)
|
|
||||||
{
|
|
||||||
case Slider slider:
|
|
||||||
{
|
|
||||||
foreach (var nested in slider.NestedHitObjects)
|
|
||||||
increaseHp(nested);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Spinner spinner:
|
|
||||||
{
|
|
||||||
foreach (var nested in spinner.NestedHitObjects.Where(t => t is not SpinnerBonusTick))
|
|
||||||
increaseHp(nested);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: Because HP is capped during the above increases, long sliders (with many ticks) or spinners
|
|
||||||
// will appear to overkill at lower drain levels than they should. However, it is also not correct to simply use the uncapped version.
|
|
||||||
if (hpOverkill > 0 && currentHp - hpOverkill <= lowestHpEver)
|
|
||||||
{
|
|
||||||
fail = true;
|
|
||||||
testDrop *= 0.96;
|
|
||||||
OnIterationFail?.Invoke($"FAILED drop {testDrop}: overkill ({currentHp} - {hpOverkill} <= {lowestHpEver})");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
increaseHp(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fail && currentHp < lowestHpEnd)
|
|
||||||
{
|
|
||||||
fail = true;
|
|
||||||
testDrop *= 0.94;
|
|
||||||
hpMultiplierNormal *= 1.01;
|
|
||||||
OnIterationFail?.Invoke($"FAILED drop {testDrop}: end hp too low ({currentHp} < {lowestHpEnd})");
|
|
||||||
}
|
|
||||||
|
|
||||||
double recovery = (currentHpUncapped - 1) / Beatmap.HitObjects.Count;
|
|
||||||
|
|
||||||
if (!fail && recovery < hpRecoveryAvailable)
|
|
||||||
{
|
|
||||||
fail = true;
|
|
||||||
testDrop *= 0.96;
|
|
||||||
hpMultiplierNormal *= 1.01;
|
|
||||||
OnIterationFail?.Invoke($"FAILED drop {testDrop}: recovery too low ({recovery} < {hpRecoveryAvailable})");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fail)
|
|
||||||
{
|
|
||||||
OnIterationSuccess?.Invoke($"PASSED drop {testDrop}");
|
|
||||||
return testDrop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void reduceHp(double amount)
|
|
||||||
{
|
|
||||||
currentHpUncapped = Math.Max(0, currentHpUncapped - amount);
|
|
||||||
currentHp = Math.Max(0, currentHp - amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void increaseHp(HitObject hitObject)
|
|
||||||
{
|
|
||||||
double amount = healthIncreaseFor(hitObject, hitObject.CreateJudgement().MaxResult);
|
|
||||||
currentHpUncapped += amount;
|
|
||||||
currentHp = Math.Max(0, Math.Min(1, currentHp + amount));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double GetHealthIncreaseFor(JudgementResult result) => healthIncreaseFor(result.HitObject, result.Type);
|
protected override double GetHealthIncreaseFor(HitObject hitObject, HitResult result)
|
||||||
|
|
||||||
private double healthIncreaseFor(HitObject hitObject, HitResult result)
|
|
||||||
{
|
{
|
||||||
double increase = 0;
|
double increase = 0;
|
||||||
|
|
||||||
@ -217,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hpMultiplierNormal * increase;
|
return HpMultiplierNormal * increase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,8 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AddStep("apply perfect hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect }));
|
AddStep("apply perfect hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect }));
|
||||||
AddAssert("not failed", () => !processor.HasFailed);
|
AddAssert("not failed", () => !processor.HasFailed);
|
||||||
|
|
||||||
AddStep($"apply {resultApplied.ToString().ToLowerInvariant()} hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = resultApplied }));
|
AddStep($"apply {resultApplied.ToString().ToLowerInvariant()} hit result",
|
||||||
|
() => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = resultApplied }));
|
||||||
AddAssert("failed", () => processor.HasFailed);
|
AddAssert("failed", () => processor.HasFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,6 +233,84 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
assertHealthEqualTo(1);
|
assertHealthEqualTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoBreakDrainRate()
|
||||||
|
{
|
||||||
|
DrainingHealthProcessor hp = new DrainingHealthProcessor(-1000);
|
||||||
|
hp.ApplyBeatmap(new Beatmap<JudgeableHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new JudgeableHitObject { StartTime = 0 },
|
||||||
|
new JudgeableHitObject { StartTime = 2000 }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(hp.DrainRate, Is.EqualTo(4.5E-5).Within(0.1E-5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleBreakDrainRate()
|
||||||
|
{
|
||||||
|
DrainingHealthProcessor hp = new DrainingHealthProcessor(-1000);
|
||||||
|
hp.ApplyBeatmap(new Beatmap<JudgeableHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new JudgeableHitObject { StartTime = 0 },
|
||||||
|
new JudgeableHitObject { StartTime = 2000 }
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new BreakPeriod(500, 1500)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(hp.DrainRate, Is.EqualTo(9.1E-5).Within(0.1E-5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOverlappingBreakDrainRate()
|
||||||
|
{
|
||||||
|
DrainingHealthProcessor hp = new DrainingHealthProcessor(-1000);
|
||||||
|
hp.ApplyBeatmap(new Beatmap<JudgeableHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new JudgeableHitObject { StartTime = 0 },
|
||||||
|
new JudgeableHitObject { StartTime = 2000 }
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new BreakPeriod(500, 1400),
|
||||||
|
new BreakPeriod(750, 1500),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(hp.DrainRate, Is.EqualTo(9.1E-5).Within(0.1E-5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSequentialBreakDrainRate()
|
||||||
|
{
|
||||||
|
DrainingHealthProcessor hp = new DrainingHealthProcessor(-1000);
|
||||||
|
hp.ApplyBeatmap(new Beatmap<JudgeableHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new JudgeableHitObject { StartTime = 0 },
|
||||||
|
new JudgeableHitObject { StartTime = 2000 }
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new BreakPeriod(500, 1000),
|
||||||
|
new BreakPeriod(1000, 1500),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(hp.DrainRate, Is.EqualTo(9.1E-5).Within(0.1E-5));
|
||||||
|
}
|
||||||
|
|
||||||
private Beatmap createBeatmap(double startTime, double endTime, params BreakPeriod[] breaks)
|
private Beatmap createBeatmap(double startTime, double endTime, params BreakPeriod[] breaks)
|
||||||
{
|
{
|
||||||
var beatmap = new Beatmap
|
var beatmap = new Beatmap
|
||||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
() => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEditorToHandleLinks),
|
() => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEditorToHandleLinks),
|
||||||
() => Is.EqualTo(1));
|
() => Is.EqualTo(1));
|
||||||
|
|
||||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
|
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||||
AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||||
|
|
||||||
addStepClickLink("00:00:000 (1)", waitForSeek: false);
|
addStepClickLink("00:00:000 (1)", waitForSeek: false);
|
||||||
|
@ -179,7 +179,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
addFakeHit();
|
addFakeHit();
|
||||||
|
|
||||||
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
AddStep("exit", () => Player.Exit());
|
|
||||||
|
|
||||||
AddUntilStep("wait for submission", () => Player.SubmittedScore != null);
|
AddUntilStep("wait for submission", () => Player.SubmittedScore != null);
|
||||||
AddAssert("ensure failing submission", () => Player.SubmittedScore.ScoreInfo.Passed == false);
|
AddAssert("ensure failing submission", () => Player.SubmittedScore.ScoreInfo.Passed == false);
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private TestSpectatorClient spectatorClient => dependenciesScreen.SpectatorClient;
|
private TestSpectatorClient spectatorClient => dependenciesScreen.SpectatorClient;
|
||||||
private DependenciesScreen dependenciesScreen;
|
private DependenciesScreen dependenciesScreen;
|
||||||
private SoloSpectator spectatorScreen;
|
private SoloSpectatorScreen spectatorScreen;
|
||||||
|
|
||||||
private BeatmapSetInfo importedBeatmap;
|
private BeatmapSetInfo importedBeatmap;
|
||||||
private int importedBeatmapId;
|
private int importedBeatmapId;
|
||||||
@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
start();
|
start();
|
||||||
|
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
|
|
||||||
sendFrames(startTime: gameplay_start);
|
sendFrames(startTime: gameplay_start);
|
||||||
|
|
||||||
@ -100,23 +100,22 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
start();
|
start();
|
||||||
|
|
||||||
AddUntilStep("wait for player loader", () => (Stack.CurrentScreen as PlayerLoader)?.IsLoaded == true);
|
AddUntilStep("wait for player loader", () => this.ChildrenOfType<PlayerLoader>().SingleOrDefault()?.IsLoaded == true);
|
||||||
|
|
||||||
AddUntilStep("queue send frames on player load", () =>
|
AddUntilStep("queue send frames on player load", () =>
|
||||||
{
|
{
|
||||||
var loadingPlayer = (Stack.CurrentScreen as PlayerLoader)?.CurrentPlayer;
|
var loadingPlayer = this.ChildrenOfType<PlayerLoader>().SingleOrDefault()?.CurrentPlayer;
|
||||||
|
|
||||||
if (loadingPlayer == null)
|
if (loadingPlayer == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
loadingPlayer.OnLoadComplete += _ =>
|
loadingPlayer.OnLoadComplete += _ =>
|
||||||
{
|
|
||||||
spectatorClient.SendFramesFromUser(streamingUser.Id, 10, gameplay_start);
|
spectatorClient.SendFramesFromUser(streamingUser.Id, 10, gameplay_start);
|
||||||
};
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
|
|
||||||
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
||||||
AddAssert("time is greater than seek target", () => currentFrameStableTime, () => Is.GreaterThan(gameplay_start));
|
AddAssert("time is greater than seek target", () => currentFrameStableTime, () => Is.GreaterThan(gameplay_start));
|
||||||
@ -127,10 +126,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
loadSpectatingScreen();
|
loadSpectatingScreen();
|
||||||
|
|
||||||
AddAssert("screen hasn't changed", () => Stack.CurrentScreen is SoloSpectator);
|
AddAssert("screen hasn't changed", () => Stack.CurrentScreen is SoloSpectatorScreen);
|
||||||
|
|
||||||
start();
|
start();
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
|
|
||||||
sendFrames();
|
sendFrames();
|
||||||
AddAssert("ensure frames arrived", () => replayHandler.HasFrames);
|
AddAssert("ensure frames arrived", () => replayHandler.HasFrames);
|
||||||
@ -156,7 +155,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
loadSpectatingScreen();
|
loadSpectatingScreen();
|
||||||
|
|
||||||
start();
|
start();
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
checkPaused(true);
|
checkPaused(true);
|
||||||
|
|
||||||
// send enough frames to ensure play won't be paused
|
// send enough frames to ensure play won't be paused
|
||||||
@ -172,7 +171,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
sendFrames(300);
|
sendFrames(300);
|
||||||
|
|
||||||
loadSpectatingScreen();
|
loadSpectatingScreen();
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
|
|
||||||
sendFrames(300);
|
sendFrames(300);
|
||||||
|
|
||||||
@ -187,7 +186,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
start();
|
start();
|
||||||
sendFrames();
|
sendFrames();
|
||||||
|
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
|
|
||||||
Player lastPlayer = null;
|
Player lastPlayer = null;
|
||||||
AddStep("store first player", () => lastPlayer = player);
|
AddStep("store first player", () => lastPlayer = player);
|
||||||
@ -195,7 +194,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
start();
|
start();
|
||||||
sendFrames();
|
sendFrames();
|
||||||
|
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
AddAssert("player is different", () => lastPlayer != player);
|
AddAssert("player is different", () => lastPlayer != player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +205,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
start();
|
start();
|
||||||
|
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
checkPaused(true);
|
checkPaused(true);
|
||||||
sendFrames();
|
sendFrames();
|
||||||
|
|
||||||
@ -224,7 +223,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
start();
|
start();
|
||||||
sendFrames();
|
sendFrames();
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
|
|
||||||
AddStep("stop spectating", () => (Stack.CurrentScreen as Player)?.Exit());
|
AddStep("stop spectating", () => (Stack.CurrentScreen as Player)?.Exit());
|
||||||
AddUntilStep("spectating stopped", () => spectatorScreen.GetChildScreen() == null);
|
AddUntilStep("spectating stopped", () => spectatorScreen.GetChildScreen() == null);
|
||||||
@ -237,14 +236,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
start();
|
start();
|
||||||
sendFrames();
|
sendFrames();
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
|
|
||||||
AddStep("stop spectating", () => (Stack.CurrentScreen as Player)?.Exit());
|
AddStep("stop spectating", () => (Stack.CurrentScreen as Player)?.Exit());
|
||||||
AddUntilStep("spectating stopped", () => spectatorScreen.GetChildScreen() == null);
|
AddUntilStep("spectating stopped", () => spectatorScreen.GetChildScreen() == null);
|
||||||
|
|
||||||
// host starts playing a new session
|
// host starts playing a new session
|
||||||
start();
|
start();
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -255,7 +254,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
start(-1234);
|
start(-1234);
|
||||||
sendFrames();
|
sendFrames();
|
||||||
|
|
||||||
AddAssert("screen didn't change", () => Stack.CurrentScreen is SoloSpectator);
|
AddAssert("screen didn't change", () => Stack.CurrentScreen is SoloSpectatorScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -299,7 +298,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
start();
|
start();
|
||||||
sendFrames();
|
sendFrames();
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,14 +309,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
start();
|
start();
|
||||||
sendFrames();
|
sendFrames();
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
|
|
||||||
AddStep("send passed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Passed));
|
AddStep("send passed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Passed));
|
||||||
AddUntilStep("state is passed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Passed);
|
AddUntilStep("state is passed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Passed);
|
||||||
|
|
||||||
start();
|
start();
|
||||||
sendFrames();
|
sendFrames();
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,44 +327,72 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
start();
|
start();
|
||||||
sendFrames();
|
sendFrames();
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
|
|
||||||
AddStep("send quit", () => spectatorClient.SendEndPlay(streamingUser.Id));
|
AddStep("send quit", () => spectatorClient.SendEndPlay(streamingUser.Id));
|
||||||
AddUntilStep("state is quit", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Quit);
|
AddUntilStep("state is quit", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Quit);
|
||||||
|
|
||||||
|
AddAssert("wait for player exit", () => Stack.CurrentScreen is SoloSpectatorScreen);
|
||||||
|
|
||||||
start();
|
start();
|
||||||
sendFrames();
|
sendFrames();
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestFailedState()
|
public void TestFailedStateDuringPlay()
|
||||||
{
|
{
|
||||||
loadSpectatingScreen();
|
loadSpectatingScreen();
|
||||||
|
|
||||||
start();
|
start();
|
||||||
sendFrames();
|
sendFrames();
|
||||||
waitForPlayer();
|
|
||||||
|
waitForPlayerCurrent();
|
||||||
|
|
||||||
AddStep("send failed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Failed));
|
AddStep("send failed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Failed));
|
||||||
AddUntilStep("state is failed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Failed);
|
AddUntilStep("state is failed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Failed);
|
||||||
|
|
||||||
|
AddUntilStep("wait for player to fail", () => player.GameplayState.HasFailed);
|
||||||
|
|
||||||
start();
|
start();
|
||||||
sendFrames();
|
sendFrames();
|
||||||
waitForPlayer();
|
waitForPlayerCurrent();
|
||||||
|
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFailedStateDuringLoading()
|
||||||
|
{
|
||||||
|
loadSpectatingScreen();
|
||||||
|
|
||||||
|
start();
|
||||||
|
sendFrames();
|
||||||
|
|
||||||
|
waitForPlayerLoader();
|
||||||
|
|
||||||
|
AddStep("send failed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Failed));
|
||||||
|
AddUntilStep("state is failed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Failed);
|
||||||
|
|
||||||
|
AddAssert("wait for player exit", () => Stack.CurrentScreen is SoloSpectatorScreen);
|
||||||
|
|
||||||
|
start();
|
||||||
|
sendFrames();
|
||||||
|
waitForPlayerCurrent();
|
||||||
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
||||||
}
|
}
|
||||||
|
|
||||||
private OsuFramedReplayInputHandler replayHandler =>
|
private OsuFramedReplayInputHandler replayHandler =>
|
||||||
(OsuFramedReplayInputHandler)Stack.ChildrenOfType<OsuInputManager>().First().ReplayInputHandler;
|
(OsuFramedReplayInputHandler)Stack.ChildrenOfType<OsuInputManager>().First().ReplayInputHandler;
|
||||||
|
|
||||||
private Player player => Stack.CurrentScreen as Player;
|
private Player player => this.ChildrenOfType<Player>().Single();
|
||||||
|
|
||||||
private double currentFrameStableTime
|
private double currentFrameStableTime
|
||||||
=> player.ChildrenOfType<FrameStabilityContainer>().First().CurrentTime;
|
=> player.ChildrenOfType<FrameStabilityContainer>().First().CurrentTime;
|
||||||
|
|
||||||
private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true);
|
private void waitForPlayerLoader() => AddUntilStep("wait for loading", () => this.ChildrenOfType<SpectatorPlayerLoader>().SingleOrDefault()?.IsLoaded == true);
|
||||||
|
|
||||||
|
private void waitForPlayerCurrent() => AddUntilStep("wait for player current", () => this.ChildrenOfType<Player>().SingleOrDefault()?.IsCurrentScreen() == true);
|
||||||
|
|
||||||
private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.SendStartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId));
|
private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.SendStartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId));
|
||||||
|
|
||||||
@ -381,7 +408,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private void loadSpectatingScreen()
|
private void loadSpectatingScreen()
|
||||||
{
|
{
|
||||||
AddStep("load spectator", () => LoadScreen(spectatorScreen = new SoloSpectator(streamingUser)));
|
AddStep("load spectator", () => LoadScreen(spectatorScreen = new SoloSpectatorScreen(streamingUser)));
|
||||||
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded);
|
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +239,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
{
|
{
|
||||||
AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault() != null);
|
AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault() != null);
|
||||||
|
|
||||||
AddStep("open editor", () => Game.ChildrenOfType<ButtonSystem>().Single().OnEdit.Invoke());
|
AddStep("open editor", () => Game.ChildrenOfType<ButtonSystem>().Single().OnEditBeatmap?.Invoke());
|
||||||
AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded);
|
AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded);
|
||||||
AddStep("click on file", () =>
|
AddStep("click on file", () =>
|
||||||
{
|
{
|
||||||
|
@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestFromSongSelectWithFilter([Values] ScorePresentType type)
|
public void TestFromSongSelectWithFilter([Values] ScorePresentType type)
|
||||||
{
|
{
|
||||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
|
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
AddStep("filter to nothing", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).FilterControl.CurrentTextSearch.Value = "fdsajkl;fgewq");
|
AddStep("filter to nothing", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).FilterControl.CurrentTextSearch.Value = "fdsajkl;fgewq");
|
||||||
@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestFromSongSelectWithConvertRulesetChange([Values] ScorePresentType type)
|
public void TestFromSongSelectWithConvertRulesetChange([Values] ScorePresentType type)
|
||||||
{
|
{
|
||||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
|
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
AddStep("set convert to false", () => Game.LocalConfig.SetValue(OsuSetting.ShowConvertedBeatmaps, false));
|
AddStep("set convert to false", () => Game.LocalConfig.SetValue(OsuSetting.ShowConvertedBeatmaps, false));
|
||||||
@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestFromSongSelect([Values] ScorePresentType type)
|
public void TestFromSongSelect([Values] ScorePresentType type)
|
||||||
{
|
{
|
||||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
|
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
var firstImport = importScore(1);
|
var firstImport = importScore(1);
|
||||||
@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestFromSongSelectDifferentRuleset([Values] ScorePresentType type)
|
public void TestFromSongSelectDifferentRuleset([Values] ScorePresentType type)
|
||||||
{
|
{
|
||||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
|
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
var firstImport = importScore(1);
|
var firstImport = importScore(1);
|
||||||
|
@ -180,11 +180,8 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
});
|
});
|
||||||
AddStep("Show overlay", () => chatOverlay.Show());
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
AddAssert("Overlay uses config height", () => chatOverlay.Height == configChatHeight.Default);
|
AddAssert("Overlay uses config height", () => chatOverlay.Height == configChatHeight.Default);
|
||||||
AddStep("Click top bar", () =>
|
AddStep("Move mouse to drag bar", () => InputManager.MoveMouseTo(chatOverlayTopBar.DragBar));
|
||||||
{
|
AddStep("Click drag bar", () => InputManager.PressButton(MouseButton.Left));
|
||||||
InputManager.MoveMouseTo(chatOverlayTopBar);
|
|
||||||
InputManager.PressButton(MouseButton.Left);
|
|
||||||
});
|
|
||||||
AddStep("Drag overlay to new height", () => InputManager.MoveMouseTo(chatOverlayTopBar, new Vector2(0, -300)));
|
AddStep("Drag overlay to new height", () => InputManager.MoveMouseTo(chatOverlayTopBar, new Vector2(0, -300)));
|
||||||
AddStep("Stop dragging", () => InputManager.ReleaseButton(MouseButton.Left));
|
AddStep("Stop dragging", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
AddStep("Store new height", () => newHeight = chatOverlay.Height);
|
AddStep("Store new height", () => newHeight = chatOverlay.Height);
|
||||||
|
@ -67,14 +67,15 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep("Enter mode", performEnterMode);
|
AddStep("Enter mode", performEnterMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(Key.P, true)]
|
[TestCase(Key.P, Key.P)]
|
||||||
[TestCase(Key.M, true)]
|
[TestCase(Key.M, Key.P)]
|
||||||
[TestCase(Key.L, true)]
|
[TestCase(Key.L, Key.P)]
|
||||||
[TestCase(Key.E, false)]
|
[TestCase(Key.B, Key.E)]
|
||||||
[TestCase(Key.D, false)]
|
[TestCase(Key.S, Key.E)]
|
||||||
[TestCase(Key.Q, false)]
|
[TestCase(Key.D, null)]
|
||||||
[TestCase(Key.O, false)]
|
[TestCase(Key.Q, null)]
|
||||||
public void TestShortcutKeys(Key key, bool entersPlay)
|
[TestCase(Key.O, null)]
|
||||||
|
public void TestShortcutKeys(Key key, Key? subMenuEnterKey)
|
||||||
{
|
{
|
||||||
int activationCount = -1;
|
int activationCount = -1;
|
||||||
AddStep("set up action", () =>
|
AddStep("set up action", () =>
|
||||||
@ -96,8 +97,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
buttons.OnPlaylists = action;
|
buttons.OnPlaylists = action;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Key.E:
|
case Key.B:
|
||||||
buttons.OnEdit = action;
|
buttons.OnEditBeatmap = action;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.S:
|
||||||
|
buttons.OnEditSkin = action;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Key.D:
|
case Key.D:
|
||||||
@ -117,10 +122,10 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep($"press {key}", () => InputManager.Key(key));
|
AddStep($"press {key}", () => InputManager.Key(key));
|
||||||
AddAssert("state is top level", () => buttons.State == ButtonSystemState.TopLevel);
|
AddAssert("state is top level", () => buttons.State == ButtonSystemState.TopLevel);
|
||||||
|
|
||||||
if (entersPlay)
|
if (subMenuEnterKey != null)
|
||||||
{
|
{
|
||||||
AddStep("press P", () => InputManager.Key(Key.P));
|
AddStep($"press {subMenuEnterKey}", () => InputManager.Key(subMenuEnterKey.Value));
|
||||||
AddAssert("state is play", () => buttons.State == ButtonSystemState.Play);
|
AddAssert("state is not top menu", () => buttons.State != ButtonSystemState.TopLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddStep($"press {key}", () => InputManager.Key(key));
|
AddStep($"press {key}", () => InputManager.Key(key));
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
@ -153,7 +154,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
public TestTitle()
|
public TestTitle()
|
||||||
{
|
{
|
||||||
Title = "title";
|
Title = "title";
|
||||||
IconTexture = "Icons/changelog";
|
Icon = HexaconsIcons.Devtools;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
131
osu.Game/Graphics/HexaconsIcons.cs
Normal file
131
osu.Game/Graphics/HexaconsIcons.cs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Text;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics
|
||||||
|
{
|
||||||
|
public static class HexaconsIcons
|
||||||
|
{
|
||||||
|
public const string FONT_NAME = "Icons/Hexacons";
|
||||||
|
|
||||||
|
public static IconUsage BeatmapPacks => get(HexaconsMapping.beatmap_packs);
|
||||||
|
public static IconUsage Beatmap => get(HexaconsMapping.beatmap);
|
||||||
|
public static IconUsage Calendar => get(HexaconsMapping.calendar);
|
||||||
|
public static IconUsage Chart => get(HexaconsMapping.chart);
|
||||||
|
public static IconUsage Community => get(HexaconsMapping.community);
|
||||||
|
public static IconUsage Contests => get(HexaconsMapping.contests);
|
||||||
|
public static IconUsage Devtools => get(HexaconsMapping.devtools);
|
||||||
|
public static IconUsage Download => get(HexaconsMapping.download);
|
||||||
|
public static IconUsage Editor => get(HexaconsMapping.editor);
|
||||||
|
public static IconUsage FeaturedArtist => get(HexaconsMapping.featured_artist);
|
||||||
|
public static IconUsage Home => get(HexaconsMapping.home);
|
||||||
|
public static IconUsage Messaging => get(HexaconsMapping.messaging);
|
||||||
|
public static IconUsage Music => get(HexaconsMapping.music);
|
||||||
|
public static IconUsage News => get(HexaconsMapping.news);
|
||||||
|
public static IconUsage Notification => get(HexaconsMapping.notification);
|
||||||
|
public static IconUsage Profile => get(HexaconsMapping.profile);
|
||||||
|
public static IconUsage Rankings => get(HexaconsMapping.rankings);
|
||||||
|
public static IconUsage Search => get(HexaconsMapping.search);
|
||||||
|
public static IconUsage Settings => get(HexaconsMapping.settings);
|
||||||
|
public static IconUsage Social => get(HexaconsMapping.social);
|
||||||
|
public static IconUsage Store => get(HexaconsMapping.store);
|
||||||
|
public static IconUsage Tournament => get(HexaconsMapping.tournament);
|
||||||
|
public static IconUsage Wiki => get(HexaconsMapping.wiki);
|
||||||
|
|
||||||
|
private static IconUsage get(HexaconsMapping icon) => new IconUsage((char)icon, FONT_NAME);
|
||||||
|
|
||||||
|
// Basically just converting to something we can use in a `char` lookup for FontStore/GlyphStore compatibility.
|
||||||
|
// Names should match filenames in resources.
|
||||||
|
private enum HexaconsMapping
|
||||||
|
{
|
||||||
|
beatmap_packs,
|
||||||
|
beatmap,
|
||||||
|
calendar,
|
||||||
|
chart,
|
||||||
|
community,
|
||||||
|
contests,
|
||||||
|
devtools,
|
||||||
|
download,
|
||||||
|
editor,
|
||||||
|
featured_artist,
|
||||||
|
home,
|
||||||
|
messaging,
|
||||||
|
music,
|
||||||
|
news,
|
||||||
|
notification,
|
||||||
|
profile,
|
||||||
|
rankings,
|
||||||
|
search,
|
||||||
|
settings,
|
||||||
|
social,
|
||||||
|
store,
|
||||||
|
tournament,
|
||||||
|
wiki,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HexaconsStore : ITextureStore, ITexturedGlyphLookupStore
|
||||||
|
{
|
||||||
|
private readonly TextureStore textures;
|
||||||
|
|
||||||
|
public HexaconsStore(TextureStore textures)
|
||||||
|
{
|
||||||
|
this.textures = textures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
textures.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITexturedCharacterGlyph? Get(string? fontName, char character)
|
||||||
|
{
|
||||||
|
if (fontName == FONT_NAME)
|
||||||
|
return new Glyph(textures.Get($"{fontName}/{((HexaconsMapping)character).ToString().Replace("_", "-")}"));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ITexturedCharacterGlyph?> GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character));
|
||||||
|
|
||||||
|
public Texture? Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => null;
|
||||||
|
|
||||||
|
public Texture Get(string name) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public Task<Texture> GetAsync(string name, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public Stream GetStream(string name) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public IEnumerable<string> GetAvailableResources() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public Task<Texture?> GetAsync(string name, WrapMode wrapModeS, WrapMode wrapModeT, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public class Glyph : ITexturedCharacterGlyph
|
||||||
|
{
|
||||||
|
public float XOffset => default;
|
||||||
|
public float YOffset => default;
|
||||||
|
public float XAdvance => default;
|
||||||
|
public float Baseline => default;
|
||||||
|
public char Character => default;
|
||||||
|
|
||||||
|
public float GetKerning<T>(T lastGlyph) where T : ICharacterGlyph => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public Texture Texture { get; }
|
||||||
|
public float Width => Texture.Width;
|
||||||
|
public float Height => Texture.Height;
|
||||||
|
|
||||||
|
public Glyph(Texture texture)
|
||||||
|
{
|
||||||
|
Texture = texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,11 @@ namespace osu.Game.Localisation
|
|||||||
{
|
{
|
||||||
private const string prefix = @"osu.Game.Resources.Localisation.Editor";
|
private const string prefix = @"osu.Game.Resources.Localisation.Editor";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Beatmap editor"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BeatmapEditor => new TranslatableString(getKey(@"beatmap_editor"), @"Beatmap editor");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Waveform opacity"
|
/// "Waveform opacity"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Online.Spectator
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the local user is playing.
|
/// Whether the local user is playing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected internal bool IsPlaying { get; private set; }
|
private bool isPlaying { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called whenever new frames arrive from the server.
|
/// Called whenever new frames arrive from the server.
|
||||||
@ -58,17 +58,17 @@ namespace osu.Game.Online.Spectator
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called whenever a user starts a play session, or immediately if the user is being watched and currently in a play session.
|
/// Called whenever a user starts a play session, or immediately if the user is being watched and currently in a play session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual event Action<int, SpectatorState>? OnUserBeganPlaying;
|
public event Action<int, SpectatorState>? OnUserBeganPlaying;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called whenever a user finishes a play session.
|
/// Called whenever a user finishes a play session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual event Action<int, SpectatorState>? OnUserFinishedPlaying;
|
public event Action<int, SpectatorState>? OnUserFinishedPlaying;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called whenever a user-submitted score has been fully processed.
|
/// Called whenever a user-submitted score has been fully processed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual event Action<int, long>? OnUserScoreProcessed;
|
public event Action<int, long>? OnUserScoreProcessed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A dictionary containing all users currently being watched, with the number of watching components for each user.
|
/// A dictionary containing all users currently being watched, with the number of watching components for each user.
|
||||||
@ -114,7 +114,7 @@ namespace osu.Game.Online.Spectator
|
|||||||
}
|
}
|
||||||
|
|
||||||
// re-send state in case it wasn't received
|
// re-send state in case it wasn't received
|
||||||
if (IsPlaying)
|
if (isPlaying)
|
||||||
// TODO: this is likely sent out of order after a reconnect scenario. needs further consideration.
|
// TODO: this is likely sent out of order after a reconnect scenario. needs further consideration.
|
||||||
BeginPlayingInternal(currentScoreToken, currentState);
|
BeginPlayingInternal(currentScoreToken, currentState);
|
||||||
}
|
}
|
||||||
@ -179,10 +179,10 @@ namespace osu.Game.Online.Spectator
|
|||||||
// This schedule is only here to match the one below in `EndPlaying`.
|
// This schedule is only here to match the one below in `EndPlaying`.
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
if (IsPlaying)
|
if (isPlaying)
|
||||||
throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing");
|
throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing");
|
||||||
|
|
||||||
IsPlaying = true;
|
isPlaying = true;
|
||||||
|
|
||||||
// transfer state at point of beginning play
|
// transfer state at point of beginning play
|
||||||
currentState.BeatmapID = score.ScoreInfo.BeatmapInfo!.OnlineID;
|
currentState.BeatmapID = score.ScoreInfo.BeatmapInfo!.OnlineID;
|
||||||
@ -202,7 +202,7 @@ namespace osu.Game.Online.Spectator
|
|||||||
|
|
||||||
public void HandleFrame(ReplayFrame frame) => Schedule(() =>
|
public void HandleFrame(ReplayFrame frame) => Schedule(() =>
|
||||||
{
|
{
|
||||||
if (!IsPlaying)
|
if (!isPlaying)
|
||||||
{
|
{
|
||||||
Logger.Log($"Frames arrived at {nameof(SpectatorClient)} outside of gameplay scope and will be ignored.");
|
Logger.Log($"Frames arrived at {nameof(SpectatorClient)} outside of gameplay scope and will be ignored.");
|
||||||
return;
|
return;
|
||||||
@ -224,7 +224,7 @@ namespace osu.Game.Online.Spectator
|
|||||||
// We probably need to find a better way to handle this...
|
// We probably need to find a better way to handle this...
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
if (!IsPlaying)
|
if (!isPlaying)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Disposal can take some time, leading to EndPlaying potentially being called after a future play session.
|
// Disposal can take some time, leading to EndPlaying potentially being called after a future play session.
|
||||||
@ -235,7 +235,7 @@ namespace osu.Game.Online.Spectator
|
|||||||
if (pendingFrames.Count > 0)
|
if (pendingFrames.Count > 0)
|
||||||
purgePendingFrames();
|
purgePendingFrames();
|
||||||
|
|
||||||
IsPlaying = false;
|
isPlaying = false;
|
||||||
currentBeatmap = null;
|
currentBeatmap = null;
|
||||||
|
|
||||||
if (state.HasPassed)
|
if (state.HasPassed)
|
||||||
|
@ -477,6 +477,8 @@ namespace osu.Game
|
|||||||
AddFont(Resources, @"Fonts/Venera/Venera-Light");
|
AddFont(Resources, @"Fonts/Venera/Venera-Light");
|
||||||
AddFont(Resources, @"Fonts/Venera/Venera-Bold");
|
AddFont(Resources, @"Fonts/Venera/Venera-Bold");
|
||||||
AddFont(Resources, @"Fonts/Venera/Venera-Black");
|
AddFont(Resources, @"Fonts/Venera/Venera-Black");
|
||||||
|
|
||||||
|
Fonts.AddStore(new HexaconsIcons.HexaconsStore(Textures));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
{
|
{
|
||||||
Title = PageTitleStrings.MainBeatmapsetsControllerIndex;
|
Title = PageTitleStrings.MainBeatmapsetsControllerIndex;
|
||||||
Description = NamedOverlayComponentStrings.BeatmapListingDescription;
|
Description = NamedOverlayComponentStrings.BeatmapListingDescription;
|
||||||
IconTexture = "Icons/Hexacons/beatmap";
|
Icon = HexaconsIcons.Beatmap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -59,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
public BeatmapHeaderTitle()
|
public BeatmapHeaderTitle()
|
||||||
{
|
{
|
||||||
Title = PageTitleStrings.MainBeatmapsetsControllerShow;
|
Title = PageTitleStrings.MainBeatmapsetsControllerShow;
|
||||||
IconTexture = "Icons/Hexacons/beatmap";
|
Icon = HexaconsIcons.Beatmap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
@ -123,7 +124,7 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
{
|
{
|
||||||
Title = PageTitleStrings.MainChangelogControllerDefault;
|
Title = PageTitleStrings.MainChangelogControllerDefault;
|
||||||
Description = NamedOverlayComponentStrings.ChangelogDescription;
|
Description = NamedOverlayComponentStrings.ChangelogDescription;
|
||||||
IconTexture = "Icons/Hexacons/devtools";
|
Icon = HexaconsIcons.Devtools;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -13,25 +13,22 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Chat
|
namespace osu.Game.Overlays.Chat
|
||||||
{
|
{
|
||||||
public partial class ChatOverlayTopBar : Container
|
public partial class ChatOverlayTopBar : Container
|
||||||
{
|
{
|
||||||
private Box background = null!;
|
public Drawable DragBar { get; private set; } = null!;
|
||||||
|
|
||||||
private Color4 backgroundColour;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider, TextureStore textures)
|
private void load(OverlayColourProvider colourProvider, TextureStore textures)
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
background = new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = backgroundColour = colourProvider.Background3,
|
Colour = colourProvider.Background3,
|
||||||
},
|
},
|
||||||
new GridContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
@ -45,12 +42,12 @@ namespace osu.Game.Overlays.Chat
|
|||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new Sprite
|
new SpriteIcon
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Texture = textures.Get("Icons/Hexacons/messaging"),
|
Icon = HexaconsIcons.Social,
|
||||||
Size = new Vector2(18),
|
Size = new Vector2(24),
|
||||||
},
|
},
|
||||||
// Placeholder text
|
// Placeholder text
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
@ -64,19 +61,92 @@ namespace osu.Game.Overlays.Chat
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
DragBar = new DragArea
|
||||||
|
{
|
||||||
|
Alpha = RuntimeInfo.IsMobile ? 1 : 0,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Colour = colourProvider.Background4,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
background.FadeColour(backgroundColour.Lighten(0.1f), 300, Easing.OutQuint);
|
if (!RuntimeInfo.IsMobile)
|
||||||
|
DragBar.FadeIn(100);
|
||||||
return base.OnHover(e);
|
return base.OnHover(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
background.FadeColour(backgroundColour, 300, Easing.OutQuint);
|
if (!RuntimeInfo.IsMobile)
|
||||||
|
DragBar.FadeOut(100);
|
||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private partial class DragArea : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Circle circle;
|
||||||
|
|
||||||
|
public DragArea()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
circle = new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(150, 7),
|
||||||
|
Margin = new MarginPadding(12),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateScale();
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
updateScale();
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool dragging;
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
dragging = true;
|
||||||
|
updateScale();
|
||||||
|
return base.OnMouseDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
{
|
||||||
|
dragging = false;
|
||||||
|
updateScale();
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateScale()
|
||||||
|
{
|
||||||
|
if (dragging || IsHovered)
|
||||||
|
circle.FadeIn(100);
|
||||||
|
else
|
||||||
|
circle.FadeTo(0.6f, 100);
|
||||||
|
|
||||||
|
if (dragging)
|
||||||
|
circle.ScaleTo(1f, 400, Easing.OutQuint);
|
||||||
|
else if (IsHovered)
|
||||||
|
circle.ScaleTo(1.05f, 400, Easing.OutElasticHalf);
|
||||||
|
else
|
||||||
|
circle.ScaleTo(1f, 500, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,13 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
@ -29,7 +31,7 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
public partial class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, IKeyBindingHandler<PlatformAction>
|
public partial class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, IKeyBindingHandler<PlatformAction>
|
||||||
{
|
{
|
||||||
public string IconTexture => "Icons/Hexacons/messaging";
|
public IconUsage Icon => HexaconsIcons.Messaging;
|
||||||
public LocalisableString Title => ChatStrings.HeaderTitle;
|
public LocalisableString Title => ChatStrings.HeaderTitle;
|
||||||
public LocalisableString Description => ChatStrings.HeaderDescription;
|
public LocalisableString Description => ChatStrings.HeaderDescription;
|
||||||
|
|
||||||
@ -251,10 +253,14 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
isDraggingTopBar = topBar.DragBar.IsHovered;
|
||||||
|
return base.OnMouseDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e)
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
{
|
{
|
||||||
isDraggingTopBar = topBar.IsHovered;
|
|
||||||
|
|
||||||
if (!isDraggingTopBar)
|
if (!isDraggingTopBar)
|
||||||
return base.OnDragStart(e);
|
return base.OnDragStart(e);
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ namespace osu.Game.Overlays.Dashboard
|
|||||||
Text = "Spectate",
|
Text = "Spectate",
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
Action = () => performer?.PerformFromScreen(s => s.Push(new SoloSpectator(User))),
|
Action = () => performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(User))),
|
||||||
Enabled = { Value = User.Id != api.LocalUser.Value.Id }
|
Enabled = { Value = User.Id != api.LocalUser.Value.Id }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Dashboard
|
|||||||
{
|
{
|
||||||
Title = PageTitleStrings.MainHomeControllerIndex;
|
Title = PageTitleStrings.MainHomeControllerIndex;
|
||||||
Description = NamedOverlayComponentStrings.DashboardDescription;
|
Description = NamedOverlayComponentStrings.DashboardDescription;
|
||||||
IconTexture = "Icons/Hexacons/social";
|
Icon = HexaconsIcons.Social;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -17,7 +18,7 @@ namespace osu.Game.Overlays
|
|||||||
public abstract partial class FullscreenOverlay<T> : WaveOverlayContainer, INamedOverlayComponent
|
public abstract partial class FullscreenOverlay<T> : WaveOverlayContainer, INamedOverlayComponent
|
||||||
where T : OverlayHeader
|
where T : OverlayHeader
|
||||||
{
|
{
|
||||||
public virtual string IconTexture => Header.Title.IconTexture;
|
public virtual IconUsage Icon => Header.Title.Icon;
|
||||||
public virtual LocalisableString Title => Header.Title.Title;
|
public virtual LocalisableString Title => Header.Title.Title;
|
||||||
public virtual LocalisableString Description => Header.Title.Description;
|
public virtual LocalisableString Description => Header.Title.Description;
|
||||||
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Overlays
|
namespace osu.Game.Overlays
|
||||||
{
|
{
|
||||||
public interface INamedOverlayComponent
|
public interface INamedOverlayComponent
|
||||||
{
|
{
|
||||||
string IconTexture { get; }
|
IconUsage Icon { get; }
|
||||||
|
|
||||||
LocalisableString Title { get; }
|
LocalisableString Title { get; }
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ using System;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
@ -68,7 +69,7 @@ namespace osu.Game.Overlays.News
|
|||||||
{
|
{
|
||||||
Title = PageTitleStrings.MainNewsControllerDefault;
|
Title = PageTitleStrings.MainNewsControllerDefault;
|
||||||
Description = NamedOverlayComponentStrings.NewsDescription;
|
Description = NamedOverlayComponentStrings.NewsDescription;
|
||||||
IconTexture = "Icons/Hexacons/news";
|
Icon = HexaconsIcons.News;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,11 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
@ -27,7 +29,7 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
public partial class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, INotificationOverlay
|
public partial class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, INotificationOverlay
|
||||||
{
|
{
|
||||||
public string IconTexture => "Icons/Hexacons/notification";
|
public IconUsage Icon => HexaconsIcons.Notification;
|
||||||
public LocalisableString Title => NotificationsStrings.HeaderTitle;
|
public LocalisableString Title => NotificationsStrings.HeaderTitle;
|
||||||
public LocalisableString Description => NotificationsStrings.HeaderDescription;
|
public LocalisableString Description => NotificationsStrings.HeaderDescription;
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
public partial class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent
|
public partial class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent
|
||||||
{
|
{
|
||||||
public string IconTexture => "Icons/Hexacons/music";
|
public IconUsage Icon => HexaconsIcons.Music;
|
||||||
public LocalisableString Title => NowPlayingStrings.HeaderTitle;
|
public LocalisableString Title => NowPlayingStrings.HeaderTitle;
|
||||||
public LocalisableString Description => NowPlayingStrings.HeaderDescription;
|
public LocalisableString Description => NowPlayingStrings.HeaderDescription;
|
||||||
|
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -20,7 +16,7 @@ namespace osu.Game.Overlays
|
|||||||
public const float ICON_SIZE = 30;
|
public const float ICON_SIZE = 30;
|
||||||
|
|
||||||
private readonly OsuSpriteText titleText;
|
private readonly OsuSpriteText titleText;
|
||||||
private readonly Container icon;
|
private readonly Container iconContainer;
|
||||||
|
|
||||||
private LocalisableString title;
|
private LocalisableString title;
|
||||||
|
|
||||||
@ -32,12 +28,20 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
public LocalisableString Description { get; protected set; }
|
public LocalisableString Description { get; protected set; }
|
||||||
|
|
||||||
private string iconTexture;
|
private IconUsage icon;
|
||||||
|
|
||||||
public string IconTexture
|
public IconUsage Icon
|
||||||
{
|
{
|
||||||
get => iconTexture;
|
get => icon;
|
||||||
protected set => icon.Child = new OverlayTitleIcon(iconTexture = value);
|
protected set => iconContainer.Child = new SpriteIcon
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
FillMode = FillMode.Fit,
|
||||||
|
|
||||||
|
Icon = icon = value,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected OverlayTitle()
|
protected OverlayTitle()
|
||||||
@ -51,7 +55,7 @@ namespace osu.Game.Overlays
|
|||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
icon = new Container
|
iconContainer = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -68,26 +72,5 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class OverlayTitleIcon : Sprite
|
|
||||||
{
|
|
||||||
private readonly string textureName;
|
|
||||||
|
|
||||||
public OverlayTitleIcon(string textureName)
|
|
||||||
{
|
|
||||||
this.textureName = textureName;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
Anchor = Anchor.Centre;
|
|
||||||
Origin = Anchor.Centre;
|
|
||||||
FillMode = FillMode.Fit;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(TextureStore textures)
|
|
||||||
{
|
|
||||||
Texture = textures.Get(textureName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Overlays.Profile.Header;
|
using osu.Game.Overlays.Profile.Header;
|
||||||
using osu.Game.Overlays.Profile.Header.Components;
|
using osu.Game.Overlays.Profile.Header.Components;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
@ -86,7 +87,7 @@ namespace osu.Game.Overlays.Profile
|
|||||||
public ProfileHeaderTitle()
|
public ProfileHeaderTitle()
|
||||||
{
|
{
|
||||||
Title = PageTitleStrings.MainUsersControllerDefault;
|
Title = PageTitleStrings.MainUsersControllerDefault;
|
||||||
IconTexture = "Icons/Hexacons/profile";
|
Icon = HexaconsIcons.Profile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ namespace osu.Game.Overlays.Rankings
|
|||||||
{
|
{
|
||||||
Title = PageTitleStrings.MainRankingControllerDefault;
|
Title = PageTitleStrings.MainRankingControllerDefault;
|
||||||
Description = NamedOverlayComponentStrings.RankingsDescription;
|
Description = NamedOverlayComponentStrings.RankingsDescription;
|
||||||
IconTexture = "Icons/Hexacons/rankings";
|
Icon = HexaconsIcons.Rankings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,16 @@ using osu.Game.Overlays.Settings.Sections.Input;
|
|||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Overlays
|
namespace osu.Game.Overlays
|
||||||
{
|
{
|
||||||
public partial class SettingsOverlay : SettingsPanel, INamedOverlayComponent
|
public partial class SettingsOverlay : SettingsPanel, INamedOverlayComponent
|
||||||
{
|
{
|
||||||
public string IconTexture => "Icons/Hexacons/settings";
|
public IconUsage Icon => HexaconsIcons.Settings;
|
||||||
public LocalisableString Title => SettingsStrings.HeaderTitle;
|
public LocalisableString Title => SettingsStrings.HeaderTitle;
|
||||||
public LocalisableString Description => SettingsStrings.HeaderDescription;
|
public LocalisableString Description => SettingsStrings.HeaderDescription;
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -9,12 +12,21 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Components;
|
using osu.Game.Screens.Edit.Components;
|
||||||
|
using osu.Game.Screens.Menu;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.SkinEditor
|
namespace osu.Game.Overlays.SkinEditor
|
||||||
@ -31,12 +43,27 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
|
|
||||||
private SkinEditor? skinEditor;
|
private SkinEditor? skinEditor;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IPerformFromScreenRunner? performer { get; set; }
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
public readonly EditorClipboard Clipboard = new EditorClipboard();
|
public readonly EditorClipboard Clipboard = new EditorClipboard();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuGame game { get; set; } = null!;
|
private OsuGame game { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private MusicController music { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Bindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||||
|
|
||||||
private OsuScreen? lastTargetScreen;
|
private OsuScreen? lastTargetScreen;
|
||||||
|
|
||||||
private Vector2 lastDrawSize;
|
private Vector2 lastDrawSize;
|
||||||
@ -72,6 +99,9 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
{
|
{
|
||||||
globallyDisableBeatmapSkinSetting();
|
globallyDisableBeatmapSkinSetting();
|
||||||
|
|
||||||
|
if (lastTargetScreen is MainMenu)
|
||||||
|
PresentGameplay();
|
||||||
|
|
||||||
if (skinEditor != null)
|
if (skinEditor != null)
|
||||||
{
|
{
|
||||||
skinEditor.Show();
|
skinEditor.Show();
|
||||||
@ -105,6 +135,36 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
globallyReenableBeatmapSkinSetting();
|
globallyReenableBeatmapSkinSetting();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PresentGameplay()
|
||||||
|
{
|
||||||
|
performer?.PerformFromScreen(screen =>
|
||||||
|
{
|
||||||
|
// If we're playing the intro, switch away to another beatmap.
|
||||||
|
if (beatmap.Value.BeatmapSetInfo.Protected)
|
||||||
|
{
|
||||||
|
music.NextTrack();
|
||||||
|
Schedule(PresentGameplay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screen is Player)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod();
|
||||||
|
|
||||||
|
IReadOnlyList<Mod> usableMods = mods.Value;
|
||||||
|
|
||||||
|
if (replayGeneratingMod != null)
|
||||||
|
usableMods = usableMods.Append(replayGeneratingMod).ToArray();
|
||||||
|
|
||||||
|
if (!ModUtils.CheckCompatibleSet(usableMods, out var invalid))
|
||||||
|
mods.Value = mods.Value.Except(invalid).ToArray();
|
||||||
|
|
||||||
|
if (replayGeneratingMod != null)
|
||||||
|
screen.Push(new EndlessPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods)));
|
||||||
|
}, new[] { typeof(Player), typeof(PlaySongSelect) });
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -222,5 +282,25 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
leasedBeatmapSkins?.Return();
|
leasedBeatmapSkins?.Return();
|
||||||
leasedBeatmapSkins = null;
|
leasedBeatmapSkins = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private partial class EndlessPlayer : ReplayPlayer
|
||||||
|
{
|
||||||
|
public EndlessPlayer(Func<IBeatmap, IReadOnlyList<Mod>, Score> createScore)
|
||||||
|
: base(createScore, new PlayerConfiguration
|
||||||
|
{
|
||||||
|
ShowResults = false,
|
||||||
|
AutomaticallySkipIntro = true,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (GameplayState.HasPassed)
|
||||||
|
GameplayClockContainer.Seek(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -14,12 +11,8 @@ using osu.Game.Graphics.Containers;
|
|||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Rulesets;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Play;
|
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Utils;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.SkinEditor
|
namespace osu.Game.Overlays.SkinEditor
|
||||||
@ -36,10 +29,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
private IPerformFromScreenRunner? performer { get; set; }
|
private IPerformFromScreenRunner? performer { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
private SkinEditorOverlay? skinEditorOverlay { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private Bindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
|
||||||
|
|
||||||
public SkinEditorSceneLibrary()
|
public SkinEditorSceneLibrary()
|
||||||
{
|
{
|
||||||
@ -96,24 +86,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
Text = SkinEditorStrings.Gameplay,
|
Text = SkinEditorStrings.Gameplay,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Action = () => performer?.PerformFromScreen(screen =>
|
Action = () => skinEditorOverlay?.PresentGameplay(),
|
||||||
{
|
|
||||||
if (screen is Player)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod();
|
|
||||||
|
|
||||||
IReadOnlyList<Mod> usableMods = mods.Value;
|
|
||||||
|
|
||||||
if (replayGeneratingMod != null)
|
|
||||||
usableMods = usableMods.Append(replayGeneratingMod).ToArray();
|
|
||||||
|
|
||||||
if (!ModUtils.CheckCompatibleSet(usableMods, out var invalid))
|
|
||||||
mods.Value = mods.Value.Except(invalid).ToArray();
|
|
||||||
|
|
||||||
if (replayGeneratingMod != null)
|
|
||||||
screen.Push(new PlayerLoader(() => new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods))));
|
|
||||||
}, new[] { typeof(Player), typeof(PlaySongSelect) })
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -10,12 +10,11 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Database;
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Backgrounds;
|
using osu.Game.Graphics.Backgrounds;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
@ -36,16 +35,13 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
IconContainer.Show();
|
IconContainer.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private TextureStore textures { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private ReadableKeyCombinationProvider keyCombinationProvider { get; set; } = null!;
|
private ReadableKeyCombinationProvider keyCombinationProvider { get; set; } = null!;
|
||||||
|
|
||||||
public void SetIcon(string texture) =>
|
public void SetIcon(IconUsage icon) =>
|
||||||
SetIcon(new Sprite
|
SetIcon(new SpriteIcon
|
||||||
{
|
{
|
||||||
Texture = textures.Get(texture),
|
Icon = icon,
|
||||||
});
|
});
|
||||||
|
|
||||||
public LocalisableString Text
|
public LocalisableString Text
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
{
|
{
|
||||||
TooltipMain = ToolbarStrings.HomeHeaderTitle;
|
TooltipMain = ToolbarStrings.HomeHeaderTitle;
|
||||||
TooltipSub = ToolbarStrings.HomeHeaderDescription;
|
TooltipSub = ToolbarStrings.HomeHeaderDescription;
|
||||||
SetIcon("Icons/Hexacons/home");
|
SetIcon(HexaconsIcons.Home);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
{
|
{
|
||||||
TooltipMain = named.Title;
|
TooltipMain = named.Title;
|
||||||
TooltipSub = named.Description;
|
TooltipSub = named.Description;
|
||||||
SetIcon(named.IconTexture);
|
SetIcon(named.Icon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
@ -81,7 +82,7 @@ namespace osu.Game.Overlays.Wiki
|
|||||||
{
|
{
|
||||||
Title = PageTitleStrings.MainWikiControllerDefault;
|
Title = PageTitleStrings.MainWikiControllerDefault;
|
||||||
Description = NamedOverlayComponentStrings.WikiDescription;
|
Description = NamedOverlayComponentStrings.WikiDescription;
|
||||||
IconTexture = "Icons/Hexacons/wiki";
|
Icon = HexaconsIcons.Wiki;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,18 +103,20 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
if (beatmap.HitObjects.Count > 0)
|
if (beatmap.HitObjects.Count > 0)
|
||||||
gameplayEndTime = beatmap.HitObjects[^1].GetEndTime();
|
gameplayEndTime = beatmap.HitObjects[^1].GetEndTime();
|
||||||
|
|
||||||
noDrainPeriodTracker = new PeriodTracker(beatmap.Breaks.Select(breakPeriod => new Period(
|
noDrainPeriodTracker = new PeriodTracker(
|
||||||
beatmap.HitObjects
|
beatmap.Breaks.Select(breakPeriod =>
|
||||||
.Select(hitObject => hitObject.GetEndTime())
|
new Period(
|
||||||
.Where(endTime => endTime <= breakPeriod.StartTime)
|
beatmap.HitObjects
|
||||||
.DefaultIfEmpty(double.MinValue)
|
.Select(hitObject => hitObject.GetEndTime())
|
||||||
.Last(),
|
.Where(endTime => endTime <= breakPeriod.StartTime)
|
||||||
beatmap.HitObjects
|
.DefaultIfEmpty(double.MinValue)
|
||||||
.Select(hitObject => hitObject.StartTime)
|
.Last(),
|
||||||
.Where(startTime => startTime >= breakPeriod.EndTime)
|
beatmap.HitObjects
|
||||||
.DefaultIfEmpty(double.MaxValue)
|
.Select(hitObject => hitObject.StartTime)
|
||||||
.First()
|
.Where(startTime => startTime >= breakPeriod.EndTime)
|
||||||
)));
|
.DefaultIfEmpty(double.MaxValue)
|
||||||
|
.First()
|
||||||
|
)));
|
||||||
|
|
||||||
targetMinimumHealth = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, min_health_target, mid_health_target, max_health_target);
|
targetMinimumHealth = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, min_health_target, mid_health_target, max_health_target);
|
||||||
|
|
||||||
@ -159,26 +161,24 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
{
|
{
|
||||||
double currentHealth = 1;
|
double currentHealth = 1;
|
||||||
double lowestHealth = 1;
|
double lowestHealth = 1;
|
||||||
int currentBreak = -1;
|
int currentBreak = 0;
|
||||||
|
|
||||||
for (int i = 0; i < healthIncreases.Count; i++)
|
for (int i = 0; i < healthIncreases.Count; i++)
|
||||||
{
|
{
|
||||||
double currentTime = healthIncreases[i].time;
|
double currentTime = healthIncreases[i].time;
|
||||||
double lastTime = i > 0 ? healthIncreases[i - 1].time : DrainStartTime;
|
double lastTime = i > 0 ? healthIncreases[i - 1].time : DrainStartTime;
|
||||||
|
|
||||||
// Subtract any break time from the duration since the last object
|
while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= currentTime)
|
||||||
if (Beatmap.Breaks.Count > 0)
|
|
||||||
{
|
{
|
||||||
// Advance the last break occuring before the current time
|
// If two hitobjects are separated by a break period, there is no drain for the full duration between the hitobjects.
|
||||||
while (currentBreak + 1 < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak + 1].EndTime < currentTime)
|
// This differs from legacy (version < 8) beatmaps which continue draining until the break section is entered,
|
||||||
currentBreak++;
|
// but this shouldn't have a noticeable impact in practice.
|
||||||
|
lastTime = currentTime;
|
||||||
if (currentBreak >= 0)
|
currentBreak++;
|
||||||
lastTime = Math.Max(lastTime, Beatmap.Breaks[currentBreak].EndTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply health adjustments
|
// Apply health adjustments
|
||||||
currentHealth -= (healthIncreases[i].time - lastTime) * result;
|
currentHealth -= (currentTime - lastTime) * result;
|
||||||
lowestHealth = Math.Min(lowestHealth, currentHealth);
|
lowestHealth = Math.Min(lowestHealth, currentHealth);
|
||||||
currentHealth = Math.Min(1, currentHealth + healthIncreases[i].health);
|
currentHealth = Math.Min(1, currentHealth + healthIncreases[i].health);
|
||||||
|
|
||||||
|
158
osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs
Normal file
158
osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Scoring
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="DrainingHealthProcessor"/> that matches legacy drain rate calculations as best as possible.
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class LegacyDrainingHealthProcessor : DrainingHealthProcessor
|
||||||
|
{
|
||||||
|
public Action<string>? OnIterationFail;
|
||||||
|
public Action<string>? OnIterationSuccess;
|
||||||
|
|
||||||
|
protected double HpMultiplierNormal { get; private set; }
|
||||||
|
|
||||||
|
private double lowestHpEver;
|
||||||
|
private double lowestHpEnd;
|
||||||
|
private double hpRecoveryAvailable;
|
||||||
|
|
||||||
|
protected LegacyDrainingHealthProcessor(double drainStartTime)
|
||||||
|
: base(drainStartTime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ApplyBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.975, 0.8, 0.3);
|
||||||
|
lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.99, 0.9, 0.4);
|
||||||
|
hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.04, 0.02, 0);
|
||||||
|
|
||||||
|
base.ApplyBeatmap(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Reset(bool storeResults)
|
||||||
|
{
|
||||||
|
HpMultiplierNormal = 1;
|
||||||
|
base.Reset(storeResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override double ComputeDrainRate()
|
||||||
|
{
|
||||||
|
double testDrop = 0.00025;
|
||||||
|
double currentHp;
|
||||||
|
double currentHpUncapped;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
currentHp = 1;
|
||||||
|
currentHpUncapped = 1;
|
||||||
|
|
||||||
|
double lowestHp = currentHp;
|
||||||
|
double lastTime = DrainStartTime;
|
||||||
|
int currentBreak = 0;
|
||||||
|
bool fail = false;
|
||||||
|
int topLevelObjectCount = 0;
|
||||||
|
|
||||||
|
foreach (var h in EnumerateTopLevelHitObjects())
|
||||||
|
{
|
||||||
|
topLevelObjectCount++;
|
||||||
|
|
||||||
|
while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= h.StartTime)
|
||||||
|
{
|
||||||
|
// If two hitobjects are separated by a break period, there is no drain for the full duration between the hitobjects.
|
||||||
|
// This differs from legacy (version < 8) beatmaps which continue draining until the break section is entered,
|
||||||
|
// but this shouldn't have a noticeable impact in practice.
|
||||||
|
lastTime = h.StartTime;
|
||||||
|
currentBreak++;
|
||||||
|
}
|
||||||
|
|
||||||
|
reduceHp(testDrop * (h.StartTime - lastTime));
|
||||||
|
|
||||||
|
lastTime = h.GetEndTime();
|
||||||
|
|
||||||
|
if (currentHp < lowestHp)
|
||||||
|
lowestHp = currentHp;
|
||||||
|
|
||||||
|
if (currentHp <= lowestHpEver)
|
||||||
|
{
|
||||||
|
fail = true;
|
||||||
|
testDrop *= 0.96;
|
||||||
|
OnIterationFail?.Invoke($"FAILED drop {testDrop}: hp too low ({currentHp} < {lowestHpEver})");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double hpReduction = testDrop * (h.GetEndTime() - h.StartTime);
|
||||||
|
double hpOverkill = Math.Max(0, hpReduction - currentHp);
|
||||||
|
reduceHp(hpReduction);
|
||||||
|
|
||||||
|
foreach (var nested in EnumerateNestedHitObjects(h))
|
||||||
|
increaseHp(nested);
|
||||||
|
|
||||||
|
// Note: Because HP is capped during the above increases, long sliders (with many ticks) or spinners
|
||||||
|
// will appear to overkill at lower drain levels than they should. However, it is also not correct to simply use the uncapped version.
|
||||||
|
if (hpOverkill > 0 && currentHp - hpOverkill <= lowestHpEver)
|
||||||
|
{
|
||||||
|
fail = true;
|
||||||
|
testDrop *= 0.96;
|
||||||
|
OnIterationFail?.Invoke($"FAILED drop {testDrop}: overkill ({currentHp} - {hpOverkill} <= {lowestHpEver})");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
increaseHp(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fail && currentHp < lowestHpEnd)
|
||||||
|
{
|
||||||
|
fail = true;
|
||||||
|
testDrop *= 0.94;
|
||||||
|
HpMultiplierNormal *= 1.01;
|
||||||
|
OnIterationFail?.Invoke($"FAILED drop {testDrop}: end hp too low ({currentHp} < {lowestHpEnd})");
|
||||||
|
}
|
||||||
|
|
||||||
|
double recovery = (currentHpUncapped - 1) / Math.Max(1, topLevelObjectCount);
|
||||||
|
|
||||||
|
if (!fail && recovery < hpRecoveryAvailable)
|
||||||
|
{
|
||||||
|
fail = true;
|
||||||
|
testDrop *= 0.96;
|
||||||
|
HpMultiplierNormal *= 1.01;
|
||||||
|
OnIterationFail?.Invoke($"FAILED drop {testDrop}: recovery too low ({recovery} < {hpRecoveryAvailable})");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fail)
|
||||||
|
{
|
||||||
|
OnIterationSuccess?.Invoke($"PASSED drop {testDrop}");
|
||||||
|
return testDrop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reduceHp(double amount)
|
||||||
|
{
|
||||||
|
currentHpUncapped = Math.Max(0, currentHpUncapped - amount);
|
||||||
|
currentHp = Math.Max(0, currentHp - amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void increaseHp(HitObject hitObject)
|
||||||
|
{
|
||||||
|
double amount = GetHealthIncreaseFor(hitObject, hitObject.CreateJudgement().MaxResult);
|
||||||
|
currentHpUncapped += amount;
|
||||||
|
currentHp = Math.Max(0, Math.Min(1, currentHp + amount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected sealed override double GetHealthIncreaseFor(JudgementResult result) => GetHealthIncreaseFor(result.HitObject, result.Type);
|
||||||
|
|
||||||
|
protected abstract IEnumerable<HitObject> EnumerateTopLevelHitObjects();
|
||||||
|
|
||||||
|
protected abstract IEnumerable<HitObject> EnumerateNestedHitObjects(HitObject hitObject);
|
||||||
|
|
||||||
|
protected abstract double GetHealthIncreaseFor(HitObject hitObject, HitResult result);
|
||||||
|
}
|
||||||
|
}
|
@ -46,12 +46,12 @@ namespace osu.Game.Screens.Edit.Components.Menus
|
|||||||
Padding = new MarginPadding(8),
|
Padding = new MarginPadding(8),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Sprite
|
new SpriteIcon
|
||||||
{
|
{
|
||||||
Size = new Vector2(26),
|
Size = new Vector2(26),
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Texture = textures.Get("Icons/Hexacons/editor"),
|
Icon = HexaconsIcons.Editor,
|
||||||
},
|
},
|
||||||
text = new TextFlowContainer
|
text = new TextFlowContainer
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -79,7 +80,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
{
|
{
|
||||||
Title = EditorSetupStrings.BeatmapSetup.ToLower();
|
Title = EditorSetupStrings.BeatmapSetup.ToLower();
|
||||||
Description = EditorSetupStrings.BeatmapSetupDescription;
|
Description = EditorSetupStrings.BeatmapSetupDescription;
|
||||||
IconTexture = "Icons/Hexacons/social";
|
Icon = HexaconsIcons.Social;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
@ -36,29 +34,29 @@ namespace osu.Game.Screens.Menu
|
|||||||
{
|
{
|
||||||
public partial class ButtonSystem : Container, IStateful<ButtonSystemState>, IKeyBindingHandler<GlobalAction>
|
public partial class ButtonSystem : Container, IStateful<ButtonSystemState>, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
public event Action<ButtonSystemState> StateChanged;
|
|
||||||
|
|
||||||
private readonly IBindable<bool> isIdle = new BindableBool();
|
|
||||||
|
|
||||||
public Action OnEdit;
|
|
||||||
public Action OnExit;
|
|
||||||
public Action OnBeatmapListing;
|
|
||||||
public Action OnSolo;
|
|
||||||
public Action OnSettings;
|
|
||||||
public Action OnMultiplayer;
|
|
||||||
public Action OnPlaylists;
|
|
||||||
|
|
||||||
public const float BUTTON_WIDTH = 140f;
|
public const float BUTTON_WIDTH = 140f;
|
||||||
public const float WEDGE_WIDTH = 20;
|
public const float WEDGE_WIDTH = 20;
|
||||||
|
|
||||||
[CanBeNull]
|
public event Action<ButtonSystemState>? StateChanged;
|
||||||
private OsuLogo logo;
|
|
||||||
|
public Action? OnEditBeatmap;
|
||||||
|
public Action? OnEditSkin;
|
||||||
|
public Action? OnExit;
|
||||||
|
public Action? OnBeatmapListing;
|
||||||
|
public Action? OnSolo;
|
||||||
|
public Action? OnSettings;
|
||||||
|
public Action? OnMultiplayer;
|
||||||
|
public Action? OnPlaylists;
|
||||||
|
|
||||||
|
private readonly IBindable<bool> isIdle = new BindableBool();
|
||||||
|
|
||||||
|
private OsuLogo? logo;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Assign the <see cref="OsuLogo"/> that this ButtonSystem should manage the position of.
|
/// Assign the <see cref="OsuLogo"/> that this ButtonSystem should manage the position of.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logo">The instance of the logo to be assigned. If null, we are suspending from the screen that uses this ButtonSystem.</param>
|
/// <param name="logo">The instance of the logo to be assigned. If null, we are suspending from the screen that uses this ButtonSystem.</param>
|
||||||
public void SetOsuLogo(OsuLogo logo)
|
public void SetOsuLogo(OsuLogo? logo)
|
||||||
{
|
{
|
||||||
this.logo = logo;
|
this.logo = logo;
|
||||||
|
|
||||||
@ -84,9 +82,10 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
private readonly List<MainMenuButton> buttonsTopLevel = new List<MainMenuButton>();
|
private readonly List<MainMenuButton> buttonsTopLevel = new List<MainMenuButton>();
|
||||||
private readonly List<MainMenuButton> buttonsPlay = new List<MainMenuButton>();
|
private readonly List<MainMenuButton> buttonsPlay = new List<MainMenuButton>();
|
||||||
|
private readonly List<MainMenuButton> buttonsEdit = new List<MainMenuButton>();
|
||||||
|
|
||||||
private Sample sampleBackToLogo;
|
private Sample? sampleBackToLogo;
|
||||||
private Sample sampleLogoSwoosh;
|
private Sample? sampleLogoSwoosh;
|
||||||
|
|
||||||
private readonly LogoTrackingContainer logoTrackingContainer;
|
private readonly LogoTrackingContainer logoTrackingContainer;
|
||||||
|
|
||||||
@ -108,7 +107,8 @@ namespace osu.Game.Screens.Menu
|
|||||||
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel,
|
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel,
|
||||||
-WEDGE_WIDTH)
|
-WEDGE_WIDTH)
|
||||||
{
|
{
|
||||||
VisibleState = ButtonSystemState.Play,
|
VisibleStateMin = ButtonSystemState.Play,
|
||||||
|
VisibleStateMax = ButtonSystemState.Edit,
|
||||||
},
|
},
|
||||||
logoTrackingContainer.LogoFacade.With(d => d.Scale = new Vector2(0.74f))
|
logoTrackingContainer.LogoFacade.With(d => d.Scale = new Vector2(0.74f))
|
||||||
});
|
});
|
||||||
@ -116,31 +116,36 @@ namespace osu.Game.Screens.Menu
|
|||||||
buttonArea.Flow.CentreTarget = logoTrackingContainer.LogoFacade;
|
buttonArea.Flow.CentreTarget = logoTrackingContainer.LogoFacade;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
private OsuGame game { get; set; }
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private OsuGame? game { get; set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
private LoginOverlay loginOverlay { get; set; }
|
private LoginOverlay? loginOverlay { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio, IdleTracker idleTracker, GameHost host)
|
private void load(AudioManager audio, IdleTracker? idleTracker, GameHost host)
|
||||||
{
|
{
|
||||||
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
|
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
|
||||||
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M));
|
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M));
|
||||||
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
|
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
|
||||||
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
|
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
|
||||||
|
|
||||||
|
buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", HexaconsIcons.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B));
|
||||||
|
buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", HexaconsIcons.Editor, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S));
|
||||||
|
buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit);
|
||||||
|
|
||||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
|
||||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-default-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => State = ButtonSystemState.Edit, 0, Key.E));
|
||||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D));
|
||||||
|
|
||||||
if (host.CanExit)
|
if (host.CanExit)
|
||||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
|
||||||
|
|
||||||
buttonArea.AddRange(buttonsPlay);
|
buttonArea.AddRange(buttonsPlay);
|
||||||
|
buttonArea.AddRange(buttonsEdit);
|
||||||
buttonArea.AddRange(buttonsTopLevel);
|
buttonArea.AddRange(buttonsTopLevel);
|
||||||
|
|
||||||
buttonArea.ForEach(b =>
|
buttonArea.ForEach(b =>
|
||||||
@ -270,6 +275,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case ButtonSystemState.Edit:
|
||||||
case ButtonSystemState.Play:
|
case ButtonSystemState.Play:
|
||||||
StopSamplePlayback();
|
StopSamplePlayback();
|
||||||
backButton.TriggerClick();
|
backButton.TriggerClick();
|
||||||
@ -305,6 +311,10 @@ namespace osu.Game.Screens.Menu
|
|||||||
case ButtonSystemState.Play:
|
case ButtonSystemState.Play:
|
||||||
buttonsPlay.First().TriggerClick();
|
buttonsPlay.First().TriggerClick();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
case ButtonSystemState.Edit:
|
||||||
|
buttonsEdit.First().TriggerClick();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,6 +338,8 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
Logger.Log($"{nameof(ButtonSystem)}'s state changed from {lastState} to {state}");
|
Logger.Log($"{nameof(ButtonSystem)}'s state changed from {lastState} to {state}");
|
||||||
|
|
||||||
|
buttonArea.FinishTransforms(true);
|
||||||
|
|
||||||
using (buttonArea.BeginDelayedSequence(lastState == ButtonSystemState.Initial ? 150 : 0))
|
using (buttonArea.BeginDelayedSequence(lastState == ButtonSystemState.Initial ? 150 : 0))
|
||||||
{
|
{
|
||||||
buttonArea.ButtonSystemState = state;
|
buttonArea.ButtonSystemState = state;
|
||||||
@ -340,7 +352,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledDelegate logoDelayedAction;
|
private ScheduledDelegate? logoDelayedAction;
|
||||||
|
|
||||||
private void updateLogoState(ButtonSystemState lastState = ButtonSystemState.Initial)
|
private void updateLogoState(ButtonSystemState lastState = ButtonSystemState.Initial)
|
||||||
{
|
{
|
||||||
@ -414,6 +426,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
Initial,
|
Initial,
|
||||||
TopLevel,
|
TopLevel,
|
||||||
Play,
|
Play,
|
||||||
|
Edit,
|
||||||
EnteringMode,
|
EnteringMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ using osu.Game.Input.Bindings;
|
|||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.SkinEditor;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Screens.Backgrounds;
|
using osu.Game.Screens.Backgrounds;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
@ -93,6 +94,9 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
private Sample reappearSampleSwoosh;
|
private Sample reappearSampleSwoosh;
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private SkinEditorOverlay skinEditor { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics, AudioManager audio)
|
private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics, AudioManager audio)
|
||||||
{
|
{
|
||||||
@ -120,11 +124,15 @@ namespace osu.Game.Screens.Menu
|
|||||||
{
|
{
|
||||||
Buttons = new ButtonSystem
|
Buttons = new ButtonSystem
|
||||||
{
|
{
|
||||||
OnEdit = delegate
|
OnEditBeatmap = () =>
|
||||||
{
|
{
|
||||||
Beatmap.SetDefault();
|
Beatmap.SetDefault();
|
||||||
this.Push(new EditorLoader());
|
this.Push(new EditorLoader());
|
||||||
},
|
},
|
||||||
|
OnEditSkin = () =>
|
||||||
|
{
|
||||||
|
skinEditor?.Show();
|
||||||
|
},
|
||||||
OnSolo = loadSoloSongSelect,
|
OnSolo = loadSoloSongSelect,
|
||||||
OnMultiplayer = () => this.Push(new Multiplayer()),
|
OnMultiplayer = () => this.Push(new Multiplayer()),
|
||||||
OnPlaylists = () => this.Push(new Playlists()),
|
OnPlaylists = () => this.Push(new Playlists()),
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
@ -33,7 +31,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MainMenuButton : BeatSyncedContainer, IStateful<ButtonState>
|
public partial class MainMenuButton : BeatSyncedContainer, IStateful<ButtonState>
|
||||||
{
|
{
|
||||||
public event Action<ButtonState> StateChanged;
|
public event Action<ButtonState>? StateChanged;
|
||||||
|
|
||||||
public readonly Key[] TriggerKeys;
|
public readonly Key[] TriggerKeys;
|
||||||
|
|
||||||
@ -44,18 +42,28 @@ namespace osu.Game.Screens.Menu
|
|||||||
private readonly string sampleName;
|
private readonly string sampleName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The menu state for which we are visible for.
|
/// The menu state for which we are visible for (assuming only one).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ButtonSystemState VisibleState = ButtonSystemState.TopLevel;
|
public ButtonSystemState VisibleState
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
VisibleStateMin = value;
|
||||||
|
VisibleStateMax = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly Action clickAction;
|
public ButtonSystemState VisibleStateMin = ButtonSystemState.TopLevel;
|
||||||
private Sample sampleClick;
|
public ButtonSystemState VisibleStateMax = ButtonSystemState.TopLevel;
|
||||||
private Sample sampleHover;
|
|
||||||
private SampleChannel sampleChannel;
|
private readonly Action? clickAction;
|
||||||
|
private Sample? sampleClick;
|
||||||
|
private Sample? sampleHover;
|
||||||
|
private SampleChannel? sampleChannel;
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, params Key[] triggerKeys)
|
public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action? clickAction = null, float extraWidth = 0, params Key[] triggerKeys)
|
||||||
{
|
{
|
||||||
this.sampleName = sampleName;
|
this.sampleName = sampleName;
|
||||||
this.clickAction = clickAction;
|
this.clickAction = clickAction;
|
||||||
@ -315,9 +323,9 @@ namespace osu.Game.Screens.Menu
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (value == VisibleState)
|
if (value <= VisibleStateMax && value >= VisibleStateMin)
|
||||||
State = ButtonState.Expanded;
|
State = ButtonState.Expanded;
|
||||||
else if (value < VisibleState)
|
else if (value < VisibleStateMin)
|
||||||
State = ButtonState.Contracted;
|
State = ButtonState.Contracted;
|
||||||
else
|
else
|
||||||
State = ButtonState.Exploded;
|
State = ButtonState.Exploded;
|
||||||
|
@ -228,7 +228,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState)
|
protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState) => Schedule(() =>
|
||||||
{
|
{
|
||||||
var playerArea = instances.Single(i => i.UserId == userId);
|
var playerArea = instances.Single(i => i.UserId == userId);
|
||||||
|
|
||||||
@ -242,9 +242,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
playerArea.LoadScore(spectatorGameplayState.Score);
|
playerArea.LoadScore(spectatorGameplayState.Score);
|
||||||
|
});
|
||||||
|
|
||||||
|
protected override void FailGameplay(int userId)
|
||||||
|
{
|
||||||
|
// We probably want to visualise this in the future.
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void QuitGameplay(int userId)
|
protected override void QuitGameplay(int userId) => Schedule(() =>
|
||||||
{
|
{
|
||||||
RemoveUser(userId);
|
RemoveUser(userId);
|
||||||
|
|
||||||
@ -252,7 +257,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
|
|
||||||
instance.FadeColour(colours.Gray4, 400, Easing.OutQuint);
|
instance.FadeColour(colours.Gray4, 400, Easing.OutQuint);
|
||||||
syncManager.RemoveManagedClock(instance.SpectatorPlayerClock);
|
syncManager.RemoveManagedClock(instance.SpectatorPlayerClock);
|
||||||
}
|
});
|
||||||
|
|
||||||
public override bool OnBackButton()
|
public override bool OnBackButton()
|
||||||
{
|
{
|
||||||
|
@ -26,11 +26,22 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
public override LocalisableString Header => GameplayMenuOverlayStrings.FailedHeader;
|
public override LocalisableString Header => GameplayMenuOverlayStrings.FailedHeader;
|
||||||
|
|
||||||
|
private readonly bool showButtons;
|
||||||
|
|
||||||
|
public FailOverlay(bool showButtons = true)
|
||||||
|
{
|
||||||
|
this.showButtons = showButtons;
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
AddButton(GameplayMenuOverlayStrings.Retry, colours.YellowDark, () => OnRetry?.Invoke());
|
if (showButtons)
|
||||||
AddButton(GameplayMenuOverlayStrings.Quit, new Color4(170, 27, 39, 255), () => OnQuit?.Invoke());
|
{
|
||||||
|
AddButton(GameplayMenuOverlayStrings.Retry, colours.YellowDark, () => OnRetry?.Invoke());
|
||||||
|
AddButton(GameplayMenuOverlayStrings.Quit, new Color4(170, 27, 39, 255), () => OnQuit?.Invoke());
|
||||||
|
}
|
||||||
|
|
||||||
// from #10339 maybe this is a better visual effect
|
// from #10339 maybe this is a better visual effect
|
||||||
Add(new Container
|
Add(new Container
|
||||||
{
|
{
|
||||||
|
@ -155,7 +155,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
this.getLookup = getLookup;
|
this.getLookup = getLookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITexturedCharacterGlyph? Get(string fontName, char character)
|
public ITexturedCharacterGlyph? Get(string? fontName, char character)
|
||||||
{
|
{
|
||||||
string lookup = getLookup(character);
|
string lookup = getLookup(character);
|
||||||
var texture = textures.Get($"Gameplay/Fonts/{fontName}-{lookup}");
|
var texture = textures.Get($"Gameplay/Fonts/{fontName}-{lookup}");
|
||||||
|
@ -267,7 +267,7 @@ namespace osu.Game.Screens.Play
|
|||||||
createGameplayComponents(Beatmap.Value)
|
createGameplayComponents(Beatmap.Value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FailOverlay = new FailOverlay
|
FailOverlay = new FailOverlay(Configuration.AllowUserInteraction)
|
||||||
{
|
{
|
||||||
SaveReplay = async () => await prepareAndImportScoreAsync(true).ConfigureAwait(false),
|
SaveReplay = async () => await prepareAndImportScoreAsync(true).ConfigureAwait(false),
|
||||||
OnRetry = () => Restart(),
|
OnRetry = () => Restart(),
|
||||||
@ -894,6 +894,13 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
#region Fail Logic
|
#region Fail Logic
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when gameplay has permanently failed.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnFail()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
protected FailOverlay FailOverlay { get; private set; }
|
protected FailOverlay FailOverlay { get; private set; }
|
||||||
|
|
||||||
private FailAnimationContainer failAnimationContainer;
|
private FailAnimationContainer failAnimationContainer;
|
||||||
@ -923,8 +930,21 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
failAnimationContainer.Start();
|
failAnimationContainer.Start();
|
||||||
|
|
||||||
if (GameplayState.Mods.OfType<IApplicableFailOverride>().Any(m => m.RestartOnFail))
|
// Failures can be triggered either by a judgement, or by a mod.
|
||||||
Restart(true);
|
//
|
||||||
|
// For the case of a judgement, due to ordering considerations, ScoreProcessor will not have received
|
||||||
|
// the final judgement which triggered the failure yet (see DrawableRuleset.NewResult handling above).
|
||||||
|
//
|
||||||
|
// A schedule here ensures that any lingering judgements from the current frame are applied before we
|
||||||
|
// finalise the score as "failed".
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
ScoreProcessor.FailScore(Score.ScoreInfo);
|
||||||
|
OnFail();
|
||||||
|
|
||||||
|
if (GameplayState.Mods.OfType<IApplicableFailOverride>().Any(m => m.RestartOnFail))
|
||||||
|
Restart(true);
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -934,11 +954,6 @@ namespace osu.Game.Screens.Play
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void onFailComplete()
|
private void onFailComplete()
|
||||||
{
|
{
|
||||||
// fail completion is a good point to mark a score as failed,
|
|
||||||
// since the last judgement that caused the fail only applies to score processor after onFail.
|
|
||||||
// todo: this should probably be handled better.
|
|
||||||
ScoreProcessor.FailScore(Score.ScoreInfo);
|
|
||||||
|
|
||||||
GameplayClockContainer.Stop();
|
GameplayClockContainer.Stop();
|
||||||
|
|
||||||
FailOverlay.Retries = RestartCount;
|
FailOverlay.Retries = RestartCount;
|
||||||
|
@ -17,8 +17,8 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
protected override UserActivity InitialActivity => new UserActivity.SpectatingUser(Score.ScoreInfo);
|
protected override UserActivity InitialActivity => new UserActivity.SpectatingUser(Score.ScoreInfo);
|
||||||
|
|
||||||
public SoloSpectatorPlayer(Score score, PlayerConfiguration configuration = null)
|
public SoloSpectatorPlayer(Score score)
|
||||||
: base(score, configuration)
|
: base(score, new PlayerConfiguration { AllowUserInteraction = false })
|
||||||
{
|
{
|
||||||
this.score = score;
|
this.score = score;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -33,41 +30,40 @@ using osuTK;
|
|||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
[Cached(typeof(IPreviewTrackOwner))]
|
[Cached(typeof(IPreviewTrackOwner))]
|
||||||
public partial class SoloSpectator : SpectatorScreen, IPreviewTrackOwner
|
public partial class SoloSpectatorScreen : SpectatorScreen, IPreviewTrackOwner
|
||||||
{
|
{
|
||||||
[NotNull]
|
[Resolved]
|
||||||
private readonly APIUser targetUser;
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private PreviewTrackManager previewTrackManager { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private PreviewTrackManager previewTrackManager { get; set; }
|
private BeatmapManager beatmaps { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmaps { get; set; }
|
private BeatmapModelDownloader beatmapDownloader { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private BeatmapModelDownloader beatmapDownloader { get; set; }
|
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
private Container beatmapPanelContainer;
|
private Container beatmapPanelContainer = null!;
|
||||||
private RoundedButton watchButton;
|
private RoundedButton watchButton = null!;
|
||||||
private SettingsCheckbox automaticDownload;
|
private SettingsCheckbox automaticDownload = null!;
|
||||||
|
|
||||||
|
private readonly APIUser targetUser;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The player's immediate online gameplay state.
|
/// The player's immediate online gameplay state.
|
||||||
/// This doesn't always reflect the gameplay state being watched.
|
/// This doesn't always reflect the gameplay state being watched.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private SpectatorGameplayState immediateSpectatorGameplayState;
|
private SpectatorGameplayState? immediateSpectatorGameplayState;
|
||||||
|
|
||||||
private GetBeatmapSetRequest onlineBeatmapRequest;
|
private GetBeatmapSetRequest? onlineBeatmapRequest;
|
||||||
|
|
||||||
private APIBeatmapSet beatmapSet;
|
private APIBeatmapSet? beatmapSet;
|
||||||
|
|
||||||
public SoloSpectator([NotNull] APIUser targetUser)
|
public SoloSpectatorScreen(APIUser targetUser)
|
||||||
: base(targetUser.Id)
|
: base(targetUser.Id)
|
||||||
{
|
{
|
||||||
this.targetUser = targetUser;
|
this.targetUser = targetUser;
|
||||||
@ -168,28 +164,47 @@ namespace osu.Game.Screens.Play
|
|||||||
automaticDownload.Current.BindValueChanged(_ => checkForAutomaticDownload());
|
automaticDownload.Current.BindValueChanged(_ => checkForAutomaticDownload());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNewPlayingUserState(int userId, SpectatorState spectatorState)
|
protected override void OnNewPlayingUserState(int userId, SpectatorState spectatorState) => Schedule(() =>
|
||||||
{
|
{
|
||||||
clearDisplay();
|
clearDisplay();
|
||||||
showBeatmapPanel(spectatorState);
|
showBeatmapPanel(spectatorState);
|
||||||
}
|
});
|
||||||
|
|
||||||
protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState)
|
protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState) => Schedule(() =>
|
||||||
{
|
{
|
||||||
immediateSpectatorGameplayState = spectatorGameplayState;
|
immediateSpectatorGameplayState = spectatorGameplayState;
|
||||||
watchButton.Enabled.Value = true;
|
watchButton.Enabled.Value = true;
|
||||||
|
|
||||||
scheduleStart(spectatorGameplayState);
|
scheduleStart(spectatorGameplayState);
|
||||||
|
});
|
||||||
|
|
||||||
|
protected override void FailGameplay(int userId)
|
||||||
|
{
|
||||||
|
if (this.GetChildScreen() is SpectatorPlayerLoader loader)
|
||||||
|
{
|
||||||
|
if (loader.GetChildScreen() is SpectatorPlayer player)
|
||||||
|
{
|
||||||
|
player.AllowFail();
|
||||||
|
resetStartState();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
QuitGameplay(userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void QuitGameplay(int userId)
|
protected override void QuitGameplay(int userId)
|
||||||
|
{
|
||||||
|
// Importantly, don't schedule this call, as a child screen may be present (and will cause the schedule to not be run as expected).
|
||||||
|
this.MakeCurrent();
|
||||||
|
resetStartState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetStartState() => Schedule(() =>
|
||||||
{
|
{
|
||||||
scheduledStart?.Cancel();
|
scheduledStart?.Cancel();
|
||||||
immediateSpectatorGameplayState = null;
|
immediateSpectatorGameplayState = null;
|
||||||
watchButton.Enabled.Value = false;
|
|
||||||
|
|
||||||
clearDisplay();
|
clearDisplay();
|
||||||
}
|
});
|
||||||
|
|
||||||
private void clearDisplay()
|
private void clearDisplay()
|
||||||
{
|
{
|
||||||
@ -199,10 +214,12 @@ namespace osu.Game.Screens.Play
|
|||||||
previewTrackManager.StopAnyPlaying(this);
|
previewTrackManager.StopAnyPlaying(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledDelegate scheduledStart;
|
private ScheduledDelegate? scheduledStart;
|
||||||
|
|
||||||
private void scheduleStart(SpectatorGameplayState spectatorGameplayState)
|
private void scheduleStart(SpectatorGameplayState? spectatorGameplayState)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(spectatorGameplayState != null);
|
||||||
|
|
||||||
// This function may be called multiple times in quick succession once the screen becomes current again.
|
// This function may be called multiple times in quick succession once the screen becomes current again.
|
||||||
scheduledStart?.Cancel();
|
scheduledStart?.Cancel();
|
||||||
scheduledStart = Schedule(() =>
|
scheduledStart = Schedule(() =>
|
@ -25,7 +25,15 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private readonly Score score;
|
private readonly Score score;
|
||||||
|
|
||||||
protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap
|
protected override bool CheckModsAllowFailure()
|
||||||
|
{
|
||||||
|
if (!allowFail)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return base.CheckModsAllowFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool allowFail;
|
||||||
|
|
||||||
protected SpectatorPlayer(Score score, PlayerConfiguration configuration = null)
|
protected SpectatorPlayer(Score score, PlayerConfiguration configuration = null)
|
||||||
: base(configuration)
|
: base(configuration)
|
||||||
@ -60,6 +68,12 @@ namespace osu.Game.Screens.Play
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should be called when it is apparent that the player being spectated has failed.
|
||||||
|
/// This will subsequently stop blocking the fail screen from displaying (usually done out of safety).
|
||||||
|
/// </summary>
|
||||||
|
public void AllowFail() => allowFail = true;
|
||||||
|
|
||||||
protected override void StartGameplay()
|
protected override void StartGameplay()
|
||||||
{
|
{
|
||||||
base.StartGameplay();
|
base.StartGameplay();
|
||||||
|
@ -165,10 +165,22 @@ namespace osu.Game.Screens.Play
|
|||||||
spectatorClient.BeginPlaying(token, GameplayState, Score);
|
spectatorClient.BeginPlaying(token, GameplayState, Score);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnFail()
|
||||||
|
{
|
||||||
|
base.OnFail();
|
||||||
|
|
||||||
|
submitFromFailOrQuit();
|
||||||
|
}
|
||||||
|
|
||||||
public override bool OnExiting(ScreenExitEvent e)
|
public override bool OnExiting(ScreenExitEvent e)
|
||||||
{
|
{
|
||||||
bool exiting = base.OnExiting(e);
|
bool exiting = base.OnExiting(e);
|
||||||
|
submitFromFailOrQuit();
|
||||||
|
return exiting;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void submitFromFailOrQuit()
|
||||||
|
{
|
||||||
if (LoadedBeatmapSuccessfully)
|
if (LoadedBeatmapSuccessfully)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
@ -177,8 +189,6 @@ namespace osu.Game.Screens.Play
|
|||||||
spectatorClient.EndPlaying(GameplayState);
|
spectatorClient.EndPlaying(GameplayState);
|
||||||
}).FireAndForget();
|
}).FireAndForget();
|
||||||
}
|
}
|
||||||
|
|
||||||
return exiting;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@ -82,6 +83,7 @@ namespace osu.Game.Screens.Ranking
|
|||||||
private static readonly Color4 contracted_top_layer_colour = Color4Extensions.FromHex("#353535");
|
private static readonly Color4 contracted_top_layer_colour = Color4Extensions.FromHex("#353535");
|
||||||
private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535");
|
private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535");
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
public event Action<PanelState> StateChanged;
|
public event Action<PanelState> StateChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
@ -33,22 +30,27 @@ namespace osu.Game.Screens.Spectate
|
|||||||
private readonly List<int> users = new List<int>();
|
private readonly List<int> users = new List<int>();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmaps { get; set; }
|
private BeatmapManager beatmaps { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private RulesetStore rulesets { get; set; }
|
private RulesetStore rulesets { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private SpectatorClient spectatorClient { get; set; }
|
private SpectatorClient spectatorClient { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private UserLookupCache userLookupCache { get; set; }
|
private UserLookupCache userLookupCache { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private RealmAccess realm { get; set; } = null!;
|
||||||
|
|
||||||
private readonly IBindableDictionary<int, SpectatorState> userStates = new BindableDictionary<int, SpectatorState>();
|
private readonly IBindableDictionary<int, SpectatorState> userStates = new BindableDictionary<int, SpectatorState>();
|
||||||
|
|
||||||
private readonly Dictionary<int, APIUser> userMap = new Dictionary<int, APIUser>();
|
private readonly Dictionary<int, APIUser> userMap = new Dictionary<int, APIUser>();
|
||||||
private readonly Dictionary<int, SpectatorGameplayState> gameplayStates = new Dictionary<int, SpectatorGameplayState>();
|
private readonly Dictionary<int, SpectatorGameplayState> gameplayStates = new Dictionary<int, SpectatorGameplayState>();
|
||||||
|
|
||||||
|
private IDisposable? realmSubscription;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="SpectatorScreen"/>.
|
/// Creates a new <see cref="SpectatorScreen"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -58,11 +60,6 @@ namespace osu.Game.Screens.Spectate
|
|||||||
this.users.AddRange(users);
|
this.users.AddRange(users);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private RealmAccess realm { get; set; }
|
|
||||||
|
|
||||||
private IDisposable realmSubscription;
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -90,7 +87,7 @@ namespace osu.Game.Screens.Spectate
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beatmapsChanged(IRealmCollection<BeatmapSetInfo> items, ChangeSet changes)
|
private void beatmapsChanged(IRealmCollection<BeatmapSetInfo> items, ChangeSet? changes)
|
||||||
{
|
{
|
||||||
if (changes?.InsertedIndices == null) return;
|
if (changes?.InsertedIndices == null) return;
|
||||||
|
|
||||||
@ -109,7 +106,7 @@ namespace osu.Game.Screens.Spectate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs<int, SpectatorState> e)
|
private void onUserStatesChanged(object? sender, NotifyDictionaryChangedEventArgs<int, SpectatorState> e)
|
||||||
{
|
{
|
||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
@ -132,7 +129,7 @@ namespace osu.Game.Screens.Spectate
|
|||||||
switch (newState.State)
|
switch (newState.State)
|
||||||
{
|
{
|
||||||
case SpectatedUserState.Playing:
|
case SpectatedUserState.Playing:
|
||||||
Schedule(() => OnNewPlayingUserState(userId, newState));
|
OnNewPlayingUserState(userId, newState);
|
||||||
startGameplay(userId);
|
startGameplay(userId);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -140,6 +137,10 @@ namespace osu.Game.Screens.Spectate
|
|||||||
markReceivedAllFrames(userId);
|
markReceivedAllFrames(userId);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SpectatedUserState.Failed:
|
||||||
|
failGameplay(userId);
|
||||||
|
break;
|
||||||
|
|
||||||
case SpectatedUserState.Quit:
|
case SpectatedUserState.Quit:
|
||||||
quitGameplay(userId);
|
quitGameplay(userId);
|
||||||
break;
|
break;
|
||||||
@ -176,7 +177,7 @@ namespace osu.Game.Screens.Spectate
|
|||||||
var gameplayState = new SpectatorGameplayState(score, resolvedRuleset, beatmaps.GetWorkingBeatmap(resolvedBeatmap));
|
var gameplayState = new SpectatorGameplayState(score, resolvedRuleset, beatmaps.GetWorkingBeatmap(resolvedBeatmap));
|
||||||
|
|
||||||
gameplayStates[userId] = gameplayState;
|
gameplayStates[userId] = gameplayState;
|
||||||
Schedule(() => StartGameplay(userId, gameplayState));
|
StartGameplay(userId, gameplayState);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -188,6 +189,20 @@ namespace osu.Game.Screens.Spectate
|
|||||||
gameplayState.Score.Replay.HasReceivedAllFrames = true;
|
gameplayState.Score.Replay.HasReceivedAllFrames = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void failGameplay(int userId)
|
||||||
|
{
|
||||||
|
if (!userMap.ContainsKey(userId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!gameplayStates.ContainsKey(userId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
markReceivedAllFrames(userId);
|
||||||
|
|
||||||
|
gameplayStates.Remove(userId);
|
||||||
|
FailGameplay(userId);
|
||||||
|
}
|
||||||
|
|
||||||
private void quitGameplay(int userId)
|
private void quitGameplay(int userId)
|
||||||
{
|
{
|
||||||
if (!userMap.ContainsKey(userId))
|
if (!userMap.ContainsKey(userId))
|
||||||
@ -199,29 +214,39 @@ namespace osu.Game.Screens.Spectate
|
|||||||
markReceivedAllFrames(userId);
|
markReceivedAllFrames(userId);
|
||||||
|
|
||||||
gameplayStates.Remove(userId);
|
gameplayStates.Remove(userId);
|
||||||
Schedule(() => QuitGameplay(userId));
|
QuitGameplay(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a spectated user's state has changed to a new state indicating the player is currently playing.
|
/// Invoked when a spectated user's state has changed to a new state indicating the player is currently playing.
|
||||||
|
/// Thread safety is not guaranteed – should be scheduled as required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userId">The user whose state has changed.</param>
|
/// <param name="userId">The user whose state has changed.</param>
|
||||||
/// <param name="spectatorState">The new state.</param>
|
/// <param name="spectatorState">The new state.</param>
|
||||||
protected abstract void OnNewPlayingUserState(int userId, [NotNull] SpectatorState spectatorState);
|
protected abstract void OnNewPlayingUserState(int userId, SpectatorState spectatorState);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts gameplay for a user.
|
/// Starts gameplay for a user.
|
||||||
|
/// Thread safety is not guaranteed – should be scheduled as required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userId">The user to start gameplay for.</param>
|
/// <param name="userId">The user to start gameplay for.</param>
|
||||||
/// <param name="spectatorGameplayState">The gameplay state.</param>
|
/// <param name="spectatorGameplayState">The gameplay state.</param>
|
||||||
protected abstract void StartGameplay(int userId, [NotNull] SpectatorGameplayState spectatorGameplayState);
|
protected abstract void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Quits gameplay for a user.
|
/// Quits gameplay for a user.
|
||||||
|
/// Thread safety is not guaranteed – should be scheduled as required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userId">The user to quit gameplay for.</param>
|
/// <param name="userId">The user to quit gameplay for.</param>
|
||||||
protected abstract void QuitGameplay(int userId);
|
protected abstract void QuitGameplay(int userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fails gameplay for a user.
|
||||||
|
/// Thread safety is not guaranteed – should be scheduled as required.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user to fail gameplay for.</param>
|
||||||
|
protected abstract void FailGameplay(int userId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stops spectating a user.
|
/// Stops spectating a user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -243,7 +268,7 @@ namespace osu.Game.Screens.Spectate
|
|||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
if (spectatorClient != null)
|
if (spectatorClient.IsNotNull())
|
||||||
{
|
{
|
||||||
foreach ((int userId, var _) in userMap)
|
foreach ((int userId, var _) in userMap)
|
||||||
spectatorClient.StopWatchingUser(userId);
|
spectatorClient.StopWatchingUser(userId);
|
||||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Skinning
|
|||||||
this.maxSize = maxSize;
|
this.maxSize = maxSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITexturedCharacterGlyph? Get(string fontName, char character)
|
public ITexturedCharacterGlyph? Get(string? fontName, char character)
|
||||||
{
|
{
|
||||||
string lookup = getLookupName(character);
|
string lookup = getLookupName(character);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user