diff --git a/osu.Game.Rulesets.IGPlayer.Tests/osu.Game.Rulesets.IGPlayer.Tests.csproj b/osu.Game.Rulesets.IGPlayer.Tests/osu.Game.Rulesets.IGPlayer.Tests.csproj
index 207ce60..8f0c051 100644
--- a/osu.Game.Rulesets.IGPlayer.Tests/osu.Game.Rulesets.IGPlayer.Tests.csproj
+++ b/osu.Game.Rulesets.IGPlayer.Tests/osu.Game.Rulesets.IGPlayer.Tests.csproj
@@ -13,6 +13,7 @@
+
diff --git a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Data/Menu/BMData.cs b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Data/Menu/BMData.cs
index 73b9d81..a4e48d7 100644
--- a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Data/Menu/BMData.cs
+++ b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Data/Menu/BMData.cs
@@ -1,3 +1,4 @@
+using System;
using Newtonsoft.Json;
using osu.Framework.Graphics.Audio;
using osu.Game.Beatmaps;
@@ -53,6 +54,7 @@ public void UpdateBeatmap(WorkingBeatmap beatmap)
this.MetaData.Mapper = metadata.Author.Username;
this.MetaData.DiffName = beatmap.BeatmapInfo.DifficultyName;
+ //todo: 适应Mod变更
var diffInf = beatmap.BeatmapInfo.Difficulty;
this.Stats.AR = diffInf.ApproachRate;
this.Stats.CS = diffInf.CircleSize;
@@ -60,6 +62,17 @@ public void UpdateBeatmap(WorkingBeatmap beatmap)
this.Stats.OD = diffInf.OverallDifficulty;
this.Stats.SR = (float)beatmap.BeatmapInfo.StarRating;
+ this.Stats.BPM.Max = (int)Math.Round(beatmap.Beatmap.ControlPointInfo.BPMMaximum);
+ this.Stats.BPM.Max = (int)Math.Round(beatmap.Beatmap.ControlPointInfo.BPMMinimum);
+
+ this.Path.AudioPath = "~ NOT IMPLEMENTED ~";
+ this.Path.BackgroundPath = "~ NOT IMPLEMENTED ~";
+ this.Path.BeatmapFile = "~ NOT IMPLEMENTED ~";
+ this.Path.BgPath = "~ NOT IMPLEMENTED ~";
+ this.Path.BeatmapFolder = "~ NOT IMPLEMENTED ~";
+
+ this.Stats.MaxCombo = -1;
+
short rankingStatus;
switch (beatmap.BeatmapInfo.Status)
diff --git a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Data/Menu/MenuModsData.cs b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Data/Menu/MenuModsData.cs
index 1cff6d9..2301d0f 100644
--- a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Data/Menu/MenuModsData.cs
+++ b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Data/Menu/MenuModsData.cs
@@ -1,7 +1,4 @@
-using System.Collections.Generic;
-using System.Linq;
using Newtonsoft.Json;
-using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Data.Menu
{
@@ -12,18 +9,5 @@ public struct MenuModsData
[JsonProperty("str")]
public string Acronyms;
-
- public void UpdateFrom(IReadOnlyList mods)
- {
- this.AppliedMods = mods.Count;
-
- if (mods.Count >= 1)
- {
- string str = mods.Aggregate("", (current, mod) => current + $"{mod.Acronym}");
- this.Acronyms = str;
- }
- else
- this.Acronyms = "NM";
- }
}
}
diff --git a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Data/Root.cs b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Data/Root.cs
index 2cb860e..dd7c502 100644
--- a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Data/Root.cs
+++ b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Data/Root.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Framework.Graphics.Audio;
using osu.Game.Beatmaps;
@@ -7,6 +8,7 @@
using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Data.Results;
using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Data.Settings;
using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Data.Tourney;
+using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Data
{
@@ -28,6 +30,10 @@ public class DataRoot : ISelfUpdatableFromBeatmap, ISelfUpdatableFromAudio
[JsonProperty("tourney")]
public TourneyValues TourneyValues = new TourneyValues();
+ public void ApplyMods(IList mods)
+ {
+ }
+
public void UpdateBeatmap(WorkingBeatmap workingBeatmap)
{
MenuValues.UpdateBeatmap(workingBeatmap);
diff --git a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/GosuCompatInjector.cs b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/GosuCompatInjector.cs
index 0722553..9d8a413 100644
--- a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/GosuCompatInjector.cs
+++ b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/GosuCompatInjector.cs
@@ -1,6 +1,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Logging;
using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Web;
using osuTK;
@@ -8,7 +9,7 @@ namespace osu.Game.Rulesets.IGPlayer.Feature.Gosumemory
{
public partial class GosuCompatInjector : Component
{
- public static Updater? Updater { get; private set; }
+ public TrackerHub? Updater { get; private set; }
private Container getContainerFromGame(string containerName, OsuGame game)
{
@@ -33,38 +34,46 @@ private Container getContainerFromGame(string containerName, OsuGame game)
private void initializeUpdater()
{
+ if (game == null)
+ {
+ Logger.Log("OsuGame is null, returning...");
+ return;
+ }
+
var children = new Drawable[]
{
- handler = new WsLoader
+ handler = new WebSocketLoader
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
- Updater = new Updater(handler)
+ Updater = new TrackerHub(handler)
};
- if (game != null)
- {
- var container = this.getContainerFromGame("mfosu Gosumemory compat container", game);
+ var container = this.getContainerFromGame("mfosu Gosumemory compat container", game);
+ container.AddRange(children);
- if (container.Children.Count == 0)
- container.AddRange(children);
- }
+ Logger.Log("Added gosu compat!");
}
[BackgroundDependencyLoader]
private void load(OsuGame? game)
{
+ Logger.Log("Gosu compat injector!");
+
+ AlwaysPresent = true;
+
this.Anchor = Anchor.Centre;
this.Origin = Anchor.Centre;
this.RelativeSizeAxes = Axes.Both;
this.Size = new Vector2(0.6f);
+ Logger.Log($"Updater is {Updater}");
if (Updater == null)
initializeUpdater();
}
- private WsLoader? handler;
+ private WebSocketLoader? handler;
}
}
diff --git a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/AbstractTracker.cs b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/AbstractTracker.cs
new file mode 100644
index 0000000..8a6f3ff
--- /dev/null
+++ b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/AbstractTracker.cs
@@ -0,0 +1,34 @@
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Timing;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Tracker;
+
+public partial class AbstractTracker : CompositeDrawable
+{
+ protected TrackerHub Hub { get; private set; }
+
+ public AbstractTracker(TrackerHub hub)
+ {
+ this.Hub = hub;
+ AlwaysPresent = true;
+
+ InternalChild = new OsuSpriteText
+ {
+ Text = $"{this}",
+ Margin = new MarginPadding(30)
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ this.Clock = new FramedClock(null, false);
+ }
+
+ public virtual void UpdateValues()
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/BeatmapStrainTracker.cs b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/BeatmapStrainTracker.cs
new file mode 100644
index 0000000..83687c7
--- /dev/null
+++ b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/BeatmapStrainTracker.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions;
+using osu.Framework.Logging;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Tracker;
+
+///
+/// 计算pp/密度表
+///
+public partial class BeatmapStrainTracker : AbstractTracker
+{
+ public BeatmapStrainTracker(TrackerHub hub)
+ : base(hub)
+ {
+ }
+
+ private double invokeTime = 0d;
+ private WorkingBeatmap? beatmapOnInvoke;
+
+ private CancellationTokenSource? ppStrainCancellationTokenSource;
+
+ private bool scheduleStrainComputes;
+
+ private readonly Bindable working = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load(Bindable globalWorking)
+ {
+ this.working.BindTo(globalWorking);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ working.BindValueChanged(e =>
+ {
+ this.UpdateStrain(e.NewValue);
+ });
+ }
+
+ protected override void Update()
+ {
+ if (scheduleStrainComputes && beatmapOnInvoke == working.Value)
+ UpdateStrain(this.working.Value);
+ }
+
+ public void UpdateStrain(WorkingBeatmap workingBeatmap)
+ {
+ this.invokeTime = Clock.CurrentTime;
+ beatmapOnInvoke = workingBeatmap;
+ scheduleStrainComputes = false;
+
+ ppStrainCancellationTokenSource?.Cancel();
+ ppStrainCancellationTokenSource = new CancellationTokenSource();
+
+ Task.Run(async () => await updateStrain(workingBeatmap), ppStrainCancellationTokenSource.Token)
+ .ContinueWith(task =>
+ {
+ if (!task.IsCompleted) return;
+
+ if (task.Exception != null)
+ {
+ Logging.LogError(task.Exception, "Error occurred while updating strain");
+ return;
+ }
+
+ this.Schedule(() =>
+ {
+ float[] result = task.GetResultSafely();
+ Hub.GetDataRoot().MenuValues.pp.Strains = result;
+ });
+ });
+ }
+
+ private Task updateStrain(WorkingBeatmap workingBeatmap)
+ {
+ try
+ {
+ double length = workingBeatmap.Track.Length;
+
+ //WorkingBeatmap.TrackLoaded: true + WorkingBeatmap.Track.IsLoaded: false -> Track Length: 0
+ if (length <= 0)
+ {
+ //持续5秒都没有音频,可能已经损坏,清空分布
+ //todo: 没有音频的时候使用谱面长度来计算并更新分布和进度
+ if (Clock.CurrentTime - invokeTime >= 10 * 1000)
+ {
+ Hub.GetDataRoot().MenuValues.pp.Strains = new[] { 0f };
+
+ Logger.Log("谱面音频在10秒内都没有加载,将放弃计算物件分布...", level: LogLevel.Important);
+ return Task.FromResult(new[] { 0f });
+ }
+
+ scheduleStrainComputes = true;
+ return Task.FromResult(new [] { 0f });
+ }
+
+ scheduleStrainComputes = false;
+
+ // 最大分段数和密度缩放
+ int maximumSegments = 512;
+ double segmentScale = 1;
+
+ // 根据歌曲长度分段,总共分为 (歌曲总时间(秒) * segScale) 段
+ int targetSegments = (int)(TimeSpan.FromMilliseconds(length).TotalSeconds * segmentScale);
+
+ // 限制最大分段数量
+ targetSegments = Math.Min(maximumSegments, targetSegments);
+ if (targetSegments <= 0) targetSegments = 1;
+
+ // 尝试自动转谱
+ var converter = workingBeatmap.BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapConverter(workingBeatmap.Beatmap);
+ IBeatmap? beatmap = null;
+
+ //Logger.Log($"Track length: {length} ~ Segments {targetSegments} ~ Conv? {converter.CanConvert()} ~ Loaded? {workingBeatmap.Track.IsLoaded} ~ Track? {workingBeatmap.Track}");
+ if (converter.CanConvert()) beatmap = converter.Convert();
+ var hitObjects = beatmap?.HitObjects ?? new HitObject[] { };
+
+ //获取每段的音频跨度
+ double audioStep = length / targetSegments;
+
+ //Segment -> Count
+ var segments = new Dictionary();
+
+ for (int i = 0; i < targetSegments; i++)
+ {
+ //此段的音频跨度
+ double startTime = i * audioStep;
+ double endTime = (1 + i) * audioStep;
+
+ //将跨度内的所有物件添加进来
+ //o -> [startTime, endTime)
+ int count = hitObjects.Count(o => o.StartTime < endTime && o.StartTime >= startTime);
+
+ segments.TryAdd(i, count);
+ }
+
+ //最后将其返回
+ return Task.FromResult(segments.Values.ToArray());
+ }
+ catch (Exception e)
+ {
+ return Task.FromException(e);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/BeatmapTracker.cs b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/BeatmapTracker.cs
new file mode 100644
index 0000000..d3fe4b9
--- /dev/null
+++ b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/BeatmapTracker.cs
@@ -0,0 +1,38 @@
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Logging;
+using osu.Game.Beatmaps;
+
+namespace osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Tracker;
+
+public partial class BeatmapTracker : AbstractTracker
+{
+ public BeatmapTracker(TrackerHub hub)
+ : base(hub)
+ {
+ }
+
+ private readonly Bindable beatmap = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load(Bindable globalBeatmap)
+ {
+ this.beatmap.BindTo(globalBeatmap);
+ }
+
+ protected override void LoadComplete()
+ {
+ Logger.Log("DDDLOADCOMPLETE");
+ base.LoadComplete();
+
+ this.beatmap.BindValueChanged(e =>
+ {
+ this.onBeatmapChanged(e.NewValue);
+ }, true);
+ }
+
+ private void onBeatmapChanged(WorkingBeatmap newBeatmap)
+ {
+ Hub.GetDataRoot().UpdateBeatmap(newBeatmap);
+ }
+}
diff --git a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/PPRulesetTracker.cs b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/PPRulesetTracker.cs
new file mode 100644
index 0000000..4e0e66b
--- /dev/null
+++ b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/PPRulesetTracker.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Data.Consts;
+using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.PP;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Scoring;
+
+namespace osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Tracker;
+
+///
+/// 用于更新一些游戏外的PP/游戏模式信息
+///
+public partial class PPRulesetTracker : AbstractTracker
+{
+ public PPRulesetTracker(TrackerHub hub)
+ : base(hub)
+ {
+ }
+
+ [Resolved]
+ private Bindable ruleset { get; set; } = null!;
+
+ [Resolved]
+ private IBindable> mods { get; set; } = null!;
+
+ private Ruleset? rsInstance;
+ private PerformanceCalculator? performanceCalculator;
+
+ private readonly Bindable working = new Bindable();
+
+ private CancellationTokenSource? ppCalcTokenSource;
+
+ [BackgroundDependencyLoader]
+ private void load(Bindable globalWorking)
+ {
+ this.working.BindTo(globalWorking);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ // Mods产生变动时视为谱面更新并重新计算PP
+ mods.BindValueChanged(e =>
+ {
+ var newMods = e.NewValue;
+ var dataRoot = Hub.GetDataRoot();
+
+ dataRoot.MenuValues.Mods.AppliedMods = newMods.Count;
+
+ if (newMods.Count >= 1)
+ {
+ string str = newMods.Aggregate("", (current, mod) => current + $"{mod.Acronym}");
+ dataRoot.MenuValues.Mods.Acronyms = str;
+ }
+ else
+ {
+ dataRoot.MenuValues.Mods.Acronyms = "NM";
+ }
+
+ working.TriggerChange();
+ }, true);
+
+ // 游戏模式产生变动时更新相关信息并视为谱面更新重新计算PP
+ ruleset.BindValueChanged(v =>
+ {
+ this.rsInstance = v.NewValue.CreateInstance();
+ this.performanceCalculator = rsInstance.CreatePerformanceCalculator();
+ Hub.GetDataRoot().GameplayValues.Gamemode = LegacyGamemodes.FromRulesetInfo(v.NewValue);
+
+ working.TriggerChange();
+ }, true);
+
+ // 谱面产生变动时更新PP信息
+ working.BindValueChanged(e =>
+ {
+ var modsCopy = mods.Value.Where(m => m.Acronym != "CL").Select(m => m.DeepClone()).ToArray();
+
+ runCalculateMaxPP(e.NewValue, modsCopy)
+ .ContinueWith(task =>
+ {
+ if (!task.IsCompleted) return;
+
+ this.Schedule(() =>
+ {
+ var result = task.GetResultSafely();
+
+ var dataRoot = Hub.GetDataRoot();
+ dataRoot.GameplayValues.pp.MaxThisPlay = dataRoot.GameplayValues.pp.PPIfFc = result.MaxPP;
+ dataRoot.MenuValues.pp.PPPerfect = result.MaxPP;
+ });
+ });
+ }, true);
+ }
+
+ private struct PerformanceInfo
+ {
+ public int MaxPP;
+ }
+
+ private Task runCalculateMaxPP(WorkingBeatmap workingBeatmap, Mod[] modsCopy)
+ {
+ ppCalcTokenSource?.Cancel();
+ ppCalcTokenSource = new CancellationTokenSource();
+
+ return Task.Run(async () => await calculateMaxmiumPerformancePoints(workingBeatmap, modsCopy).ConfigureAwait(false), ppCalcTokenSource.Token);
+ }
+
+ private Task calculateMaxmiumPerformancePoints(WorkingBeatmap workingBeatmap, Mod[] modsCopy)
+ {
+ int maxpp = 0;
+
+ try
+ {
+ var score = new ScoreInfo(workingBeatmap.BeatmapInfo, ruleset.Value)
+ {
+ Mods = modsCopy
+ };
+
+ PerformanceAttributes performanceAttribute;
+
+ if (!rsInstance?.CreateBeatmapConverter(workingBeatmap.Beatmap).CanConvert() ?? true)
+ performanceAttribute = new PerformanceAttributes();
+ else
+ {
+ // 使用此rsInstance的performanceCalc
+ performanceAttribute = this.performanceCalculator != null
+ ? new Calculator(rsInstance, this.performanceCalculator).CalculatePerfectPerformance(score, workingBeatmap)
+ : new PerformanceAttributes();
+ }
+
+ maxpp = (int)performanceAttribute.Total;
+ }
+ catch (Exception e)
+ {
+ Logging.LogError(e, "Error occurred while calculating performance point!");
+ }
+
+ var info = new PerformanceInfo
+ {
+ MaxPP = maxpp
+ };
+
+ return Task.FromResult(info);
+ }
+}
diff --git a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/ScreenTracker.cs b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/ScreenTracker.cs
new file mode 100644
index 0000000..a574e32
--- /dev/null
+++ b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Tracker/ScreenTracker.cs
@@ -0,0 +1,320 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Logging;
+using osu.Framework.Screens;
+using osu.Game.Beatmaps;
+using osu.Game.Online.API;
+using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Data.Consts;
+using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Data.Gameplay;
+using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Extensions;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens;
+using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
+using osu.Game.Screens.Ranking;
+
+namespace osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Tracker;
+
+public partial class ScreenTracker : AbstractTracker
+{
+ public ScreenTracker(TrackerHub hub)
+ : base(hub)
+ {
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ LocateScreenStack();
+
+ if (screenStack == null && !warningPrinted)
+ {
+ Logger.Log("无法定位到OsuScreenStack, 一些功能可能不会正常运作", level: LogLevel.Important);
+ warningPrinted = true;
+ }
+ }
+
+ [Resolved]
+ private OsuGame game { get; set; } = null!;
+
+ [Resolved]
+ private IAPIProvider api { get; set; } = null!;
+
+ private static bool warningPrinted;
+ private static OsuScreenStack? screenStack;
+
+ protected OsuScreenStack? getScreenStack()
+ {
+ return screenStack;
+ }
+
+ public void LocateScreenStack()
+ {
+ if (screenStack != null) return;
+
+ const BindingFlags flag = BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic;
+ var screenStackField = game.GetType().GetFields(flag).FirstOrDefault(f => f.FieldType == typeof(OsuScreenStack));
+
+ if (screenStackField == null) return;
+
+ object? val = screenStackField.GetValue(game);
+
+ if (val is not OsuScreenStack osuScreenStack) return;
+
+ screenStack = osuScreenStack;
+
+ screenStack.ScreenExited += onScreenSwitch;
+ screenStack.ScreenPushed += onScreenSwitch;
+ }
+
+ private Screens.Play.Player? playerScreen;
+
+ private ResultsScreen? resultsScreen;
+
+ private CancellationTokenSource? scorePPCalcTokenSource;
+
+ private string playerName = "???";
+
+ private HealthProcessorAccessor? healthProcessorAccessor;
+
+ private bool isInGame()
+ {
+ return playerScreen != null;
+ }
+
+ private CounterContainer? inGamePPCounter;
+
+ private int performanceThisRun = 0;
+
+ public override void UpdateValues()
+ {
+ base.UpdateValues();
+
+ if (!isInGame())
+ {
+ if (inGamePPCounter == null) return;
+
+ inGamePPCounter.Expire();
+ inGamePPCounter = null;
+
+ return;
+ }
+
+ var scoreInfo = playerScreen!.Score?.ScoreInfo.DeepClone();
+
+ //scoreInfo in EndlessPlayer would be null for somehow
+ if (scoreInfo == null)
+ return;
+
+ playerScreen!.GameplayState.ScoreProcessor.PopulateScore(scoreInfo);
+
+ var dataRoot = Hub.GetDataRoot();
+ dataRoot.GameplayValues.FromScore(scoreInfo);
+
+ if (inGamePPCounter == null)
+ AddInternal(inGamePPCounter = new CounterContainer(playerScreen.GameplayState, playerScreen.GameplayState.ScoreProcessor));
+
+ double health = 200 * (healthProcessorAccessor?.HealthProcessor.Health.Value ?? 0d);
+ dataRoot.GameplayValues.Hp.Smooth = dataRoot.GameplayValues.Hp.Normal;
+ dataRoot.GameplayValues.Hp.Normal = (float)health;
+
+ performanceThisRun = inGamePPCounter.Current.Value;
+ dataRoot.GameplayValues.pp.Current = this.performanceThisRun;
+ }
+
+ private void onScreenSwitch(IScreen prevScreen, IScreen nextScreen)
+ {
+ scorePPCalcTokenSource?.Cancel();
+
+ Logger.Log($"🦢 Screen Switch! {prevScreen} -> {nextScreen}");
+
+ this.playerScreen = null;
+ this.resultsScreen = null;
+
+ //updateCancellationTokenSource?.Cancel();
+
+ var dataRoot = Hub.GetDataRoot();
+
+ //从结算切换到其他页面:重置游玩数据
+ if (prevScreen is ResultsScreen || nextScreen is PlayerLoader)
+ {
+ performanceThisRun = 0;
+ dataRoot.GameplayValues.Reset();
+ }
+
+ healthProcessorAccessor?.Expire();
+ healthProcessorAccessor = null;
+
+ switch (nextScreen)
+ {
+ case ResultsScreen results:
+ this.onResultsScreen(results);
+ break;
+
+ case Screens.Play.Player player:
+ this.onPlayerScreen(player);
+ break;
+
+ default:
+ dataRoot.MenuValues.OsuState = OsuStates.IDLE;
+ break;
+ }
+
+ dataRoot.GameplayValues.Name = playerName = (playerScreen != null)
+ ? (playerScreen.Score?.ScoreInfo?.User.Username ?? "???")
+ : (resultsScreen != null ? (resultsScreen.SelectedScore.Value?.User.Username ?? "???") : api.LocalUser.Value.Username);
+ }
+
+ [Resolved]
+ private BeatmapDifficultyCache beatmapDifficultyCache { get; set; } = null!;
+
+ private void onResultsScreen(ResultsScreen results)
+ {
+ var score = results.SelectedScore.Value;
+ var dataRoot = Hub.GetDataRoot();
+
+ dataRoot.MenuValues.OsuState = OsuStates.IDLE;
+ this.resultsScreen = results;
+
+ if (score == null) return;
+
+ dataRoot.GameplayValues.FromScore(score);
+ dataRoot.GameplayValues.pp.Current = (int?)score.PP ?? 0;
+
+ if (score.PP.HasValue)
+ {
+ dataRoot.GameplayValues.pp.Current = (int)score.PP;
+ }
+ else
+ {
+ scorePPCalcTokenSource?.Cancel();
+ scorePPCalcTokenSource = new CancellationTokenSource();
+
+ if (score.BeatmapInfo != null)
+ {
+ // 参考了 osu.Game.Screens.Ranking.Expanded.Statistics.PerformanceStatistic
+ Task.Run(async () =>
+ {
+ double pp = await calculatePPFromScore(score, scorePPCalcTokenSource.Token).ConfigureAwait(false);
+ this.Schedule(() => dataRoot.GameplayValues.pp.Current = (int)Math.Floor(pp));
+ }, scorePPCalcTokenSource.Token);
+ }
+ else
+ {
+ Logger.Log("score.BeatmapInfo is null?! Not updating pp to gosu...");
+ }
+ }
+ }
+
+ private async Task calculatePPFromScore(ScoreInfo score, CancellationToken cancellationToken)
+ {
+ if (score.BeatmapInfo == null) return 0d;
+
+ var scorePPCalculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator();
+ var starDiff = await beatmapDifficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false);
+
+ if (starDiff?.Attributes == null || scorePPCalculator == null) return 0d;
+
+ var result = await scorePPCalculator.CalculateAsync(score, starDiff.Value.Attributes, cancellationToken).ConfigureAwait(false);
+ return result.Total;
+ }
+
+ private void onPlayerScreen(Screens.Play.Player player)
+ {
+ this.playerScreen = player;
+ Hub.GetDataRoot().MenuValues.OsuState = OsuStates.PLAYING;
+
+ Logger.Log("PLAYER!");
+
+ player.DimmableStoryboard?.Add(healthProcessorAccessor = new HealthProcessorAccessor());
+ }
+
+ private void updateLeaderboard(IList scoreInfos)
+ {
+ var list = new List();
+
+ int index = 0;
+ LeaderboardPlayer? ourPlayer = null;
+
+ foreach (var scoreInfo in scoreInfos)
+ {
+ var lbp = new LeaderboardPlayer
+ {
+ Name = scoreInfo.RealmUser.Username,
+ Score = (int)scoreInfo.TotalScore,
+ Combo = scoreInfo.Combo,
+ MaxCombo = scoreInfo.MaxCombo,
+ Mods = "NM",
+ Hit300 = scoreInfo.GetResultsPerfect(),
+ Hit100 = scoreInfo.GetResultsGreat(),
+ Hit50 = scoreInfo.Statistics.GetValueOrDefault(HitResult.Meh, 0),
+ HitMiss = scoreInfo.Statistics.GetValueOrDefault(HitResult.Miss, 0),
+ Team = 0,
+ Position = index
+ };
+
+ index++;
+
+ if (lbp.Name == playerName)
+ ourPlayer = lbp;
+ else
+ list.Add(lbp);
+ }
+
+ var dataRoot = Hub.GetDataRoot();
+ dataRoot.GameplayValues.Leaderboard.Slots = list.ToArray();
+ dataRoot.GameplayValues.Leaderboard.OurPlayer = ourPlayer;
+ }
+
+ private partial class HealthProcessorAccessor : CompositeDrawable
+ {
+ [Resolved]
+ private HealthProcessor healthProcessor { get; set; } = null!;
+
+ public HealthProcessor HealthProcessor => healthProcessor;
+ }
+
+ private partial class CounterContainer : CompositeDrawable
+ {
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ => dependencyContainer = new DependencyContainer(base.CreateChildDependencies(parent));
+
+ private DependencyContainer dependencyContainer = null!;
+ private readonly GameplayState gameplayState;
+ private readonly ScoreProcessor scoreProcessor;
+
+ public CounterContainer(GameplayState gameplayState, ScoreProcessor scoreProcessor)
+ {
+ this.gameplayState = gameplayState;
+ this.scoreProcessor = scoreProcessor;
+ }
+
+ private readonly PerformancePointsCounter counter = new PerformancePointsCounter
+ {
+ AlwaysPresent = true
+ };
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGame game)
+ {
+ dependencyContainer.CacheAs(typeof(GameplayState), gameplayState);
+ dependencyContainer.CacheAs(typeof(ScoreProcessor), scoreProcessor);
+
+ this.AlwaysPresent = true;
+ this.Alpha = 0;
+
+ this.AddInternal(counter);
+ }
+
+ public Bindable Current => counter.Current;
+ }
+}
diff --git a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/TrackerHub.cs b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/TrackerHub.cs
new file mode 100644
index 0000000..92f15a1
--- /dev/null
+++ b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/TrackerHub.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Logging;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics.UserInterfaceV2;
+using osu.Game.Overlays;
+using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Data;
+using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Tracker;
+using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Web;
+using osu.Game.Scoring;
+
+namespace osu.Game.Rulesets.IGPlayer.Feature.Gosumemory
+{
+ public partial class TrackerHub : CompositeDrawable
+ {
+ private readonly WebSocketLoader wsLoader;
+
+ public TrackerHub(WebSocketLoader game)
+ {
+ this.wsLoader = game;
+ }
+
+ public DataRoot GetDataRoot()
+ {
+ return wsLoader.DataRoot;
+ }
+
+ private readonly List trackers = new List();
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Logger.Log("Gosumemoty Compat!");
+
+ this.Clock = new FramedClock(null, false);
+
+ this.addTrackerRange(new AbstractTracker[]
+ {
+ new BeatmapStrainTracker(this),
+ new PPRulesetTracker(this),
+ new BeatmapTracker(this),
+ new ScreenTracker(this)
+ });
+
+#if DEBUG
+ AddInternal(new RoundedButton
+ {
+ Height = 60f,
+ Width = 60f,
+ Text = "Dump JSON",
+ Action = () =>
+ {
+ UpdateValues();
+
+ string str = JsonConvert.SerializeObject(wsLoader.DataRoot, Formatting.None, new JsonSerializerSettings
+ {
+ NullValueHandling = NullValueHandling.Include
+ });
+
+ Logger.Log(str);
+ }
+ });
+#endif
+ }
+
+ private void addTracker(AbstractTracker tracker)
+ {
+ if (trackers.Contains(tracker)) return;
+
+ trackers.Add(tracker);
+ this.AddInternal(tracker);
+ }
+
+ private void addTrackerRange(AbstractTracker[] trackers)
+ {
+ foreach (var abstractTracker in trackers)
+ this.addTracker(abstractTracker);
+ }
+
+ private void removeTracker(AbstractTracker tracker)
+ {
+ trackers.Remove(tracker);
+ if (this.InternalChildren.Contains(tracker))
+ this.RemoveInternal(tracker, true);
+ }
+
+ [Resolved]
+ private MusicController musicController { get; set; } = null!;
+
+ [Resolved]
+ private Bindable workingBeatmap { get; set; } = null!;
+
+ [Resolved]
+ private OsuGame osuGame { get; set; } = null!;
+
+ private double lastUpdate;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ //更新太快容易卡住网页
+ if (Clock.CurrentTime - lastUpdate < 200) return;
+
+ lastUpdate = Clock.CurrentTime;
+ UpdateValues();
+ }
+
+ //region InGame and PP
+
+ [Resolved]
+ private ScoreManager scoreManager { get; set; } = null!;
+
+ //endregion
+
+ //private CancellationTokenSource? updateCancellationTokenSource;
+
+ public void UpdateValues()
+ {
+ this.AlwaysPresent = true;
+
+ var obj = wsLoader.DataRoot;
+ obj.UpdateTrack(musicController.CurrentTrack);
+
+ foreach (var abstractTracker in trackers)
+ {
+ try
+ {
+ abstractTracker.UpdateValues();
+ }
+ catch (Exception e)
+ {
+ Logging.LogError(e, $"Error occurred while updating tracker {abstractTracker}, disabling this...");
+ }
+ }
+
+ try
+ {
+ string str = JsonConvert.SerializeObject(obj, Formatting.None, new JsonSerializerSettings
+ {
+ NullValueHandling = NullValueHandling.Include
+ });
+
+ this.wsLoader.Boardcast(str);
+ }
+ catch (Exception e)
+ {
+ Logger.Log($"Unable to update osu status to WebSocket: {e.Message}", level: LogLevel.Important);
+ Logger.Log(e.ToString());
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Updater.cs b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Updater.cs
deleted file mode 100644
index a7ed150..0000000
--- a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Updater.cs
+++ /dev/null
@@ -1,528 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Threading;
-using System.Threading.Tasks;
-using Newtonsoft.Json;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Logging;
-using osu.Framework.Screens;
-using osu.Framework.Timing;
-using osu.Game.Beatmaps;
-using osu.Game.Online.API;
-using osu.Game.Overlays;
-using osu.Game.Rulesets.Difficulty;
-using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Data.Consts;
-using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Data.Gameplay;
-using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Extensions;
-using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.PP;
-using osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Web;
-using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Scoring;
-using osu.Game.Scoring;
-using osu.Game.Screens;
-using osu.Game.Screens.Play;
-using osu.Game.Screens.Play.HUD;
-using osu.Game.Screens.Ranking;
-
-namespace osu.Game.Rulesets.IGPlayer.Feature.Gosumemory
-{
- public partial class Updater : CompositeDrawable
- {
- private readonly WsLoader wsLoader;
-
- public Updater(WsLoader game)
- {
- this.wsLoader = game;
- }
-
- [Resolved]
- private OsuGame game { get; set; } = null!;
-
- public void LocateScreenStack()
- {
- if (screenStack != null) return;
-
- const BindingFlags flag = BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic;
- var screenStackField = game.GetType().GetFields(flag).FirstOrDefault(f => f.FieldType == typeof(OsuScreenStack));
-
- if (screenStackField == null) return;
-
- object? val = screenStackField.GetValue(game);
-
- if (val is not OsuScreenStack osuScreenStack) return;
-
- this.screenStack = osuScreenStack;
-
- screenStack.ScreenExited += onScreenSwitch;
- screenStack.ScreenPushed += onScreenSwitch;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- if (screenStack == null)
- LocateScreenStack();
-
- if (this.screenStack == null)
- Logger.Log("无法定位到OsuScreenStack, 一些功能可能不会正常运作", level: LogLevel.Important);
-
- Logger.Log("Gosumemoty Compat!");
-
- this.Clock = new FramedClock(null, false);
-
- var obj = wsLoader.DataRoot;
-
- workingBeatmap.BindValueChanged(v =>
- {
- beatmapChangedTime = Clock.CurrentTime;
-
- if (!isInGame()) updateOverallPerformancePoints(workingBeatmap.Value, v.OldValue != v.NewValue);
- obj.UpdateBeatmap(v.NewValue);
- });
-
- ruleset.BindValueChanged(v =>
- {
- this.rsInstance = v.NewValue.CreateInstance();
- this.performanceCalculator = rsInstance.CreatePerformanceCalculator();
- wsLoader.DataRoot.GameplayValues.Gamemode = LegacyGamemodes.FromRulesetInfo(v.NewValue);
-
- workingBeatmap.TriggerChange();
- }, true);
-
- mods.BindValueChanged(v =>
- {
- obj.MenuValues.Mods.UpdateFrom(v.NewValue);
- workingBeatmap.TriggerChange();
- });
-
- updateOverallPerformancePoints(workingBeatmap.Value);
- }
-
- private OsuScreenStack? screenStack;
-
- [Resolved]
- private IBindable> mods { get; set; } = null!;
-
- [Resolved]
- private MusicController musicController { get; set; } = null!;
-
- [Resolved]
- private Bindable workingBeatmap { get; set; } = null!;
-
- [Resolved]
- private OsuGame osuGame { get; set; } = null!;
-
- [Resolved]
- private Bindable ruleset { get; set; } = null!;
-
- private double lastUpdate;
-
- protected override void Update()
- {
- base.Update();
-
- //更新太快容易卡住网页
- if (Clock.CurrentTime - lastUpdate < 200) return;
-
- lastUpdate = Clock.CurrentTime;
- UpdateValues();
- if (scheduleStrainComputes) updatePPStrains(workingBeatmap.Value);
- }
-
- //region InGame and PP
-
- private CounterContainer? inGamePPCounter;
-
- private int maxPP = 0;
- private int performanceThisRun = 0;
-
- [Resolved]
- private ScoreManager scoreManager { get; set; } = null!;
-
- [Resolved]
- private BeatmapDifficultyCache beatmapDifficultyCache { get; set; } = null!;
-
- private CancellationTokenSource? overallPPCancellationTokenSource;
-
- private void updateOverallPerformancePoints(WorkingBeatmap workingBeatmap, bool recalculateStrains = true)
- {
- // 排除Classic模组(会导致最大pp不准确)
- var modsCopy = mods.Value.Where(m => m.Acronym != "CL")
- .Select(m => m.DeepClone()).ToArray();
-
- overallPPCancellationTokenSource?.Cancel();
- overallPPCancellationTokenSource = new CancellationTokenSource();
-
- scheduleStrainComputes = true;
-
- Task.Run(async () => await updateOverallPPTask(workingBeatmap, modsCopy), overallPPCancellationTokenSource.Token);
- }
-
- private Task updateOverallPPTask(WorkingBeatmap workingBeatmap, Mod[] modsCopy)
- {
- int maxpp = 0;
- var obj = wsLoader.DataRoot;
-
- try
- {
- var score = new ScoreInfo(workingBeatmap.BeatmapInfo, ruleset.Value)
- {
- Mods = modsCopy
- };
-
- PerformanceAttributes performanceAttribute;
-
- if (!rsInstance?.CreateBeatmapConverter(workingBeatmap.Beatmap).CanConvert() ?? true)
- performanceAttribute = new PerformanceAttributes();
- else
- {
- // 使用此rsInstance的performanceCalc
- performanceAttribute = this.performanceCalculator != null
- ? new Calculator(rsInstance, this.performanceCalculator).CalculatePerfectPerformance(score, workingBeatmap)
- : new PerformanceAttributes();
- }
-
- maxpp = (int)performanceAttribute.Total;
- }
- catch (Exception)
- {
- }
-
- this.maxPP = maxpp;
- obj.GameplayValues.pp.MaxThisPlay = obj.GameplayValues.pp.PPIfFc = maxpp;
- obj.MenuValues.pp.PPPerfect = maxpp;
-
- return Task.CompletedTask;
- }
-
- private Ruleset? rsInstance;
-
- private CancellationTokenSource? ppStrainCancellationTokenSource;
-
- private bool scheduleStrainComputes;
-
- private double beatmapChangedTime;
-
- private void updatePPStrains(WorkingBeatmap workingBeatmap)
- {
- ppStrainCancellationTokenSource?.Cancel();
- ppStrainCancellationTokenSource = new CancellationTokenSource();
-
- Task.Run(() =>
- {
- try
- {
- double length = workingBeatmap.Track.Length;
-
- //WorkingBeatmap.TrackLoaded: true + WorkingBeatmap.Track.IsLoaded: false -> Track Length: 0
- if (length <= 0)
- {
- //持续5秒都没有音频,可能已经损坏,清空分布
- //todo: 没有音频的时候使用谱面长度来计算并更新分布和进度
- if (Clock.CurrentTime - beatmapChangedTime >= 5 * 1000)
- {
- wsLoader.DataRoot.MenuValues.pp.Strains = new[] { 0f };
- return;
- }
-
- scheduleStrainComputes = true;
- return;
- }
-
- scheduleStrainComputes = false;
-
- // 最大分段数和密度缩放
- int maximumSegments = 512;
- double segmentScale = 1;
-
- // 根据歌曲长度分段,每 (1 * segScale) 秒分一段
- int targetSegments = (int)(TimeSpan.FromMilliseconds(length).TotalSeconds * segmentScale);
- targetSegments = Math.Min(maximumSegments, targetSegments);
- if (targetSegments <= 0) targetSegments = 1;
-
- // 尝试自动转谱
- var converter = workingBeatmap.BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapConverter(workingBeatmap.Beatmap);
- IBeatmap? beatmap = null;
-
- //Logger.Log($"Track length: {length} ~ Segments {targetSegments} ~ Conv? {converter.CanConvert()} ~ Loaded? {workingBeatmap.Track.IsLoaded} ~ Track? {workingBeatmap.Track}");
- if (converter.CanConvert()) beatmap = converter.Convert();
- var hitObjects = beatmap?.HitObjects ?? new HitObject[] { };
-
- //获取每段的音频跨度
- double audioStep = length / targetSegments;
-
- //Segment -> Count
- var segments = new Dictionary();
-
- for (int i = 0; i < targetSegments; i++)
- {
- //此段的音频跨度
- double startTime = i * audioStep;
- double endTime = (1 + i) * audioStep;
-
- //将跨度内的所有物件添加进来
- //o -> [startTime, endTime)
- int count = hitObjects.Count(o => o.StartTime < endTime && o.StartTime >= startTime);
-
- segments.TryAdd(i, count);
- }
-
- //最后添加到DataRoot里
- wsLoader.DataRoot.MenuValues.pp.Strains = segments.Values.ToArray();
- }
- catch (Exception e)
- {
- }
- }, ppStrainCancellationTokenSource.Token);
- }
-
- private PerformanceCalculator? performanceCalculator;
-
- //endregion
-
- private bool isInGame()
- {
- return playerScreen != null;
- }
-
- private Screens.Play.Player? playerScreen;
-
- private ResultsScreen? resultsScreen;
-
- private CancellationTokenSource? scorePPCalcTokenSource;
-
- private string playerName = "???";
-
- private void onScreenSwitch(IScreen prevScreen, IScreen nextScreen)
- {
- scorePPCalcTokenSource?.Cancel();
-
- this.playerScreen = null;
- this.resultsScreen = null;
-
- //updateCancellationTokenSource?.Cancel();
-
- var dataRoot = wsLoader.DataRoot;
-
- //从结算切换到其他页面:重置游玩数据
- if (prevScreen is ResultsScreen || nextScreen is PlayerLoader)
- {
- performanceThisRun = 0;
- dataRoot.GameplayValues.Reset();
- }
-
- healthProcessorAccessor?.Expire();
- healthProcessorAccessor = null;
-
- switch (nextScreen)
- {
- case ResultsScreen results:
- var score = results.SelectedScore.Value;
-
- if (score != null)
- {
- dataRoot.GameplayValues.FromScore(score);
- dataRoot.GameplayValues.pp.Current = (int?)score.PP ?? 0;
-
- if (score.PP.HasValue)
- {
- dataRoot.GameplayValues.pp.Current = (int)score.PP;
- }
- else
- {
- scorePPCalcTokenSource?.Cancel();
- scorePPCalcTokenSource = new CancellationTokenSource();
-
- if (score.BeatmapInfo == null)
- {
- Logger.Log("score.BeatmapInfo is null?! Not updating pp to gosu...");
- }
- else
- {
- // 参考了 osu.Game.Screens.Ranking.Expanded.Statistics.PerformanceStatistic
- Task.Run(async () =>
- {
- var scorePPCalculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator();
- var starDiff = await beatmapDifficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods).ConfigureAwait(false);
-
- double pp = 0d;
-
- if (starDiff?.Attributes != null && scorePPCalculator != null)
- {
- var result = await scorePPCalculator.CalculateAsync(score, starDiff.Value.Attributes, scorePPCalcTokenSource.Token).ConfigureAwait(false);
- pp = result.Total;
- }
-
- this.Schedule(() => dataRoot.GameplayValues.pp.Current = (int)Math.Floor(pp));
- }, scorePPCalcTokenSource.Token);
- }
- }
-
- dataRoot.MenuValues.Mods.UpdateFrom(score.Mods.Where(m => m.Acronym != "CL").ToArray());
- }
-
- dataRoot.MenuValues.OsuState = OsuStates.PLAYING;
- this.resultsScreen = results;
- break;
-
- case Screens.Play.Player player:
- this.playerScreen = player;
- dataRoot.MenuValues.OsuState = OsuStates.PLAYING;
-
- player.DimmableStoryboard?.Add(healthProcessorAccessor = new HealthProcessorAccessor());
- break;
-
- default:
- dataRoot.MenuValues.OsuState = OsuStates.IDLE;
- break;
- }
-
- dataRoot.GameplayValues.Name = playerName = (playerScreen != null)
- ? (playerScreen.Score?.ScoreInfo?.User.Username ?? "???")
- : (resultsScreen != null ? (resultsScreen.SelectedScore.Value?.User.Username ?? "???") : api.LocalUser.Value.Username);
- }
-
- private void updateLeaderboard(IList scoreInfos)
- {
- var list = new List();
-
- int index = 0;
- LeaderboardPlayer? ourPlayer = null;
-
- foreach (var scoreInfo in scoreInfos)
- {
- var lbp = new LeaderboardPlayer
- {
- Name = scoreInfo.RealmUser.Username,
- Score = (int)scoreInfo.TotalScore,
- Combo = scoreInfo.Combo,
- MaxCombo = scoreInfo.MaxCombo,
- Mods = "NM",
- Hit300 = scoreInfo.GetResultsPerfect(),
- Hit100 = scoreInfo.GetResultsGreat(),
- Hit50 = scoreInfo.Statistics.GetValueOrDefault(HitResult.Meh, 0),
- HitMiss = scoreInfo.Statistics.GetValueOrDefault(HitResult.Miss, 0),
- Team = 0,
- Position = index
- };
-
- index++;
-
- if (lbp.Name == playerName)
- ourPlayer = lbp;
- else
- list.Add(lbp);
- }
-
- wsLoader.DataRoot.GameplayValues.Leaderboard.Slots = list.ToArray();
- wsLoader.DataRoot.GameplayValues.Leaderboard.OurPlayer = ourPlayer;
- }
-
- private HealthProcessorAccessor? healthProcessorAccessor;
-
- [Resolved]
- private IAPIProvider api { get; set; } = null!;
-
- //private CancellationTokenSource? updateCancellationTokenSource;
-
- public void UpdateValues()
- {
- this.AlwaysPresent = true;
-
- var obj = wsLoader.DataRoot;
- obj.UpdateTrack(musicController.CurrentTrack);
-
- if (isInGame())
- {
- var scoreInfo = playerScreen!.Score?.ScoreInfo.DeepClone();
-
- //scoreInfo in EndlessPlayer would be null for somehow
- if (scoreInfo == null)
- return;
-
- playerScreen!.GameplayState.ScoreProcessor.PopulateScore(scoreInfo);
-
- obj.GameplayValues.FromScore(scoreInfo);
-
- if (inGamePPCounter == null)
- AddInternal(inGamePPCounter = new CounterContainer(playerScreen.GameplayState, playerScreen.GameplayState.ScoreProcessor));
-
- double health = 200 * (healthProcessorAccessor?.HealthProcessor.Health.Value ?? 0d);
- obj.GameplayValues.Hp.Smooth = obj.GameplayValues.Hp.Normal;
- obj.GameplayValues.Hp.Normal = (float)health;
-
- performanceThisRun = inGamePPCounter.Current.Value;
- obj.GameplayValues.pp.Current = this.performanceThisRun;
- }
-
- if (inGamePPCounter != null && !isInGame())
- {
- inGamePPCounter.Expire();
- inGamePPCounter = null;
- }
-
- try
- {
- string str = JsonConvert.SerializeObject(obj, Formatting.None, new JsonSerializerSettings
- {
- NullValueHandling = NullValueHandling.Include
- });
-
- this.wsLoader.Boardcast(str);
- }
- catch (Exception e)
- {
- Logger.Log($"Unable to update osu status to WebSocket: {e.Message}", level: LogLevel.Important);
- Logger.Log(e.ToString());
- }
- }
-
- private partial class HealthProcessorAccessor : CompositeDrawable
- {
- [Resolved]
- private HealthProcessor healthProcessor { get; set; } = null!;
-
- public HealthProcessor HealthProcessor => healthProcessor;
- }
-
- private partial class CounterContainer : CompositeDrawable
- {
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
- => dependencyContainer = new DependencyContainer(base.CreateChildDependencies(parent));
-
- private DependencyContainer dependencyContainer = null!;
- private readonly GameplayState gameplayState;
- private readonly ScoreProcessor scoreProcessor;
-
- public CounterContainer(GameplayState gameplayState, ScoreProcessor scoreProcessor)
- {
- this.gameplayState = gameplayState;
- this.scoreProcessor = scoreProcessor;
- }
-
- private readonly PerformancePointsCounter counter = new PerformancePointsCounter
- {
- AlwaysPresent = true
- };
-
- [BackgroundDependencyLoader]
- private void load(OsuGame game)
- {
- dependencyContainer.CacheAs(typeof(GameplayState), gameplayState);
- dependencyContainer.CacheAs(typeof(ScoreProcessor), scoreProcessor);
-
- this.AlwaysPresent = true;
- this.Alpha = 0;
-
- this.AddInternal(counter);
- }
-
- public Bindable Current => counter.Current;
- }
- }
-}
diff --git a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Web/WsServer.cs b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Web/WebSocketLoader.cs
similarity index 91%
rename from osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Web/WsServer.cs
rename to osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Web/WebSocketLoader.cs
index 474b7d8..bf3a836 100644
--- a/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Web/WsServer.cs
+++ b/osu.Game.Rulesets.IGPlayer/Feature/Gosumemory/Web/WebSocketLoader.cs
@@ -10,14 +10,20 @@
namespace osu.Game.Rulesets.IGPlayer.Feature.Gosumemory.Web
{
- public partial class WsLoader : CompositeDrawable
+ public partial class WebSocketLoader : CompositeDrawable
{
public readonly DataRoot DataRoot = new DataRoot();
+ public WebSocketLoader()
+ {
+ AlwaysPresent = true;
+ }
+
[BackgroundDependencyLoader]
private void load()
{
- Schedule(() => startServer());
+ Logger.Log("WS LOAD!");
+ Schedule(startServer);
}
public void Restart()
diff --git a/osu.Game.Rulesets.IGPlayer/Helper/Injectors/OsuGameInjector.cs b/osu.Game.Rulesets.IGPlayer/Helper/Injectors/OsuGameInjector.cs
index d673198..a0e7bad 100644
--- a/osu.Game.Rulesets.IGPlayer/Helper/Injectors/OsuGameInjector.cs
+++ b/osu.Game.Rulesets.IGPlayer/Helper/Injectors/OsuGameInjector.cs
@@ -32,7 +32,7 @@ public static int GetRegisteredSessionHash()
public static bool InjectDependencies(Storage storage, OsuGame gameInstance, Scheduler scheduler)
{
- int sessionHashCode = gameInstance.GetHashCode();
+ int sessionHashCode = gameInstance.Toolbar.GetHashCode();
if (currentSessionHash == sessionHashCode)
{
diff --git a/osu.Game.Rulesets.IGPlayer/IGPlayerRuleset.cs b/osu.Game.Rulesets.IGPlayer/IGPlayerRuleset.cs
index 3e736a4..a26f3b3 100644
--- a/osu.Game.Rulesets.IGPlayer/IGPlayerRuleset.cs
+++ b/osu.Game.Rulesets.IGPlayer/IGPlayerRuleset.cs
@@ -129,7 +129,7 @@ private void load(OsuGame game, Storage storage, IModelImporter
}
};
- Logger.Log("[IGPlayer] Injecting dependencies...");
+ Logger.Log("[IGPlayer] Injecting dependencies.d..");
Logger.Log($"Deps: Game = '{game}' :: Storage = '{storage}' :: Importer = '{beatmapImporter}' :: IAPIProvider = '{api}'");
if (OsuGameInjector.InjectDependencies(storage, game, this.Scheduler)) return;