|
@@ -0,0 +1,410 @@
|
|
|
+using System;
|
|
|
+using System.ComponentModel;
|
|
|
+using System.Reflection;
|
|
|
+using Newtonsoft.Json;
|
|
|
+using WebSocketSharp;
|
|
|
+
|
|
|
+namespace BsWidget.BSDataPuller;
|
|
|
+
|
|
|
+internal sealed class BsDataPullerClient
|
|
|
+{
|
|
|
+ private bool _isRunning;
|
|
|
+ private WebSocket _webSocketLd;
|
|
|
+ private WebSocket _webSocketMd;
|
|
|
+
|
|
|
+ public event EventHandler<BsDataPullerEventArgs> Event;
|
|
|
+
|
|
|
+ public void Start()
|
|
|
+ {
|
|
|
+ _isRunning = true;
|
|
|
+
|
|
|
+ _webSocketLd = new WebSocket($"ws://{Program.Host}:2946/BSDataPuller/LiveData");
|
|
|
+ _webSocketLd.OnClose += WebSocket_OnClose;
|
|
|
+ _webSocketLd.OnError += WebSocket_OnError;
|
|
|
+ _webSocketLd.OnMessage += WebSocketLd_OnMessage;
|
|
|
+ _webSocketLd.ConnectAsync();
|
|
|
+
|
|
|
+ _webSocketMd = new WebSocket($"ws://{Program.Host}:2946/BSDataPuller/MapData");
|
|
|
+ _webSocketMd.OnClose += WebSocket_OnClose;
|
|
|
+ _webSocketMd.OnError += WebSocket_OnError;
|
|
|
+ _webSocketMd.OnMessage += WebSocketMd_OnMessage;
|
|
|
+ _webSocketMd.ConnectAsync();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Stop()
|
|
|
+ {
|
|
|
+ _isRunning = false;
|
|
|
+ _webSocketLd.Close();
|
|
|
+ _webSocketMd.Close();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void WebSocket_OnError(object sender, ErrorEventArgs e)
|
|
|
+ {
|
|
|
+ if (_isRunning) ((WebSocket)sender).Connect();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void WebSocket_OnClose(object sender, CloseEventArgs e)
|
|
|
+ {
|
|
|
+ if (_isRunning) ((WebSocket)sender).Connect();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void WebSocketLd_OnMessage(object sender, MessageEventArgs e)
|
|
|
+ {
|
|
|
+ var evt = JsonConvert.DeserializeObject<LiveData>(e.Data);
|
|
|
+ OnEvent(new BsDataPullerEventArgs { Type = BsDataPullerEventArgsType.LiveData, LiveData = evt });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void WebSocketMd_OnMessage(object sender, MessageEventArgs e)
|
|
|
+ {
|
|
|
+ var evt = JsonConvert.DeserializeObject<MapData>(e.Data);
|
|
|
+ OnEvent(new BsDataPullerEventArgs { Type = BsDataPullerEventArgsType.MapData, MapData = evt });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnEvent(BsDataPullerEventArgs e) => Event?.Invoke(this, e);
|
|
|
+}
|
|
|
+
|
|
|
+internal class BsDataPullerEventArgs
|
|
|
+{
|
|
|
+ public BsDataPullerEventArgsType Type { get; set; }
|
|
|
+ public LiveData LiveData { get; set; }
|
|
|
+ public MapData MapData { get; set; }
|
|
|
+}
|
|
|
+
|
|
|
+internal enum BsDataPullerEventArgsType
|
|
|
+{
|
|
|
+ LiveData = 1,
|
|
|
+ MapData = 2,
|
|
|
+}
|
|
|
+
|
|
|
+public class MapData
|
|
|
+{
|
|
|
+ #region Level
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// This can remain <see href="false"/> even if <see cref="LevelFailed"/> is <see href="true"/>,
|
|
|
+ /// when <see cref="Modifiers.NoFailOn0Energy"/> is <see href="true"/>.
|
|
|
+ /// </remarks>
|
|
|
+ /// <value>Default is <see href="false"/>.</value>
|
|
|
+ public bool InLevel { get; set; }
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="false"/>.</value>
|
|
|
+ public bool LevelPaused { get; set; }
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="false"/>.</value>
|
|
|
+ public bool LevelFinished { get; set; }
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="false"/>.</value>
|
|
|
+ public bool LevelFailed { get; set; }
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="false"/>.</value>
|
|
|
+ public bool LevelQuit { get; set; }
|
|
|
+
|
|
|
+ #endregion Level
|
|
|
+
|
|
|
+ #region Map
|
|
|
+
|
|
|
+ /// <summary>The hash ID for the current map.</summary>
|
|
|
+ /// <remarks><see href="null"/> if the hash could not be determined (e.g. if the map is not a custom level).</remarks>
|
|
|
+ /// <value>Default is <see href="null"/>.</value>
|
|
|
+ public string? Hash { get; set; }
|
|
|
+
|
|
|
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
|
+
|
|
|
+ /// <summary>The name of the current map.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see cref="string.Empty"/>.</value>
|
|
|
+ [DefaultValue("")]
|
|
|
+ public string SongName { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The sub-name of the current map.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see cref="string.Empty"/>.</value>
|
|
|
+ [DefaultValue("")]
|
|
|
+ public string SongSubName { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The author of the song.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see cref="string.Empty"/>.</value>
|
|
|
+ [DefaultValue("")]
|
|
|
+ public string SongAuthor { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The mapper of the current chart.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see cref="string.Empty"/>.</value>
|
|
|
+ [DefaultValue("")]
|
|
|
+ public string Mapper { get; set; }
|
|
|
+
|
|
|
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks><see href="null"/> if the BSR key could not be obtained.</remarks>
|
|
|
+ /// <value>Default is <see href="null"/>.</value>
|
|
|
+ public string? BSRKey { get; set; }
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks><see href="null"/> if the cover image could not be obtained.</remarks>
|
|
|
+ /// <value>Default is <see href="null"/>.</value>
|
|
|
+ public string? CoverImage { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The duration of the map in seconds.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+ public int Duration { get; set; }
|
|
|
+
|
|
|
+ #endregion Map
|
|
|
+
|
|
|
+ #region Difficulty
|
|
|
+
|
|
|
+#pragma warning disable CS8618
|
|
|
+
|
|
|
+ /// <summary>The type of map.</summary>
|
|
|
+ /// <remarks>i.e. Standard, 360, OneSaber, etc.</remarks>
|
|
|
+ /// <value>Default is <see cref="string.Empty"/>.</value>
|
|
|
+ [DefaultValue("")]
|
|
|
+ public string MapType { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The standard difficulty label of the map.</summary>
|
|
|
+ /// <remarks>i.e. Easy, Normal, Hard, etc.</remarks>
|
|
|
+ /// <value>Default is <see cref="string.Empty"/>.</value>
|
|
|
+ [DefaultValue("")]
|
|
|
+ public string Difficulty { get; set; }
|
|
|
+
|
|
|
+#pragma warning restore CS8618
|
|
|
+
|
|
|
+ /// <summary>The custom difficulty label set by the mapper.</summary>
|
|
|
+ /// <remarks><see href="null"/> if there is none.</remarks>
|
|
|
+ /// <value>Default is <see cref="string.Empty"/>.</value>
|
|
|
+ public string? CustomDifficultyLabel { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The beats per minute of the current map.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+ public int BPM { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The note jump speed of the current map.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+ public double NJS { get; set; }
|
|
|
+
|
|
|
+#pragma warning disable CS8618
|
|
|
+
|
|
|
+ /// <summary>The modifiers selected by the player for the current level.</summary>
|
|
|
+ /// <remarks>i.e. No fail, No arrows, Ghost notes, etc.</remarks>
|
|
|
+ /// <value>Default is <see cref="Data.Modifiers"/>.</value>
|
|
|
+ public Modifiers Modifiers { get; set; }
|
|
|
+
|
|
|
+#pragma warning restore CS8618
|
|
|
+
|
|
|
+ /// <summary>The score multiplier set by the users selection of modifiers.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="1.0"/>.</value>
|
|
|
+ [DefaultValue(1.0f)]
|
|
|
+ public float ModifiersMultiplier { get; set; }
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="false"/>.</value>
|
|
|
+ public bool PracticeMode { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The modifiers selected by the user that are specific to practice mode.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see cref="Data.PracticeModeModifiers"/>.</value>
|
|
|
+#pragma warning disable CS8618
|
|
|
+ public PracticeModeModifiers PracticeModeModifiers { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The amount Play Points this map is worth.</summary>
|
|
|
+ /// <remarks><see href="0"/> if the map is unranked or the value was undetermined.</remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+#pragma warning restore CS8618
|
|
|
+ public double PP { get; set; }
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks><see href="0"/> if the value was undetermined.</remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+ public double Star { get; set; }
|
|
|
+
|
|
|
+ #endregion Difficulty
|
|
|
+
|
|
|
+ #region Misc
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see cref="UnityEngine.Application.version"/>.</value>
|
|
|
+ [JsonProperty]
|
|
|
+ public static readonly string GameVersion;
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value><see cref="System.Version"/>.</value>
|
|
|
+ [JsonProperty]
|
|
|
+ public static readonly string PluginVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(3);
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="false"/>.</value>
|
|
|
+ [DefaultValue(false)]
|
|
|
+ public bool IsMultiplayer { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The previous local record set by the player for this map specific mode and difficulty.</summary>
|
|
|
+ /// <remarks><see href="0"/> if the map variant hasn't never been played before.</remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+ public int PreviousRecord { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The BSR key fore the last played map.</summary>
|
|
|
+ /// <remarks><para>
|
|
|
+ /// <see href="null"/> if there was no previous map or the previous maps BSR key was undetermined.<br/>
|
|
|
+ /// This value won't be updated if the current map is the same as the last.
|
|
|
+ /// </para></remarks>
|
|
|
+ /// <value>Default is <see href="null"/>.</value>
|
|
|
+ public string? PreviousBSR { get; set; }
|
|
|
+
|
|
|
+ #endregion Misc
|
|
|
+}
|
|
|
+
|
|
|
+public class LiveData
|
|
|
+{
|
|
|
+ #region Score
|
|
|
+
|
|
|
+ /// <summary>The current raw score.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+ public int Score { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The current score with the player selected multipliers applied.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+ public int ScoreWithMultipliers { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The maximum possible raw score for the current number of cut notes.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+ public int MaxScore { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The maximum possible score with the player selected multipliers applied for the current number of cut notes.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+ public int MaxScoreWithMultipliers { get; set; }
|
|
|
+
|
|
|
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
|
+
|
|
|
+ /// <summary>The <see cref="string"/> rank label for the current score.</summary>
|
|
|
+ /// <remarks>i.e. SS, S, A, B, etc.</remarks>
|
|
|
+ /// <value>Default is <see href="SSS"/>.</value>
|
|
|
+ [DefaultValue("SSS")]
|
|
|
+ public string Rank { get; set; }
|
|
|
+
|
|
|
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="true"/>.</value>
|
|
|
+ [DefaultValue(true)]
|
|
|
+ public bool FullCombo { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The total number of notes spawned since the start position of the song until the current position in the song.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+ public int NotesSpawned { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The current note cut combo count without error.</summary>
|
|
|
+ /// <remarks>Resets back to <see href="0"/> when the player: misses a note, hits a note incorrectly, takes damage or hits a bomb.</remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+ public int Combo { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The total number of missed and incorrectly hit notes since the start position of the song until the current position in the song.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+ public int Misses { get; set; }
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="100"/>.</value>
|
|
|
+ [DefaultValue(100)]
|
|
|
+ public double Accuracy { get; set; }
|
|
|
+
|
|
|
+ /// <summary></summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="50"/>.</value>
|
|
|
+ [DefaultValue(50)]
|
|
|
+ public double PlayerHealth { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The individual scores for the last hit note.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="Data.SBlockHitScore"/>.</value>
|
|
|
+ public SBlockHitScore BlockHitScore { get; set; }
|
|
|
+
|
|
|
+
|
|
|
+ #endregion Score
|
|
|
+
|
|
|
+ #region Misc
|
|
|
+
|
|
|
+ /// <summary>The total amount of time in seconds since the start of the map.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see href="0"/>.</value>
|
|
|
+ [DefaultValue(0)]
|
|
|
+ public int TimeElapsed { get; set; }
|
|
|
+
|
|
|
+ /// <summary>The event that caused the update trigger to be fired.</summary>
|
|
|
+ /// <remarks></remarks>
|
|
|
+ /// <value>Default is <see cref="ELiveDataEventTriggers.Unknown"/>.</value>
|
|
|
+ [DefaultValue(ELiveDataEventTriggers.Unknown)]
|
|
|
+ public ELiveDataEventTriggers EventTrigger { get; set; }
|
|
|
+
|
|
|
+ #endregion Misc
|
|
|
+}
|
|
|
+
|
|
|
+public enum ELiveDataEventTriggers
|
|
|
+{
|
|
|
+ Unknown = 0,
|
|
|
+ TimerElapsed,
|
|
|
+ NoteMissed,
|
|
|
+ EnergyChange,
|
|
|
+ ScoreChange
|
|
|
+}
|
|
|
+
|
|
|
+public class PracticeModeModifiers
|
|
|
+{
|
|
|
+ public float SongSpeedMul { get; set; }
|
|
|
+ public bool StartInAdvanceAndClearNotes { get; set; }
|
|
|
+ public float SongStartTime { get; set; }
|
|
|
+}
|
|
|
+
|
|
|
+public class Modifiers
|
|
|
+{
|
|
|
+ public bool NoFailOn0Energy { get; internal set; }
|
|
|
+ public bool OneLife { get; internal set; }
|
|
|
+ public bool FourLives { get; internal set; }
|
|
|
+ public bool NoBombs { get; internal set; }
|
|
|
+ public bool NoWalls { get; internal set; }
|
|
|
+ public bool NoArrows { get; internal set; }
|
|
|
+ public bool GhostNotes { get; internal set; }
|
|
|
+ public bool DisappearingArrows { get; internal set; }
|
|
|
+ public bool SmallNotes { get; internal set; }
|
|
|
+ public bool ProMode { get; internal set; }
|
|
|
+ public bool StrictAngles { get; internal set; }
|
|
|
+ public bool ZenMode { get; internal set; }
|
|
|
+ public bool SlowerSong { get; internal set; }
|
|
|
+ public bool FasterSong { get; internal set; }
|
|
|
+ public bool SuperFastSong { get; internal set; }
|
|
|
+}
|
|
|
+
|
|
|
+public struct SBlockHitScore
|
|
|
+{
|
|
|
+ /// <summary><see href="0"/> to <see href="70"/></summary>
|
|
|
+ public int PreSwing { get; internal set; }
|
|
|
+ /// <summary><see href="0"/> to <see href="30"/></summary>
|
|
|
+ public int PostSwing { get; internal set; }
|
|
|
+ /// <summary><see href="0"/> to <see href="15"/></summary>
|
|
|
+ public int CenterSwing { get; internal set; }
|
|
|
+
|
|
|
+ public int Total => PreSwing + PostSwing + CenterSwing;
|
|
|
+}
|