mirror of
https://github.com/ppy/osu.git
synced 2026-05-23 15:00:46 +08:00
Tidy up ResultsScreen (#37242)
Just the bare minimum code quality so I can start working on these classes.. Please push back if this doesn't seem better than what was already there. This is mostly autopilot fixing for me based on how I've been writing code for osu! to date. There are changes to the load process but nothing which should cause issues, I hope.
This commit is contained in:
committed by
GitHub
Unverified
parent
9c43739228
commit
4b8fa892ea
@@ -0,0 +1,576 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.RankedPlay;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Components;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay
|
||||
{
|
||||
public partial class ResultsScreen
|
||||
{
|
||||
private partial class MainPanel : CompositeDrawable
|
||||
{
|
||||
public required ScoreInfo PlayerScore { get; init; }
|
||||
public required ScoreInfo OpponentScore { get; init; }
|
||||
public required RankedPlayDamageInfo PlayerDamageInfo { get; init; }
|
||||
public required RankedPlayDamageInfo OpponentDamageInfo { get; init; }
|
||||
|
||||
[Resolved]
|
||||
private RankedPlayMatchInfo matchInfo { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colour { get; set; } = null!;
|
||||
|
||||
private static Vector2 cardSize => new Vector2(950, 550);
|
||||
|
||||
private readonly Bindable<Visibility> cornerPieceVisibility = new Bindable<Visibility>();
|
||||
private readonly Bindable<float> scoreBarProgress = new Bindable<float>();
|
||||
|
||||
private PanelScaffold panelScaffold = null!;
|
||||
private Box flash = null!;
|
||||
private ScoreDetails playerScoreDetails = null!;
|
||||
private ScoreDetails opponentScoreDetails = null!;
|
||||
private RankedPlayScoreCounter playerScoreCounter = null!;
|
||||
private RankedPlayScoreCounter opponentScoreCounter = null!;
|
||||
private RankedPlayScoreCounter damageCounter = null!;
|
||||
private OsuSpriteText flyingDamageText = null!;
|
||||
private ScoreBar playerScoreBar = null!;
|
||||
private ScoreBar opponentScoreBar = null!;
|
||||
private OsuSpriteText roundNumber = null!;
|
||||
private RankedPlayUserDisplay playerUserDisplay = null!;
|
||||
private RankedPlayUserDisplay opponentUserDisplay = null!;
|
||||
|
||||
private RankedPlayDamageInfo losingDamageInfo = null!;
|
||||
|
||||
private Sample resultsAppearSample = null!;
|
||||
private Sample dmgFlySample = null!;
|
||||
private Sample dmgHitSample = null!;
|
||||
private Sample hpDownSample = null!;
|
||||
private Sample playerAppearSample = null!;
|
||||
private Sample pseudoScoreCounterSample = null!;
|
||||
private Sample scoreTickSample = null!;
|
||||
private Sample gradePassSample = null!;
|
||||
private Sample gradePassSsSample = null!;
|
||||
private Sample gradeFailSample = null!;
|
||||
private Sample gradeFailDSample = null!;
|
||||
private SampleChannel? playerScoreTickChannel;
|
||||
private SampleChannel? opponentScoreTickChannel;
|
||||
private readonly BindableDouble playerScoreTickPitch = new BindableDouble();
|
||||
private readonly BindableDouble opponentScoreTickPitch = new BindableDouble();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
// this works under the assumption that only one player can receive damage each round
|
||||
losingDamageInfo = matchInfo.RoomState.Users
|
||||
.Select(it => it.Value.DamageInfo)
|
||||
.OfType<RankedPlayDamageInfo>()
|
||||
.MaxBy(it => it.Damage)!;
|
||||
|
||||
AddInternal(panelScaffold = new PanelScaffold
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children =
|
||||
[
|
||||
new RankedPlayCornerPiece(RankedPlayColourScheme.BLUE, Anchor.BottomLeft)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
State = { BindTarget = cornerPieceVisibility },
|
||||
Child = playerUserDisplay = new RankedPlayUserDisplay(PlayerScore.User, Anchor.BottomLeft, RankedPlayColourScheme.BLUE)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Health = { Value = PlayerDamageInfo.OldLife }
|
||||
}
|
||||
},
|
||||
new RankedPlayCornerPiece(RankedPlayColourScheme.RED, Anchor.BottomRight)
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
State = { BindTarget = cornerPieceVisibility },
|
||||
Child = opponentUserDisplay = new RankedPlayUserDisplay(OpponentScore.User, Anchor.BottomRight, RankedPlayColourScheme.RED)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Health = { Value = OpponentDamageInfo.OldLife }
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 110,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Padding = new MarginPadding { Bottom = 30 },
|
||||
Child = roundNumber = new OsuSpriteText
|
||||
{
|
||||
Text = $"Round {matchInfo.CurrentRound}",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(size: 36, weight: FontWeight.Bold, typeface: Typeface.TorusAlternate),
|
||||
Alpha = 0,
|
||||
},
|
||||
},
|
||||
new GridContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = cardSize,
|
||||
Padding = new MarginPadding { Bottom = 110, Top = 60, Horizontal = 60 },
|
||||
ColumnDimensions =
|
||||
[
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 40),
|
||||
new Dimension(GridSizeMode.Absolute, 60),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(GridSizeMode.Absolute, 60),
|
||||
new Dimension(GridSizeMode.Absolute, 40),
|
||||
new Dimension(),
|
||||
],
|
||||
Content = new Drawable?[][]
|
||||
{
|
||||
[
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions =
|
||||
[
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
],
|
||||
Content = new Drawable[][]
|
||||
{
|
||||
[
|
||||
playerScoreDetails = new ScoreDetails(PlayerScore, RankedPlayColourScheme.BLUE)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
},
|
||||
],
|
||||
[
|
||||
playerScoreCounter = new RankedPlayScoreCounter(numDigits(PlayerScore.TotalScore))
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 60, fixedWidth: true),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Spacing = new Vector2(-4),
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
null,
|
||||
playerScoreBar = new ScoreBar(RankedPlayColourScheme.BLUE)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.05f,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Alpha = 0,
|
||||
},
|
||||
null,
|
||||
opponentScoreBar = new ScoreBar(RankedPlayColourScheme.RED)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.05f,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Alpha = 0,
|
||||
},
|
||||
null,
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions =
|
||||
[
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
],
|
||||
Content = new Drawable[][]
|
||||
{
|
||||
[
|
||||
opponentScoreDetails = new ScoreDetails(OpponentScore, RankedPlayColourScheme.RED)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
},
|
||||
],
|
||||
[
|
||||
opponentScoreCounter = new RankedPlayScoreCounter(numDigits(OpponentScore.TotalScore))
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 60, fixedWidth: true),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Spacing = new Vector2(-4),
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
flash = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
],
|
||||
BottomOrnament =
|
||||
{
|
||||
Size = new Vector2(200, 60),
|
||||
Alpha = 0,
|
||||
Children =
|
||||
[
|
||||
new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children =
|
||||
[
|
||||
damageCounter = new RankedPlayScoreCounter(numDigits(losingDamageInfo.Damage))
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 36, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||
Spacing = new Vector2(-2),
|
||||
},
|
||||
flyingDamageText = new OsuSpriteText
|
||||
{
|
||||
Text = FormattableString.Invariant($"{losingDamageInfo.Damage:N0}"),
|
||||
Font = OsuFont.GetFont(size: 36, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||
Spacing = new Vector2(-2),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BypassAutoSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
BypassAutoSizeAxes = Axes.Both,
|
||||
Text = $"{matchInfo.RoomState.DamageMultiplier.ToStandardFormattedString(maxDecimalDigits: 1)}x",
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 42),
|
||||
Rotation = 30,
|
||||
Alpha = 0,
|
||||
Colour = colour.RedLight
|
||||
},
|
||||
]
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = Precision.AlmostEquals(matchInfo.RoomState.DamageMultiplier, 1)
|
||||
? "Damage"
|
||||
: $"Damage {matchInfo.RoomState.DamageMultiplier.ToStandardFormattedString(maxDecimalDigits: 1)}x",
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 22),
|
||||
},
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
resultsAppearSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/results-appear");
|
||||
dmgFlySample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/dmg-fly");
|
||||
dmgHitSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/dmg-hit");
|
||||
hpDownSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/hp-down");
|
||||
playerAppearSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/players-appear");
|
||||
pseudoScoreCounterSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/pseudo-score-counter");
|
||||
scoreTickSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/score-tick");
|
||||
gradePassSample = audio.Samples.Get(@"Results/rank-impact-pass");
|
||||
gradePassSsSample = audio.Samples.Get(@"Results/rank-impact-pass-ss");
|
||||
gradeFailSample = audio.Samples.Get(@"Results/rank-impact-fail");
|
||||
gradeFailDSample = audio.Samples.Get(@"Results/rank-impact-fail-d");
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
playAnimation();
|
||||
}
|
||||
|
||||
private void playAnimation()
|
||||
{
|
||||
const double text_movement_duration = 400;
|
||||
|
||||
double delay = 0;
|
||||
|
||||
resultsAppearSample.Play();
|
||||
|
||||
panelScaffold.FadeIn(100)
|
||||
.ResizeTo(0)
|
||||
.ResizeTo(cardSize with { Y = 30 }, 600, Easing.OutExpo)
|
||||
// deliberately cutting this delay 300ms short so the vertical resize interrupts the horizontal one
|
||||
.Delay(300)
|
||||
.ResizeHeightTo(cardSize.Y, 800, Easing.OutExpo);
|
||||
|
||||
flash.Delay(150).FadeOut(600, Easing.Out);
|
||||
|
||||
using (BeginDelayedSequence(700))
|
||||
{
|
||||
roundNumber.FadeIn(600);
|
||||
playerScoreCounter.FadeIn(600);
|
||||
opponentScoreCounter.FadeIn(600);
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
cornerPieceVisibility.Value = Visibility.Visible;
|
||||
playerAppearSample.Play();
|
||||
});
|
||||
}
|
||||
|
||||
using (BeginDelayedSequence(900))
|
||||
{
|
||||
panelScaffold.BottomOrnament
|
||||
.FadeIn(300)
|
||||
.ResizeWidthTo(cardSize.X - 550, 600, Easing.OutExpo);
|
||||
}
|
||||
|
||||
delay += 1000;
|
||||
|
||||
using (BeginDelayedSequence(delay))
|
||||
{
|
||||
const double score_text_duration = 2000;
|
||||
|
||||
playerScoreCounter.TransformValueTo(PlayerScore.TotalScore, score_text_duration - 500);
|
||||
opponentScoreCounter.TransformValueTo(OpponentScore.TotalScore, score_text_duration - 500);
|
||||
|
||||
damageCounter.TransformValueTo(losingDamageInfo.Damage, score_text_duration - 500);
|
||||
|
||||
long maxAchievableScore = Math.Max(
|
||||
Math.Max(PlayerScore.TotalScore, OpponentScore.TotalScore),
|
||||
1_000_000
|
||||
);
|
||||
|
||||
float playerScorePercent = (float)PlayerScore.TotalScore / maxAchievableScore;
|
||||
float opponentScorePercent = (float)OpponentScore.TotalScore / maxAchievableScore;
|
||||
float maxScorePercent = Math.Max(playerScorePercent, opponentScorePercent);
|
||||
|
||||
playerScoreBar.FadeIn(100);
|
||||
opponentScoreBar.FadeIn(100);
|
||||
|
||||
playerScoreTickChannel ??= scoreTickSample.GetChannel();
|
||||
playerScoreTickChannel.Balance.Value = -OsuGameBase.SFX_STEREO_STRENGTH;
|
||||
playerScoreTickChannel.Frequency.BindTarget = playerScoreTickPitch;
|
||||
playerScoreTickPitch.Value = 0.5f;
|
||||
playerScoreTickChannel.Looping = true;
|
||||
|
||||
opponentScoreTickChannel ??= scoreTickSample.GetChannel();
|
||||
opponentScoreTickChannel.Balance.Value = OsuGameBase.SFX_STEREO_STRENGTH;
|
||||
opponentScoreTickChannel.Frequency.BindTarget = opponentScoreTickPitch;
|
||||
opponentScoreTickPitch.Value = 0.5f;
|
||||
opponentScoreTickChannel.Looping = true;
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
if (losingDamageInfo.Damage > 0)
|
||||
pseudoScoreCounterSample.Play();
|
||||
|
||||
if (PlayerScore.TotalScore > 0)
|
||||
playerScoreTickChannel.Play();
|
||||
|
||||
if (OpponentScore.TotalScore > 0)
|
||||
opponentScoreTickChannel.Play();
|
||||
});
|
||||
|
||||
this.TransformBindableTo(scoreBarProgress, maxScorePercent, score_text_duration, new CubicBezierEasingFunction(easeIn: 0.4, easeOut: 1));
|
||||
this.TransformBindableTo(playerScoreTickPitch, 0.5f + playerScorePercent, score_text_duration, Easing.OutCubic);
|
||||
this.TransformBindableTo(opponentScoreTickPitch, 0.5f + opponentScorePercent, score_text_duration, Easing.OutCubic);
|
||||
|
||||
// safety timeout to ensure scoreTicks don't play forever
|
||||
Scheduler.AddDelayed(() =>
|
||||
{
|
||||
if (playerScoreTickChannel != null)
|
||||
playerScoreTickChannel.Looping = false;
|
||||
|
||||
if (opponentScoreTickChannel != null)
|
||||
opponentScoreTickChannel.Looping = false;
|
||||
}, score_text_duration + 500);
|
||||
|
||||
scoreBarProgress.BindValueChanged(e =>
|
||||
{
|
||||
playerScoreBar.Height = float.Lerp(0.05f, 1f, Math.Min(e.NewValue, playerScorePercent));
|
||||
opponentScoreBar.Height = float.Lerp(0.05f, 1f, Math.Min(e.NewValue, opponentScorePercent));
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
if (playerScoreTickChannel != null && playerScoreBar.Height >= playerScorePercent)
|
||||
playerScoreTickChannel.Looping = false;
|
||||
|
||||
if (opponentScoreTickChannel != null && opponentScoreBar.Height >= opponentScorePercent)
|
||||
opponentScoreTickChannel.Looping = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
delay += 2200;
|
||||
|
||||
using (BeginDelayedSequence(delay))
|
||||
{
|
||||
playerScoreDetails.FadeIn(300);
|
||||
opponentScoreDetails.FadeIn(300);
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
SampleChannel playerRankChannel = getRankSample(PlayerScore.Rank).GetChannel();
|
||||
playerRankChannel.Balance.Value = -OsuGameBase.SFX_STEREO_STRENGTH;
|
||||
playerRankChannel.Play();
|
||||
|
||||
SampleChannel opponentRankChannel = getRankSample(OpponentScore.Rank).GetChannel();
|
||||
opponentRankChannel.Balance.Value = OsuGameBase.SFX_STEREO_STRENGTH;
|
||||
opponentRankChannel.Play();
|
||||
});
|
||||
}
|
||||
|
||||
delay += 800;
|
||||
|
||||
bool playerTookDamage = OpponentScore.TotalScore > PlayerScore.TotalScore;
|
||||
double loserPanDirection = playerTookDamage ? -OsuGameBase.SFX_STEREO_STRENGTH : OsuGameBase.SFX_STEREO_STRENGTH;
|
||||
|
||||
using (BeginDelayedSequence(delay))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
RankedPlayUserDisplay userDisplay =
|
||||
PlayerScore.TotalScore > OpponentScore.TotalScore
|
||||
? opponentUserDisplay
|
||||
: playerUserDisplay;
|
||||
|
||||
Vector2 screenSpacePosition = userDisplay.HealthDisplay.ScreenSpaceImpactPosition;
|
||||
|
||||
var position1 = flyingDamageText.Parent!.ToLocalSpace(screenSpacePosition) - flyingDamageText.AnchorPosition;
|
||||
|
||||
damageCounter.FadeOut()
|
||||
.Delay(200)
|
||||
.FadeIn(300)
|
||||
.ScaleTo(0.9f)
|
||||
.ScaleTo(1f, 300, Easing.OutElasticHalf);
|
||||
|
||||
var dmgFlyChannel = dmgFlySample.GetChannel();
|
||||
this.TransformBindableTo(dmgFlyChannel.Balance, loserPanDirection, text_movement_duration, Easing.InCubic);
|
||||
dmgFlyChannel.Play();
|
||||
|
||||
flyingDamageText.FadeIn()
|
||||
.MoveTo(position1, text_movement_duration, Easing.InCubic)
|
||||
.ScaleTo(0.75f, text_movement_duration, new CubicBezierEasingFunction(easeIn: 0.35, easeOut: 0.5))
|
||||
.RotateTo(12 * Math.Sign(position1.X), text_movement_duration, new CubicBezierEasingFunction(easeIn: 0.35, easeOut: 0.5))
|
||||
.Then()
|
||||
.FadeOut();
|
||||
|
||||
Scheduler.AddDelayed(() =>
|
||||
{
|
||||
var dmgHitChannel = dmgHitSample.GetChannel();
|
||||
dmgHitChannel.Balance.Value = loserPanDirection;
|
||||
dmgHitChannel.Play();
|
||||
|
||||
userDisplay.Shake(shakeDuration: 60, shakeMagnitude: 2, maximumLength: 120);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var particle = new DamageParticle
|
||||
{
|
||||
Size = new Vector2(RNG.NextSingle(5, 15)),
|
||||
Origin = Anchor.Centre,
|
||||
Position = ToLocalSpace(screenSpacePosition),
|
||||
Rotation = RNG.NextSingle(0, 360),
|
||||
Blending = BlendingParameters.Additive,
|
||||
};
|
||||
|
||||
AddInternal(particle);
|
||||
|
||||
particle.FadeOut(600)
|
||||
.ScaleTo(0, 600)
|
||||
.RotateTo(particle.Rotation + RNG.NextSingle(-20, 20), 600)
|
||||
.FadeColour(Color4.Red, 600)
|
||||
.Expire();
|
||||
}
|
||||
}, text_movement_duration);
|
||||
});
|
||||
}
|
||||
|
||||
delay += text_movement_duration;
|
||||
|
||||
using (BeginDelayedSequence(delay))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
playerUserDisplay.Health.Value = PlayerDamageInfo.NewLife;
|
||||
opponentUserDisplay.Health.Value = OpponentDamageInfo.NewLife;
|
||||
|
||||
Scheduler.AddDelayed(() =>
|
||||
{
|
||||
var hpDecreaseChannel = hpDownSample.GetChannel();
|
||||
hpDecreaseChannel.Balance.Value = loserPanDirection;
|
||||
hpDecreaseChannel.Play();
|
||||
}, 900);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Sample getRankSample(ScoreRank rank)
|
||||
{
|
||||
switch (rank)
|
||||
{
|
||||
default:
|
||||
case ScoreRank.D:
|
||||
return gradeFailDSample;
|
||||
|
||||
case ScoreRank.C:
|
||||
case ScoreRank.B:
|
||||
return gradeFailSample;
|
||||
|
||||
case ScoreRank.A:
|
||||
case ScoreRank.S:
|
||||
case ScoreRank.SH:
|
||||
return gradePassSample;
|
||||
|
||||
case ScoreRank.X:
|
||||
case ScoreRank.XH:
|
||||
return gradePassSsSample;
|
||||
}
|
||||
}
|
||||
|
||||
private static int numDigits(long value)
|
||||
{
|
||||
if (value <= 0)
|
||||
return 1;
|
||||
|
||||
return (int)Math.Floor(Math.Log10(value)) + 1;
|
||||
}
|
||||
|
||||
private partial class DamageParticle : Triangle
|
||||
{
|
||||
private Vector2 velocity = new Vector2(RNG.NextSingle(-0.3f, 0.3f), RNG.NextSingle(-0.3f, 0.3f));
|
||||
|
||||
private Vector2 gravity => new Vector2(0, 0.0002f);
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
velocity += gravity * (float)Time.Elapsed;
|
||||
Position += velocity * (float)Time.Elapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,37 +3,23 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.RankedPlay;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Components;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay
|
||||
{
|
||||
@@ -49,15 +35,15 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapLookupCache beatmapLookupCache { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private ScoreManager scoreManager { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RankedPlayMatchInfo matchInfo { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> globalRuleset { get; set; } = null!;
|
||||
|
||||
@@ -81,51 +67,64 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay
|
||||
|
||||
loadingSpinner.Show();
|
||||
|
||||
queryScores().FireAndForget();
|
||||
fetchFinalScores().FireAndForget();
|
||||
}
|
||||
|
||||
private async Task queryScores()
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> working { get; set; } = null!;
|
||||
|
||||
private async Task fetchFinalScores()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (client.Room == null)
|
||||
return;
|
||||
|
||||
Task<APIBeatmap?> beatmapTask = beatmapLookupCache.GetBeatmapAsync(client.Room.CurrentPlaylistItem.BeatmapID);
|
||||
TaskCompletionSource<List<MultiplayerScore>> scoreTask = new TaskCompletionSource<List<MultiplayerScore>>();
|
||||
TaskCompletionSource<List<MultiplayerScore>> scoreLookup = new TaskCompletionSource<List<MultiplayerScore>>();
|
||||
|
||||
var request = new IndexPlaylistScoresRequest(client.Room.RoomID, client.Room.Settings.PlaylistItemId);
|
||||
request.Success += req => scoreTask.SetResult(req.Scores);
|
||||
request.Failure += scoreTask.SetException;
|
||||
|
||||
request.Success += req => scoreLookup.SetResult(req.Scores);
|
||||
request.Failure += scoreLookup.SetException;
|
||||
|
||||
api.Queue(request);
|
||||
|
||||
await Task.WhenAll(beatmapTask, scoreTask.Task).ConfigureAwait(false);
|
||||
List<MultiplayerScore> apiScores = await scoreLookup.Task.ConfigureAwait(false);
|
||||
|
||||
APIBeatmap? apiBeatmap = beatmapTask.GetResultSafely();
|
||||
List<MultiplayerScore> apiScores = scoreTask.Task.GetResultSafely();
|
||||
ScoreInfo[] scores = apiScores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, working.Value.BeatmapInfo)).ToArray();
|
||||
|
||||
if (apiBeatmap == null)
|
||||
return;
|
||||
Debug.Assert(scores.Length <= 2);
|
||||
|
||||
// Reference: PlaylistItemResultsScreen
|
||||
setScores(apiScores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, new BeatmapInfo
|
||||
int localUserId = api.LocalUser.Value.OnlineID;
|
||||
int opponentId = matchInfo.RoomState.Users.Keys.Single(it => it != localUserId);
|
||||
|
||||
ScoreInfo playerScore = scores.SingleOrDefault(s => s.UserID == localUserId) ?? new ScoreInfo
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty(apiBeatmap.Difficulty),
|
||||
Metadata =
|
||||
Rank = ScoreRank.F,
|
||||
Ruleset = globalRuleset.Value,
|
||||
User = new APIUser { Id = localUserId }
|
||||
};
|
||||
|
||||
ScoreInfo opponentScore = scores.SingleOrDefault(s => s.UserID == opponentId) ?? new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.F,
|
||||
Ruleset = globalRuleset.Value,
|
||||
User = new APIUser { Id = opponentId }
|
||||
};
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
LoadComponentAsync(new MainPanel
|
||||
{
|
||||
Artist = apiBeatmap.Metadata.Artist,
|
||||
Title = apiBeatmap.Metadata.Title,
|
||||
Author = new RealmUser
|
||||
{
|
||||
Username = apiBeatmap.Metadata.Author.Username,
|
||||
OnlineID = apiBeatmap.Metadata.Author.OnlineID,
|
||||
}
|
||||
},
|
||||
DifficultyName = apiBeatmap.DifficultyName,
|
||||
StarRating = apiBeatmap.StarRating,
|
||||
Length = apiBeatmap.Length,
|
||||
BPM = apiBeatmap.BPM
|
||||
})).ToArray());
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
// A little bit of room for the countdown timer...
|
||||
Margin = new MarginPadding { Top = 45 },
|
||||
PlayerScore = playerScore,
|
||||
OpponentScore = opponentScore,
|
||||
PlayerDamageInfo = matchInfo.RoomState.Users[localUserId].DamageInfo!,
|
||||
OpponentDamageInfo = matchInfo.RoomState.Users[opponentId].DamageInfo!,
|
||||
}, AddInternal);
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -137,604 +136,5 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay
|
||||
Scheduler.Add(() => loadingSpinner.Hide());
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private RankedPlayMatchInfo matchInfo { get; set; } = null!;
|
||||
|
||||
private void setScores(ScoreInfo[] scores) => Scheduler.Add(() =>
|
||||
{
|
||||
int playerId = api.LocalUser.Value.OnlineID;
|
||||
int opponentId = matchInfo.RoomState.Users.Keys.Single(it => it != playerId);
|
||||
|
||||
ScoreInfo playerScore = scores.SingleOrDefault(s => s.UserID == playerId) ?? new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.F,
|
||||
Ruleset = globalRuleset.Value,
|
||||
User = new APIUser { Id = playerId }
|
||||
};
|
||||
|
||||
ScoreInfo opponentScore = scores.SingleOrDefault(s => s.UserID == opponentId) ?? new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.F,
|
||||
Ruleset = globalRuleset.Value,
|
||||
User = new APIUser { Id = opponentId }
|
||||
};
|
||||
|
||||
AddInternal(new ResultScreenContent
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
// A little bit of room for the countdown timer...
|
||||
Margin = new MarginPadding { Top = 45 },
|
||||
PlayerScore = playerScore,
|
||||
OpponentScore = opponentScore,
|
||||
PlayerDamageInfo = matchInfo.RoomState.Users[playerId].DamageInfo!,
|
||||
OpponentDamageInfo = matchInfo.RoomState.Users[opponentId].DamageInfo!,
|
||||
});
|
||||
});
|
||||
|
||||
private partial class ResultScreenContent : CompositeDrawable
|
||||
{
|
||||
public required ScoreInfo PlayerScore { get; init; }
|
||||
public required ScoreInfo OpponentScore { get; init; }
|
||||
public required RankedPlayDamageInfo PlayerDamageInfo { get; init; }
|
||||
public required RankedPlayDamageInfo OpponentDamageInfo { get; init; }
|
||||
|
||||
[Resolved]
|
||||
private RankedPlayMatchInfo matchInfo { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colour { get; set; } = null!;
|
||||
|
||||
private static Vector2 cardSize => new Vector2(950, 550);
|
||||
|
||||
private readonly Bindable<Visibility> cornerPieceVisibility = new Bindable<Visibility>();
|
||||
private readonly Bindable<float> scoreBarProgress = new Bindable<float>();
|
||||
|
||||
private PanelScaffold panelScaffold = null!;
|
||||
private Box flash = null!;
|
||||
private ScoreDetails playerScoreDetails = null!;
|
||||
private ScoreDetails opponentScoreDetails = null!;
|
||||
private RankedPlayScoreCounter playerScoreCounter = null!;
|
||||
private RankedPlayScoreCounter opponentScoreCounter = null!;
|
||||
private RankedPlayScoreCounter damageCounter = null!;
|
||||
private OsuSpriteText flyingDamageText = null!;
|
||||
private ScoreBar playerScoreBar = null!;
|
||||
private ScoreBar opponentScoreBar = null!;
|
||||
private OsuSpriteText roundNumber = null!;
|
||||
private RankedPlayUserDisplay playerUserDisplay = null!;
|
||||
private RankedPlayUserDisplay opponentUserDisplay = null!;
|
||||
|
||||
private RankedPlayDamageInfo losingDamageInfo = null!;
|
||||
|
||||
private Sample resultsAppearSample = null!;
|
||||
private Sample dmgFlySample = null!;
|
||||
private Sample dmgHitSample = null!;
|
||||
private Sample hpDownSample = null!;
|
||||
private Sample playerAppearSample = null!;
|
||||
private Sample pseudoScoreCounterSample = null!;
|
||||
private Sample scoreTickSample = null!;
|
||||
private Sample gradePassSample = null!;
|
||||
private Sample gradePassSsSample = null!;
|
||||
private Sample gradeFailSample = null!;
|
||||
private Sample gradeFailDSample = null!;
|
||||
private SampleChannel? playerScoreTickChannel;
|
||||
private SampleChannel? opponentScoreTickChannel;
|
||||
private readonly BindableDouble playerScoreTickPitch = new BindableDouble();
|
||||
private readonly BindableDouble opponentScoreTickPitch = new BindableDouble();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
// this works under the assumption that only one player can receive damage each round
|
||||
losingDamageInfo = matchInfo.RoomState.Users
|
||||
.Select(it => it.Value.DamageInfo)
|
||||
.OfType<RankedPlayDamageInfo>()
|
||||
.MaxBy(it => it.Damage)!;
|
||||
|
||||
AddInternal(panelScaffold = new PanelScaffold
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children =
|
||||
[
|
||||
new RankedPlayCornerPiece(RankedPlayColourScheme.BLUE, Anchor.BottomLeft)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
State = { BindTarget = cornerPieceVisibility },
|
||||
Child = playerUserDisplay = new RankedPlayUserDisplay(PlayerScore.User, Anchor.BottomLeft, RankedPlayColourScheme.BLUE)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Health = { Value = PlayerDamageInfo.OldLife }
|
||||
}
|
||||
},
|
||||
new RankedPlayCornerPiece(RankedPlayColourScheme.RED, Anchor.BottomRight)
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
State = { BindTarget = cornerPieceVisibility },
|
||||
Child = opponentUserDisplay = new RankedPlayUserDisplay(OpponentScore.User, Anchor.BottomRight, RankedPlayColourScheme.RED)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Health = { Value = OpponentDamageInfo.OldLife }
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 110,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Padding = new MarginPadding { Bottom = 30 },
|
||||
Child = roundNumber = new OsuSpriteText
|
||||
{
|
||||
Text = $"Round {matchInfo.CurrentRound}",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(size: 36, weight: FontWeight.Bold, typeface: Typeface.TorusAlternate),
|
||||
Alpha = 0,
|
||||
},
|
||||
},
|
||||
new GridContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = cardSize,
|
||||
Padding = new MarginPadding { Bottom = 110, Top = 60, Horizontal = 60 },
|
||||
ColumnDimensions =
|
||||
[
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 40),
|
||||
new Dimension(GridSizeMode.Absolute, 60),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(GridSizeMode.Absolute, 60),
|
||||
new Dimension(GridSizeMode.Absolute, 40),
|
||||
new Dimension(),
|
||||
],
|
||||
Content = new Drawable?[][]
|
||||
{
|
||||
[
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions =
|
||||
[
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
],
|
||||
Content = new Drawable[][]
|
||||
{
|
||||
[
|
||||
playerScoreDetails = new ScoreDetails(PlayerScore, RankedPlayColourScheme.BLUE)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
},
|
||||
],
|
||||
[
|
||||
playerScoreCounter = new RankedPlayScoreCounter(numDigits(PlayerScore.TotalScore))
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 60, fixedWidth: true),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Spacing = new Vector2(-4),
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
null,
|
||||
playerScoreBar = new ScoreBar(RankedPlayColourScheme.BLUE)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.05f,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Alpha = 0,
|
||||
},
|
||||
null,
|
||||
opponentScoreBar = new ScoreBar(RankedPlayColourScheme.RED)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.05f,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Alpha = 0,
|
||||
},
|
||||
null,
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions =
|
||||
[
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
],
|
||||
Content = new Drawable[][]
|
||||
{
|
||||
[
|
||||
opponentScoreDetails = new ScoreDetails(OpponentScore, RankedPlayColourScheme.RED)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
},
|
||||
],
|
||||
[
|
||||
opponentScoreCounter = new RankedPlayScoreCounter(numDigits(OpponentScore.TotalScore))
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 60, fixedWidth: true),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Spacing = new Vector2(-4),
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
flash = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
],
|
||||
BottomOrnament =
|
||||
{
|
||||
Size = new Vector2(200, 60),
|
||||
Alpha = 0,
|
||||
Children =
|
||||
[
|
||||
new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children =
|
||||
[
|
||||
damageCounter = new RankedPlayScoreCounter(numDigits(losingDamageInfo.Damage))
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 36, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||
Spacing = new Vector2(-2),
|
||||
},
|
||||
flyingDamageText = new OsuSpriteText
|
||||
{
|
||||
Text = FormattableString.Invariant($"{losingDamageInfo.Damage:N0}"),
|
||||
Font = OsuFont.GetFont(size: 36, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||
Spacing = new Vector2(-2),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BypassAutoSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
BypassAutoSizeAxes = Axes.Both,
|
||||
Text = $"{matchInfo.RoomState.DamageMultiplier.ToStandardFormattedString(maxDecimalDigits: 1)}x",
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 42),
|
||||
Rotation = 30,
|
||||
Alpha = 0,
|
||||
Colour = colour.RedLight
|
||||
},
|
||||
]
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = Precision.AlmostEquals(matchInfo.RoomState.DamageMultiplier, 1)
|
||||
? "Damage"
|
||||
: $"Damage {matchInfo.RoomState.DamageMultiplier.ToStandardFormattedString(maxDecimalDigits: 1)}x",
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 22),
|
||||
},
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
resultsAppearSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/results-appear");
|
||||
dmgFlySample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/dmg-fly");
|
||||
dmgHitSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/dmg-hit");
|
||||
hpDownSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/hp-down");
|
||||
playerAppearSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/players-appear");
|
||||
pseudoScoreCounterSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/pseudo-score-counter");
|
||||
scoreTickSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/score-tick");
|
||||
gradePassSample = audio.Samples.Get(@"Results/rank-impact-pass");
|
||||
gradePassSsSample = audio.Samples.Get(@"Results/rank-impact-pass-ss");
|
||||
gradeFailSample = audio.Samples.Get(@"Results/rank-impact-fail");
|
||||
gradeFailDSample = audio.Samples.Get(@"Results/rank-impact-fail-d");
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
double delay = 0;
|
||||
|
||||
appear(ref delay);
|
||||
|
||||
animateCountersAndScoreBars(ref delay);
|
||||
|
||||
showScoreInfo(ref delay);
|
||||
|
||||
updateHealthBars(ref delay);
|
||||
}
|
||||
|
||||
private void appear(ref double delay)
|
||||
{
|
||||
resultsAppearSample.Play();
|
||||
|
||||
panelScaffold.FadeIn(100)
|
||||
.ResizeTo(0)
|
||||
.ResizeTo(cardSize with { Y = 30 }, 600, Easing.OutExpo)
|
||||
// deliberately cutting this delay 300ms short so the vertical resize interrupts the horizontal one
|
||||
.Delay(300)
|
||||
.ResizeHeightTo(cardSize.Y, 800, Easing.OutExpo);
|
||||
|
||||
flash.Delay(150).FadeOut(600, Easing.Out);
|
||||
|
||||
using (BeginDelayedSequence(700))
|
||||
{
|
||||
roundNumber.FadeIn(600);
|
||||
playerScoreCounter.FadeIn(600);
|
||||
opponentScoreCounter.FadeIn(600);
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
cornerPieceVisibility.Value = Visibility.Visible;
|
||||
playerAppearSample.Play();
|
||||
});
|
||||
}
|
||||
|
||||
using (BeginDelayedSequence(900))
|
||||
{
|
||||
panelScaffold.BottomOrnament
|
||||
.FadeIn(300)
|
||||
.ResizeWidthTo(cardSize.X - 550, 600, Easing.OutExpo);
|
||||
}
|
||||
|
||||
delay += 1000;
|
||||
}
|
||||
|
||||
private void animateCountersAndScoreBars(ref double delay)
|
||||
{
|
||||
using (BeginDelayedSequence(delay))
|
||||
{
|
||||
const double score_text_duration = 2000;
|
||||
|
||||
playerScoreCounter.TransformValueTo(PlayerScore.TotalScore, score_text_duration - 500);
|
||||
opponentScoreCounter.TransformValueTo(OpponentScore.TotalScore, score_text_duration - 500);
|
||||
|
||||
damageCounter.TransformValueTo(losingDamageInfo.Damage, score_text_duration - 500);
|
||||
|
||||
long maxAchievableScore = Math.Max(
|
||||
Math.Max(PlayerScore.TotalScore, OpponentScore.TotalScore),
|
||||
1_000_000
|
||||
);
|
||||
|
||||
float playerScorePercent = (float)PlayerScore.TotalScore / maxAchievableScore;
|
||||
float opponentScorePercent = (float)OpponentScore.TotalScore / maxAchievableScore;
|
||||
float maxScorePercent = Math.Max(playerScorePercent, opponentScorePercent);
|
||||
|
||||
playerScoreBar.FadeIn(100);
|
||||
opponentScoreBar.FadeIn(100);
|
||||
|
||||
playerScoreTickChannel ??= scoreTickSample.GetChannel();
|
||||
playerScoreTickChannel.Balance.Value = -OsuGameBase.SFX_STEREO_STRENGTH;
|
||||
playerScoreTickChannel.Frequency.BindTarget = playerScoreTickPitch;
|
||||
playerScoreTickPitch.Value = 0.5f;
|
||||
playerScoreTickChannel.Looping = true;
|
||||
|
||||
opponentScoreTickChannel ??= scoreTickSample.GetChannel();
|
||||
opponentScoreTickChannel.Balance.Value = OsuGameBase.SFX_STEREO_STRENGTH;
|
||||
opponentScoreTickChannel.Frequency.BindTarget = opponentScoreTickPitch;
|
||||
opponentScoreTickPitch.Value = 0.5f;
|
||||
opponentScoreTickChannel.Looping = true;
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
if (losingDamageInfo.Damage > 0)
|
||||
pseudoScoreCounterSample.Play();
|
||||
|
||||
if (PlayerScore.TotalScore > 0)
|
||||
playerScoreTickChannel.Play();
|
||||
|
||||
if (OpponentScore.TotalScore > 0)
|
||||
opponentScoreTickChannel.Play();
|
||||
});
|
||||
|
||||
this.TransformBindableTo(scoreBarProgress, maxScorePercent, score_text_duration, new CubicBezierEasingFunction(easeIn: 0.4, easeOut: 1));
|
||||
this.TransformBindableTo(playerScoreTickPitch, 0.5f + playerScorePercent, score_text_duration, Easing.OutCubic);
|
||||
this.TransformBindableTo(opponentScoreTickPitch, 0.5f + opponentScorePercent, score_text_duration, Easing.OutCubic);
|
||||
|
||||
// safety timeout to ensure scoreTicks don't play forever
|
||||
Scheduler.AddDelayed(() =>
|
||||
{
|
||||
if (playerScoreTickChannel != null)
|
||||
playerScoreTickChannel.Looping = false;
|
||||
|
||||
if (opponentScoreTickChannel != null)
|
||||
opponentScoreTickChannel.Looping = false;
|
||||
}, score_text_duration + 500);
|
||||
|
||||
scoreBarProgress.BindValueChanged(e =>
|
||||
{
|
||||
playerScoreBar.Height = float.Lerp(0.05f, 1f, Math.Min(e.NewValue, playerScorePercent));
|
||||
opponentScoreBar.Height = float.Lerp(0.05f, 1f, Math.Min(e.NewValue, opponentScorePercent));
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
if (playerScoreTickChannel != null && playerScoreBar.Height >= playerScorePercent)
|
||||
playerScoreTickChannel.Looping = false;
|
||||
|
||||
if (opponentScoreTickChannel != null && opponentScoreBar.Height >= opponentScorePercent)
|
||||
opponentScoreTickChannel.Looping = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
delay += 2200;
|
||||
}
|
||||
|
||||
private void updateHealthBars(ref double delay)
|
||||
{
|
||||
const double text_movement_duration = 400;
|
||||
|
||||
bool playerTookDamage = OpponentScore.TotalScore > PlayerScore.TotalScore;
|
||||
double loserPanDirection = playerTookDamage ? -OsuGameBase.SFX_STEREO_STRENGTH : OsuGameBase.SFX_STEREO_STRENGTH;
|
||||
|
||||
using (BeginDelayedSequence(delay))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
RankedPlayUserDisplay userDisplay =
|
||||
PlayerScore.TotalScore > OpponentScore.TotalScore
|
||||
? opponentUserDisplay
|
||||
: playerUserDisplay;
|
||||
|
||||
Vector2 screenSpacePosition = userDisplay.HealthDisplay.ScreenSpaceImpactPosition;
|
||||
|
||||
var position = flyingDamageText.Parent!.ToLocalSpace(screenSpacePosition) - flyingDamageText.AnchorPosition;
|
||||
|
||||
damageCounter.FadeOut()
|
||||
.Delay(200)
|
||||
.FadeIn(300)
|
||||
.ScaleTo(0.9f)
|
||||
.ScaleTo(1f, 300, Easing.OutElasticHalf);
|
||||
|
||||
var dmgFlyChannel = dmgFlySample.GetChannel();
|
||||
this.TransformBindableTo(dmgFlyChannel.Balance, loserPanDirection, text_movement_duration, Easing.InCubic);
|
||||
dmgFlyChannel.Play();
|
||||
|
||||
flyingDamageText.FadeIn()
|
||||
.MoveTo(position, text_movement_duration, Easing.InCubic)
|
||||
.ScaleTo(0.75f, text_movement_duration, new CubicBezierEasingFunction(easeIn: 0.35, easeOut: 0.5))
|
||||
.RotateTo(12 * Math.Sign(position.X), text_movement_duration, new CubicBezierEasingFunction(easeIn: 0.35, easeOut: 0.5))
|
||||
.Then()
|
||||
.FadeOut();
|
||||
|
||||
Scheduler.AddDelayed(() =>
|
||||
{
|
||||
var dmgHitChannel = dmgHitSample.GetChannel();
|
||||
dmgHitChannel.Balance.Value = loserPanDirection;
|
||||
dmgHitChannel.Play();
|
||||
|
||||
userDisplay.Shake(shakeDuration: 60, shakeMagnitude: 2, maximumLength: 120);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var particle = new DamageParticle
|
||||
{
|
||||
Size = new Vector2(RNG.NextSingle(5, 15)),
|
||||
Origin = Anchor.Centre,
|
||||
Position = ToLocalSpace(screenSpacePosition),
|
||||
Rotation = RNG.NextSingle(0, 360),
|
||||
Blending = BlendingParameters.Additive,
|
||||
};
|
||||
|
||||
AddInternal(particle);
|
||||
|
||||
particle.FadeOut(600)
|
||||
.ScaleTo(0, 600)
|
||||
.RotateTo(particle.Rotation + RNG.NextSingle(-20, 20), 600)
|
||||
.FadeColour(Color4.Red, 600)
|
||||
.Expire();
|
||||
}
|
||||
}, text_movement_duration);
|
||||
});
|
||||
}
|
||||
|
||||
delay += text_movement_duration;
|
||||
|
||||
using (BeginDelayedSequence(delay))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
playerUserDisplay.Health.Value = PlayerDamageInfo.NewLife;
|
||||
opponentUserDisplay.Health.Value = OpponentDamageInfo.NewLife;
|
||||
|
||||
Scheduler.AddDelayed(() =>
|
||||
{
|
||||
var hpDecreaseChannel = hpDownSample.GetChannel();
|
||||
hpDecreaseChannel.Balance.Value = loserPanDirection;
|
||||
hpDecreaseChannel.Play();
|
||||
}, 900);
|
||||
});
|
||||
}
|
||||
|
||||
delay += 400;
|
||||
}
|
||||
|
||||
private void showScoreInfo(ref double delay)
|
||||
{
|
||||
using (BeginDelayedSequence(delay))
|
||||
{
|
||||
playerScoreDetails.FadeIn(300);
|
||||
opponentScoreDetails.FadeIn(300);
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
SampleChannel playerRankChannel = getRankSample(PlayerScore.Rank).GetChannel();
|
||||
playerRankChannel.Balance.Value = -OsuGameBase.SFX_STEREO_STRENGTH;
|
||||
playerRankChannel.Play();
|
||||
|
||||
SampleChannel opponentRankChannel = getRankSample(OpponentScore.Rank).GetChannel();
|
||||
opponentRankChannel.Balance.Value = OsuGameBase.SFX_STEREO_STRENGTH;
|
||||
opponentRankChannel.Play();
|
||||
});
|
||||
}
|
||||
|
||||
delay += 800;
|
||||
}
|
||||
|
||||
private Sample getRankSample(ScoreRank rank)
|
||||
{
|
||||
switch (rank)
|
||||
{
|
||||
default:
|
||||
case ScoreRank.D:
|
||||
return gradeFailDSample;
|
||||
|
||||
case ScoreRank.C:
|
||||
case ScoreRank.B:
|
||||
return gradeFailSample;
|
||||
|
||||
case ScoreRank.A:
|
||||
case ScoreRank.S:
|
||||
case ScoreRank.SH:
|
||||
return gradePassSample;
|
||||
|
||||
case ScoreRank.X:
|
||||
case ScoreRank.XH:
|
||||
return gradePassSsSample;
|
||||
}
|
||||
}
|
||||
|
||||
private static int numDigits(long value)
|
||||
{
|
||||
if (value <= 0)
|
||||
return 1;
|
||||
|
||||
return (int)Math.Floor(Math.Log10(value)) + 1;
|
||||
}
|
||||
|
||||
private partial class DamageParticle : Triangle
|
||||
{
|
||||
private Vector2 velocity = new Vector2(RNG.NextSingle(-0.3f, 0.3f), RNG.NextSingle(-0.3f, 0.3f));
|
||||
|
||||
private Vector2 gravity => new Vector2(0, 0.0002f);
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
velocity += gravity * (float)Time.Elapsed;
|
||||
Position += velocity * (float)Time.Elapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user